NOIP2015 day2 [二分][DP][树剖]
2016-10-23 18:27
351 查看
T1:
题意:在一个n长序列中取走m个数,使得任意相邻数之间差值最小值最大。
分析:要最大值最小最小值最大,典型的二分答案套路。二分答案以后把不满足的数给去掉(因为去掉前一个肯定不会比去掉当前这个更优),然后注意最后一个也要算,如果要用的次数不够了就不满足,然后就没了。
题意:给定两个序列,在一个序列中取若干子序列(不重合),按顺序组成第二个序列,问组成方式的种数。
分析:DP,F[k][I][J]表示枚举到第k个字母,要组成第二个序列的后i个字母,还可以取j个子序列的方案数,那么针对当前情况只有两种情况,一是正在枚举的两个字母相同且后两个字母相同,则不需要再取一个序列(当然还要满足正在被枚举的不是最后一个,已经取了一个序列的条件),当然,还有一种是正在枚举的两个字母相同,那么无论后面的字母相不相同都可以再取一个序列;
需要注意四点:
1.显然组以后一个点的空间是开不下的,这里可以滚动数组,因为只需要用当前和之前一次的信息所以第一维开3就可以了。(之前信息是因为,当处于第一种情况时,加上的方法数要保证只包含取了上一个的方法,不然会多);
2.要加上上次的同一状态的的方法总数,即不取此位但满足的方法总数;
3.由第一点可知,这里涉及到减法,那么要注意方法数减少的情况,虽说一般不会影响,但如果减到负数去了取模方式就不一样了……而且还可能输出负数。
4.4e7可能会T,要注意常数,比如每个运算mod一次显然比直接开Longlong要常数小,以及……开滚动数组肯定比直接赋值常数小(手动再见;
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下不调栈的大小会爆栈,不过评测的时候可以过毕竟评测机的栈大小和内存大小一致。
关于树上差分,常用的有两种:(之前写的是lca和fa[lca]–,然后判断的时候是只要子节点满足就可以,这种方法显然可以举出反例,但居然AC了,只能说数据太水啊太水……
1、找出被所有路径都覆盖的边
在树中将所有路径起、始权值加1,起、始点的lca权值减2,从所有叶节点开始把权值往上累加。
最终权值为路径数的点到其父亲的边为所求边。
2、 将每条路径(s,t)上的每个点权值增加1,求各点权值
在树中将所有路径起、始权值加1,起、始点的lca权值减1,lca的父亲权值减1,从所有叶节点开始把权值往上累加。(上次做的松鼠的新家的差分方式)
题意:在一个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,从所有叶节点开始把权值往上累加。(上次做的松鼠的新家的差分方式)
相关文章推荐
- BZOJ 4326 NOIP 2015 DAY2 T3 浅谈二分及树上差分数组DFS动态统计
- Noip2015 Day2 T1 跳石头(二分答案)
- 【NOIP 2015 Day2 T1】跳石头(二分)
- 跳石头_NOIP2015 Day2 T1_二分+贪心
- 【noip2015】【Day2】【二分】【倍增】运输计划
- 洛谷Luogu-267 跳石头(NOIP提高组2015 day2 t1)(二分答案) HQG_AC的博客
- NOIP 2015 Day2 T3 运输计划(二分+dfs序+树上差分+倍增LCA)
- noip 2015 day2 t1 跳石头 二分
- NOIP 2015 Day 2 transport 运输计划 (树链剖分 序列差分 二分答案)
- [NOIP2015提高组]子串 DP
- C++——NOIP2015提高组day2 t3——运输计划
- {题解}[jzoj4326]NOIP2015提高组Day2 跳石头
- noip2015 提高组day1、day2
- NOIP2015 跳石头 解题报告(二分答案)
- [BZOJ4326][NOIP2015][倍增][二分][差分序列]运输计划
- 【2015ZUFE新生赛网络同步赛M】【DP 打表 二分】GW I (3) 暴力预处理+分类打表二分查找
- NOIP2017 Day2 T2 宝藏(状压DP)
- 二分答案-----noip2015 跳石头
- NOIP2015运输计划(二分+dfs序+lca)
- NOIP2015 day2 解题报告