NOIP2015 day2 [二分][DP][树剖]

2024-01-01 07:58
文章标签 dp 二分 day2 noip2015 树剖

本文主要是介绍NOIP2015 day2 [二分][DP][树剖],希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

T1:
题意:在一个n长序列中取走m个数,使得任意相邻数之间差值最小值最大。
分析:要最大值最小最小值最大,典型的二分答案套路。二分答案以后把不满足的数给去掉(因为去掉前一个肯定不会比去掉当前这个更优),然后注意最后一个也要算,如果要用的次数不够了就不满足,然后就没了。

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=5e4+5;
int l,n,m,a[maxn];
void init()
{scanf("%d %d %d",&l,&n,&m);for(int i=1;i<=n;i++)scanf("%d",&a[i]);
}
int check(int lim)
{int last=0,tmp=m;for(int i=1;i<=n;i++)if(a[i]-last<lim){if(tmp)tmp--;else return 0;}else last=a[i];if((!tmp)&&l-last<lim)return 0;return 1;
}
void work()
{int lef=0,rig=l+1;while(lef<rig){int mid=(lef+rig)/2;if(check(mid))lef=mid+1;else rig=mid;}printf("%d",lef-1);
}
int main()
{freopen("stone.in","r",stdin);freopen("stone.out","w",stdout);init();work();return 0;
}

题意:给定两个序列,在一个序列中取若干子序列(不重合),按顺序组成第二个序列,问组成方式的种数。
分析:DP,F[k][I][J]表示枚举到第k个字母,要组成第二个序列的后i个字母,还可以取j个子序列的方案数,那么针对当前情况只有两种情况,一是正在枚举的两个字母相同且后两个字母相同,则不需要再取一个序列(当然还要满足正在被枚举的不是最后一个,已经取了一个序列的条件),当然,还有一种是正在枚举的两个字母相同,那么无论后面的字母相不相同都可以再取一个序列;
需要注意四点:
1.显然组以后一个点的空间是开不下的,这里可以滚动数组,因为只需要用当前和之前一次的信息所以第一维开3就可以了。(之前信息是因为,当处于第一种情况时,加上的方法数要保证只包含取了上一个的方法,不然会多);
2.要加上上次的同一状态的的方法总数,即不取此位但满足的方法总数;
3.由第一点可知,这里涉及到减法,那么要注意方法数减少的情况,虽说一般不会影响,但如果减到负数去了取模方式就不一样了……而且还可能输出负数。
4.4e7可能会T,要注意常数,比如每个运算mod一次显然比直接开Longlong要常数小,以及……开滚动数组肯定比直接赋值常数小(手动再见;

#include<iostream>
#include<cstdio>
#include<cstring>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn1=1e3+5,maxn2=2e2+5;
const int modd=1e9+7;
int cur=1,nxt=2,nxt2,n,m,k;
int f[3][maxn2][maxn2];
char a[maxn1],b[maxn2];
void init()
{scanf("%d %d %d",&n,&m,&k);f[cur][0][0]=1;scanf("%s",a);scanf("%s",b);
}
void work()
{for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)for(int z=1;z<=k;z++){f[nxt][j][z]=0;if(a[n-i]==b[m-j])f[nxt][j][z]+=f[cur][j-1][z-1];if(a[n-i]==b[m-j]&&i!=1&&j!=1&&z&&a[n-i+1]==b[m-j+1])f[nxt][j][z]+=(f[cur][j-1][z]-f[nxt2][j-1][z]+modd)%modd;f[nxt][j][z]%=modd;if(f[cur][j][z])f[nxt][j][z]+=f[cur][j][z];f[nxt][j][z]%=modd;}cur++;nxt++;nxt2++;cur%=3;nxt%=3;nxt2%=3; f[cur][0][0]=1;}printf("%d",f[cur][m][k]);
}
int main()
{freopen("substring.in","r",stdin);freopen("substring.out","w",stdout);init();work();return 0;
}

T3:
题意:
对于给定的树,要在上面走若干次,可以把任意一条路径的代价变为0,求变化后,所有行程同时开始,完成时间的最大值的最小值。
分析:
又是一个最大值的最小值问题,很多人会想到二分。
二分容易被卡常,当然常数把握得好的话,tarjan和树剖是可以过的。这也是大多数AC代码的解法。也是我觉得这道题没水准的一个地方,啊,这种解法大多数人都能想出来吧……
出题人给的标准解法是趋近于O(N)的解法,大意是先用radix sort对边按权值排序(边的权值可以tarjan强制O(N)求出来),然后再从大到小枚举边,每次ans=min(ans,max(roa[i+1].wor,roa[1].wor-val),其中val是交集中最长边的权值。
不过卡在两条边求交集上了,记得上次有人讲是一条边一直缩到在另一条边上为止,不过怎样O(1)实现我还是不太清楚,问了很多人也都不会,希望有大神看到的话可以指点一下,THX!
我写的二分加树剖,WINDOWS下不调栈的大小会爆栈,不过评测的时候可以过毕竟评测机的栈大小和内存大小一致。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define clr(x) memset(x,0,sizeof(x))
using namespace std;
const int maxn=3e5+5;
int sta,fin,n,m,cnt,lll,mini;
int tov[2*maxn],fr[maxn],des[2*maxn],siz[maxn],w[maxn],wor[maxn*2],val[maxn];
int fa[maxn],dep[maxn],son[maxn],tii[maxn],top[maxn],son2[maxn],sig[maxn];
struct node1
{int lef,rig,tot;
}a[maxn*4];
struct node2
{int sta,fin,wor;
}b[maxn];
bool cmp(node2 x,node2 y)
{return x.wor>y.wor;
}
void addedge(int cur)
{scanf("%d %d %d",&sta,&fin,&wor[2*cur-1]);tov[2*cur-1]=fr[sta];fr[sta]=2*cur-1;des[2*cur-1]=fin;tov[2*cur]=fr[fin];fr[fin]=2*cur;des[2*cur]=sta;wor[2*cur]=wor[2*cur-1];
}
void dfs(int u,int fath,int ste)
{siz[u]=1;fa[u]=fath;dep[u]=ste;for(int i=fr[u];i;i=tov[i])if(des[i]!=fath){dfs(des[i],u,ste+1);siz[u]+=siz[des[i]];if(siz[des[i]]>siz[son[u]]){son[u]=des[i];son2[u]=i;}}
}
void dfs2(int u,int fath,int t,int www)
{top[u]=t;tii[u]=++cnt;w[cnt]=www;if(son[u])dfs2(son[u],u,t,wor[son2[u]]);for(int i=fr[u];i;i=tov[i])if(des[i]!=fath&&des[i]!=son[u])dfs2(des[i],u,des[i],wor[i]);
}
void pushup(int u)
{a[u].tot=a[2*u].tot+a[2*u+1].tot;
}
void build(int u,int lef,int rig)
{a[u].lef=lef;a[u].rig=rig;if(lef==rig){a[u].tot=w[lef];return;}int mid=(lef+rig)>>1;build(2*u,lef,mid);build(2*u+1,mid+1,rig);pushup(u);
}
int querytot(int u,int lef,int rig)
{if(a[u].lef==lef&&a[u].rig==rig)return a[u].tot;int mid=(a[u].lef+a[u].rig)>>1;if(rig<=mid)return querytot(2*u,lef,rig);else if(lef>mid)return querytot(2*u+1,lef,rig);return querytot(2*u,lef,mid)+querytot(2*u+1,mid+1,rig);
}
int querytotctrl(int lef,int rig)
{int fa1=top[lef],fa2=top[rig],ans=0;while(fa1!=fa2){if(dep[fa1]<dep[fa2]){swap(fa1,fa2);swap(lef,rig);}ans+=querytot(1,tii[fa1],tii[lef]);lef=fa[fa1];fa1=top[lef];}if(lef==rig)return ans;if(tii[lef]>tii[rig])swap(lef,rig);ans+=querytot(1,tii[son[lef]],tii[rig]);return ans;
}
void workedge(int cur)
{scanf("%d %d",&sta,&fin);b[cur].sta=sta;b[cur].fin=fin;b[cur].wor=querytotctrl(sta,fin);
}
int lca(int lef,int rig)
{int fa1=top[lef],fa2=top[rig];while(fa1!=fa2){if(dep[fa1]<dep[fa2]){swap(fa1,fa2);swap(lef,rig);}lef=fa[fa1];fa1=top[lef];}return fa1;
}
void dfs3(int u,int fath)
{sig[u]=val[u];for(int i=fr[u];i;i=tov[i])if(des[i]!=fath){dfs3(des[i],u);sig[u]+=sig[des[i]];if(sig[des[i]]==lll)mini=max(mini,wor[i]);}
}
int check(int lim)
{for(lll=1;lll<=m;lll++)if(b[lll].wor<=lim)break;lll--;clr(val);for(int i=1;i<=lll;i++){int bxqmz=lca(b[i].sta,b[i].fin);val[bxqmz]-=2;val[b[i].sta]++;val[b[i].fin]++;}mini=0;dfs3(1,1);if(b[1].wor-mini<=lim)return 1;return 0;
}
void init()
{scanf("%d %d",&n,&m);for(int i=1;i<n;i++)addedge(i);dfs(1,0,1);dfs2(1,0,1,0);build(1,1,n);for(int i=1;i<=m;i++)workedge(i);sort(b+1,b+m+1,cmp);
}
void work()
{int lef=0,rig=3e8+1;while(lef<rig){int mid=(lef+rig)>>1;if(check(mid))rig=mid;else lef=mid+1;}printf("%d",lef);
}
int main()
{freopen("transport.in","r",stdin);freopen("transport.out","w",stdout);init();work();return 0;
}

关于树上差分,常用的有两种:(之前写的是lca和fa[lca]–,然后判断的时候是只要子节点满足就可以,这种方法显然可以举出反例,但居然AC了,只能说数据太水啊太水……

1、找出被所有路径都覆盖的边

在树中将所有路径起、始权值加1,起、始点的lca权值减2,从所有叶节点开始把权值往上累加。

最终权值为路径数的点到其父亲的边为所求边。

2、 将每条路径(s,t)上的每个点权值增加1,求各点权值

在树中将所有路径起、始权值加1,起、始点的lca权值减1,lca的父亲权值减1,从所有叶节点开始把权值往上累加。(上次做的松鼠的新家的差分方式)

这篇关于NOIP2015 day2 [二分][DP][树剖]的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/558591

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]

uva 10118 dP

题意: 给4列篮子,每次从某一列开始无放回拿蜡烛放入篮子里,并且篮子最多只能放5支蜡烛,数字代表蜡烛的颜色。 当拿出当前颜色的蜡烛在篮子里存在时,猪脚可以把蜡烛带回家。 问最多拿多少只蜡烛。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cs

uva 10069 DP + 大数加法

代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>#include <cl

uva 10029 HASH + DP

题意: 给一个字典,里面有好多单词。单词可以由增加、删除、变换,变成另一个单词,问能变换的最长单词长度。 解析: HASH+dp 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc