2014多校4,hdu4897,hdu4898,hdu4902,hdu4906题解
来源:互联网 发布:弗洛伊德算法 path 编辑:程序博客网 时间:2024/05/17 07:57
多校4:
丽洁大神的思维果然不是一般人能懂。连看个标程都费劲得死。
所以人家能IOI第一呢。
本场又被吊打,不过结果是其它队做出来的1009是个错题……
好吧来看题目。
1001,hdu4897:树链剖分!为了做这题我才学的!
CLJ的代码风格太过诡异而不能看懂,我参照题解思考了一个算法:
线段树上保存两种标记,标记1是用于操作1,当翻转某条路径时,将该路径的标记1反转,并记录区间和。这个标记是用来表示边的,因此每个边的标记保存在该边两点中更深的一个节点上。
标记2用于操作2,因为一个节点可能连了很多轻链,因此修改节点来代替修改轻链,而重链则直接修改。用一条边的两个节点的标记2是否相同来表示这条边是否被操作2反转。因此对一条路径做操作2就是,就是将路径上重链的标记2反转,并将这条重链链头链尾相邻的重链也修改(修改标记1)。
这样查询某条边是否是黑就是
轻链:a.tag1^a.tag2^b.tag2,a、b是此边的两个端点,dep[a]>dep[b]。(轻链只有两个点!)
重链:用区间求和计算路径上的tag1的和。
链头链尾要小心。
#include<iostream>#include<cstdio>#include<cstring>using namespace std;#define ls (p<<1)#define rs (p<<1|1)#define NN 201000int en[NN],fi[NN],te,ne[NN],v[NN];int tp;int siz[NN],son[NN],top[NN],fa[NN],dep[NN],tid[NN],rank[NN];void addedge(int a,int b){ ++te;ne[te]=fi[a];fi[a]=te;v[te]=b; ++te;ne[te]=fi[b];fi[b]=te;v[te]=a;}void dfs1(int u,int father,int d){ siz[u]=1; dep[u]=d; fa[u]=father; int e,vv; //printf("%d\n",u); for(e=fi[u];e!=-1;e=ne[e]){ vv=v[e]; if (vv!=father){ dfs1(vv,u,d+1); siz[u]+=siz[vv]; if (son[u]==-1||siz[vv]>siz[son[u]]){ son[u]=vv; } } }}void dfs2(int u,int ttop){ top[u]=ttop; tid[u]=++tp; rank[tid[u]]=u; if (son[u]==-1) return; dfs2(son[u],ttop); int e,vv; for(e=fi[u];e!=-1;e=ne[e]){ vv=v[e]; if (vv!=fa[u]&&vv!=son[u]){ dfs2(vv,vv); } }}struct segtree{ int l,r,tag1,tag2,rev1,rev2;}t[NN*4];inline void Rev(int p,int func){ if (func==1) {t[p].rev1^=1;t[p].tag1=t[p].r+1-t[p].l-t[p].tag1;} if (func==2) {t[p].rev2^=1;t[p].tag2^=1;}}void lazy(int p){ if (t[p].l==t[p].r) {t[p].rev1=t[p].rev2=0;return;} if (t[p].rev1!=0) { t[p].rev1=0; Rev(ls,1); Rev(rs,1); } if (t[p].rev2!=0) { t[p].rev2=0; Rev(ls,2); Rev(rs,2); }}void build(int l,int r,int p){ t[p].l=l;t[p].r=r; t[p].tag1=t[p].tag2=t[p].rev1=t[p].rev2=0; if (l==r){return;} int m=l+r>>1; build(l,m,ls); build(m+1,r,rs);}inline void update(int p){ t[p].tag1=t[ls].tag1+t[rs].tag1;}void rev(int l,int r,int func,int p){ if (l>r) return; if (t[p].l==l&&t[p].r==r){ Rev(p,func); return; } lazy(p); int m=t[p].l+t[p].r>>1; if (r<=m) rev(l,r,func,ls); else if (l>m) rev(l,r,func,rs); else { rev(l,m,func,ls); rev(m+1,r,func,rs); } update(p);}int query(int l,int r,int p){ if (l>r) return 0; if (t[p].l==l&&t[p].r==r){ return t[p].tag1; } lazy(p); int m=t[p].l+t[p].r>>1; if (r<=m) return query(l,r,ls); else if (l>m) return query(l,r,rs); else { return query(l,m,ls)+query(m+1,r,rs); } update(p);}int querytag2(int pos,int p){ if (t[p].l==t[p].r) return t[p].tag2; lazy(p); int m=t[p].l+t[p].r>>1; if (pos<=m) return querytag2(pos,ls); else return querytag2(pos,rs); update(p);}void linerev(int a,int b){ while(top[a]!=top[b]){ if (dep[top[a]]<dep[top[b]]) swap(a,b); rev(tid[top[a]],tid[a],1,1); a=fa[top[a]]; } if (dep[a]>dep[b]) swap(a,b); rev(tid[a]+1,tid[b],1,1);//注意加1,最高点不用改变}void linerevadj(int a,int b){ while(top[a]!=top[b]){ if (dep[top[a]]<dep[top[b]]) swap(a,b); rev(tid[top[a]],tid[a],2,1); int faa=fa[top[a]]; if (son[faa]==top[a]) rev(tid[top[a]],tid[top[a]],1,1); if (son[a]!=-1) rev(tid[son[a]],tid[son[a]],1,1); a=fa[top[a]]; } if (dep[a]>dep[b]) swap(a,b); rev(tid[a],tid[b],2,1);//这里不用加1,路径上每个点都需要更改tag2标记 int faa=fa[a]; if (faa!=0&&son[faa]==a) rev(tid[a],tid[a],1,1); if (son[b]!=-1) rev(tid[son[b]],tid[son[b]],1,1);}int linequery(int a,int b){ int ret=0; while(top[a]!=top[b]){ if (dep[top[a]]<dep[top[b]]) swap(a,b); ret+=query(tid[top[a]]+1,tid[a],1); ret+=(querytag2(tid[fa[top[a]]],1)^querytag2(tid[top[a]],1)^query(tid[top[a]],tid[top[a]],1)); a=fa[top[a]]; } if (dep[a]>dep[b]) swap(a,b); ret+=query(tid[a]+1,tid[b],1); return ret;}int main(){ int cas,n,q,i,a,b,c; scanf("%d",&cas); while(cas--){ scanf("%d",&n); memset(fi,-1,sizeof(fi)); te=0; for(i=1;i<n;++i){ scanf("%d%d",&a,&b); addedge(a,b); } memset(son,-1,sizeof(son)); tp=0; dfs1(1,0,0); dfs2(1,1); build(1,tp,1); scanf("%d",&q); for(i=1;i<=q;++i){ scanf("%d%d%d",&a,&b,&c); if (a==1){ if (b==c) continue; linerev(b,c); } else if (a==2){ linerevadj(b,c); } else if (a==3){ printf("%d\n",linequery(b,c)); } } } return 0;}
1002,hdu4898:这题我几乎是把标程抄了一遍。
尤其是Check()函数我就无耻地直接Copy了一下。
这种让最大值最小、最小值最大的问题常用二分。
再讲一下Check函数的思路(想了好久),一个点可以跳到它的可跳最大值之内的任意点,将不可以往后跳的点都剔除,这样所有点都是连通的,最多可以跳剩下点数a次,最小就是每次跳最大值的次数b。因此k在[b,a]之间都是可行的。
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<vector>using namespace std;#define NN 4100char s[NN];int n,k;int tsub;int lcp[NN][NN];struct Sub{ int l,r; void init(int _l,int _r){l=_l;r=_r;} int size() {return r-l+1;} char charat(int x) { if (x>r-l) return 0; else return s[l+x]; }}sub[NN*NN];int Lcp(Sub a,Sub b){ return min(lcp[a.l][b.l],min(a.size(),b.size()));}bool operator < (Sub a,Sub b){ int m=Lcp(a,b); return a.charat(m)<b.charat(m);}int step[NN];bool check(Sub M) {vector<int> nxt(n);for (int i = 0; i < n; ++i) { Sub tmp; tmp.init(i,i+n-1);int L = Lcp(tmp, M);if (s[(i + L) % n] < M.charat(L))nxt[i] = n;elsenxt[i] = L;}for (;;) {bool done = true;for (int i = 0; i < nxt.size(); ++i) {if (nxt[i] == 0) {for (int j = 0; j < nxt.size(); ++j)if (j != i) {if (j < i && j + nxt[j] >= i)--nxt[j];else if (j > i && j + nxt[j] >= i + nxt.size())--nxt[j];}nxt.erase(nxt.begin() + i);done = false;break;}}if (done)break;}if (k > nxt.size())return false;for (int i = 0; i < nxt.size() * 2; ++i) {step[i] = i + nxt[i % nxt.size()];}for (int i = 0; i < nxt.size(); ++i) {int need = 0, at = i;while (at < i + nxt.size()) {at = step[at];++need;}if (need <= k)return true;}return false;}Sub work(){ int l=1,r=tsub,m,ans=r; int i,j,tot; while(l<r){ m=l+r>>1; Sub a=sub[m]; if (check(a)) { if (ans>m) ans=m; r=m-1; } else l=m+1; } return sub[ans];}void output(Sub a){ int i,r=a.r; for(i=a.l;i<=a.r;++i){ printf("%c",s[i]); } printf("\n");}int main(){ int cas; scanf("%d",&cas); int i,j; while(cas--){ scanf("%d%d",&n,&k); scanf("%s",s); for(i=0;i<n;++i){ s[n+i]=s[i]; } memset(lcp,0,sizeof(lcp)); for(i=n+n-1;i>=0;--i){ for(j=n+n-1;j>=0;--j){ if (s[i]==s[j]) lcp[i][j]=lcp[i+1][j+1]+1; else lcp[i][j]=0; } } tsub=0; Sub tmp; for(i=0;i<n+n;++i){ for(j=i;j<n+n;++j){ if (j-i+1>n) break; tmp.init(i,j); sub[++tsub]=tmp; } } sort(sub+1,sub+tsub+1); tmp=work(); output(tmp); } return 0;}
1006,hdu4902:线段树!
神奇诡异的证明,反正我是不会。不过看到那么多队伍都通过,就怀疑着写了一发。(居然过了什么的…)
做法看代码吧,用一个标记标记一段相同,用一个标记标记段最大值,如果2操作的值比这段的最大值大,就无视这次操作,否则就暴力往下做。由于gcd是越来越小,而且一个数取gcd最多不超过32次。想想应该操作次数应该不会太多。
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define ls (p<<1)#define rs (p<<1|1)#define NN 101000int val[NN],cas,n;struct tree{ int l,r,ma,res,val;}t[NN*4];void lazy(int p){ if (t[p].res==0) return; else if (t[p].res==1){ t[p].res=0; t[p].ma=t[p].val; if (t[p].l==t[p].r) return; t[ls].res=1;t[ls].ma=t[ls].val=t[p].val; t[rs].res=1;t[rs].ma=t[rs].val=t[p].val; }}void build(int l,int r,int p){ t[p].l=l;t[p].r=r; t[p].res=0; if (l==r) {t[p].ma=t[p].val=val[l];return;} int m=(l+r)>>1; build(l,m,ls); build(m+1,r,rs); t[p].ma=max(t[ls].ma,t[rs].ma);}void update(int l,int r,int val,int p){ if (t[p].l==l&&t[p].r==r) { t[p].res=1; t[p].val=t[p].ma=val; return; } lazy(p); int m=(t[p].l+t[p].r)>>1; if (r<=m) update(l,r,val,ls); else if (l>m) update(l,r,val,rs); else { update(l,m,val,ls); update(m+1,r,val,rs); } t[p].ma=max(t[ls].ma,t[rs].ma);}void fk(int l,int r,int val,int p){ if (t[p].l==t[p].r){ t[p].res=0; if (t[p].val>val) t[p].val=__gcd(val,t[p].val); return; } if (t[p].l==l&&t[p].r==r&&t[p].res==1) { if (t[p].val>=val) { t[p].val=__gcd(val,t[p].val); } return; } lazy(p); int m=(t[p].l+t[p].r)>>1; if (t[p].l==l&&t[p].r==r){ if (t[p].ma<=val) ; else{ fk(l,m,val,ls); fk(m+1,r,val,rs); t[p].ma=max(t[ls].ma,t[rs].ma); } return; } lazy(p); if (r<=m) fk(l,r,val,ls); else if (l>m) fk(l,r,val,rs); else { fk(l,m,val,ls); fk(m+1,r,val,rs); } t[p].ma=max(t[ls].ma,t[rs].ma);}int query(int pos,int p){ if (t[p].l==t[p].r){ return t[p].val; } lazy(p); int m=(t[p].l+t[p].r)>>1; if (pos<=m) return query(pos,ls); else return query(pos,rs);}int main(){ //freopen("1006in.txt","r",stdin); int i,a,b,c,d,q; scanf("%d",&cas); while(cas--){ scanf("%d",&n); for(i=1;i<=n;++i){ scanf("%d",&val[i]); } build(1,n,1); scanf("%d",&q); for(i=1;i<=q;++i){ scanf("%d%d%d%d",&a,&b,&c,&d); if (a==1){ update(b,c,d,1); } else if (a==2){ fk(b,c,d,1); } } for(i=1;i<=n;++i){ printf("%d ",query(i,1)); } printf("\n"); } return 0;}
1010,hdu4906:状态压缩DP
用dp[i][state]表示到第i个数能凑出state中有1的位的数的子序列有多少个。O(2^20*20*20)的复杂度。被卡掉。后来赵老师提醒0的状态很多,特判可以节省不少时间,再交,还是被卡。最后把memset改成for赋初值才通过。其实可以不用滚动数组,小的状态只会影响大的状态,状态从大到小dp就可以了。哎,真傻。
#include<iostream>#include<cstdio>#include<cstring>using namespace std;int mod=1000000007;int dp[2][2200000];int n,k,l;int cas,tn,kl,tmpk,now,pas,i,j,o;long long ns;int intns;int ans;int main(){ //freopen("1010in.txt","r",stdin); scanf("%d",&cas); tn=0; while(cas--){ scanf("%d%d%d",&n,&k,&l); memset(dp,0,sizeof(dp)); tn=(1<<k); kl=min(k,l); tmpk=1; if (l>k) tmpk+=l-k; now=0; dp[0][0]=1; for(i=1;i<=n;++i){ pas=now;now=1-now; for(j=0;j<tn;++j) dp[now][j]=0; //memset(dp[now],0,sizeof(dp[now])); for(j=0;j<tn;++j){ if (dp[pas][j]==0) continue; dp[now][j]=(dp[now][j]+((long long)dp[pas][j]*tmpk%mod))%mod; for(o=1;o<=kl;++o){ ns=(long long)j; ns=j|(ns<<(long long)o)|(1ll<<(o-1)); intns=(int)(ns%tn); dp[now][intns]=(dp[now][intns]+dp[pas][j])%mod; } } } ans=0; for(i=0;i<tn;++i){ if ((1<<(k-1))&i) ans=(ans+dp[now][i])%mod; } printf("%d\n",ans); } return 0;}
- 2014多校4,hdu4897,hdu4898,hdu4902,hdu4906题解
- hdu4897
- hdu4906 Our happy ending 2014 Multi-University Training Contest 4
- hdu4902
- 入门题 _14多校—线段树 HDU4902 Nice boat
- 【动态树】hdu4897
- hdu4897(树链剖分)
- hdu4906:3-idiots【FFT】
- HDU4902 Nice Boat(14多校联合4-1006)线段树区间更新
- HDU4902 Nice boat
- hdu4902 线段树
- HDU4902 线段树
- 线段树lazy标记??Hdu4902
- hdu4902 线段树区间更新
- HDU4902(线段树)练习题
- hdu4897 Little Devil I(树链剖分+线段树)
- hdu4906 Our happy ending --- 状压dp
- hdu4906 Our happy ending,状态压缩DP
- 【图像处理】简单的图像处理软件
- UML之类图(Class Diagram)
- Android DVM 記憶體管理研究分析
- IO复用、多进程和多线程三种并发编程模型
- poj1577~Falling Leaves~二叉排序树~
- 2014多校4,hdu4897,hdu4898,hdu4902,hdu4906题解
- License控制实现原理(20140808)
- URAL1005 - Stone Pile - 动态规划
- ListView的一个小应用
- I'm Telling the Truth(二分匹配)
- JSP引入JS文件路径错误原因分析
- 编写Android.mk中的LOCAL_SRC_FILES的终极技巧
- Android App 内存泄露之调试工具(1)
- HTML5开发-在你的游戏应用中加入广告