软件安装:树上分组DP/tarjan缩点/(也许基环树?)
提炼:tarjan环缩成点,建0虚根,跑树形DP,最难的是看出可能有n个点n条边然后缩点,n个点n条边可能不只有一个环
n个点n条边->基环树:
基环树,也是环套树,简单地讲就是树上在加一条边。
既然成环就必定要么全选要么全不选,直接缩成一个点即可。
我的错误:
1.第二次建图时跑的第一次的邻接表
2.在读入时就建立了虚根0,但tarjan缩完后将环变成了孤点,建虚根毫无作用
结论:要在有实际意义的前提上对算法作出改进!不能有啥是啥!
Code
#include<cstdio> using namespace std; const int N=105; const int V=505; int n,m,rt,num_bian,num_ibian,num_huan,num_tarjan,num_que, pw[N],pv[N],w[N],v[N],fm[N],to[N],head[N],nxt[N],ito[N],ihead[N],inxt[N],dfn[N],low[N],bel[N],que[N],in_que[N],dp[N][V],ru[N]; void add(int x,int y){ to[++num_bian]=y,fm[num_bian]=x,nxt[num_bian]=head[x],head[x]=num_bian; } void iadd(int x,int y){ ito[++num_ibian]=y,inxt[num_ibian]=ihead[x],ihead[x]=num_ibian,ru[y]++; } int min(int x,int y){return x>y?y:x;} int max(int x,int y){return x>y?x:y;} void tarjan(int x){ dfn[x]=low[x]=++num_tarjan; que[++num_que]=x;in_que[x]=1; for(int i=head[x],y;i;i=nxt[i]) if(!dfn[y=to[i]])tarjan(y),low[x]=min(low[x],low[y]); else if(in_que[y])low[x]=min(low[x],dfn[y]); if(dfn[x]==low[x]){ ++num_huan; int y; do{ y=que[num_que--]; in_que[y]=0; bel[y]=num_huan; }while(y!=x); } } void dfs(int x){ for(int i=ihead[x],y;i;i=inxt[i]){ dfs(y=ito[i]); for(int j=m;j>=0;--j)for(int k=j;k;--k) dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]); //printf("dp[%d][%d]=%d\n",x,j,dp[x][j]); } if(v[x]!=0)for(int i=m;i;--i) if(i>=v[x])dp[x][i]=dp[x][i-v[x]]+w[x]; else dp[x][i]=0; } void debug(){ for(int i=0;i<=n;++i)printf("%d ",bel[i]);puts(""); for(int i=num_huan;i;--i)printf(":%d %d ",w[i],v[i]);puts(""); printf("%d\n",rt); } int main(){ //freopen("text.in","r",stdin); //freopen("1.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)scanf("%d",&pv[i]);for(int i=1;i<=n;++i)scanf("%d",&pw[i]);for(int i=1,x;i<=n;++i){scanf("%d",&x);if(x)add(x,i);} for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i); for(int i=1;i<=num_bian;++i)if(bel[fm[i]]!=bel[to[i]])iadd(bel[fm[i]],bel[to[i]]); for(int i=0;i<=n;++i)w[bel[i]]+=pw[i],v[bel[i]]+=pv[i]; for(int i=1;i<=num_huan;++i)if(!ru[i])iadd(0,i); dfs(0); printf("%d\n",dp[0][m]); //debug(); return 0; }
我还贴心的准备了对拍(Linux)代码(其实是我拍了2h呜呜呜)
#include<bits/stdc++.h> using namespace std; int main(){ for(int i=1;i<=10000000;++i){ system("./1"); system("./2"); system("./rand"); if(system("diff 1.out 2.out")){ puts("Wrong Answer");return 0; } else puts("Accepted"); } }
rand 代码
#include<bits/stdc++.h> using namespace std; const int N=50; int main(){ freopen("text.in","w",stdout); srand((unsigned)time(0)); int n=rand()%N+1,m=rand()%N+1; printf("%d %d\n",n,m); for(int i=1;i<=n;++i){ int v=rand()%n+1; printf("%d ",v); }puts(""); for(int i=1;i<=n;++i){ int w=rand()%n+1; printf("%d ",w); }puts(""); for(int i=1,li;i<=n;++i){ do li=rand()%n; while(li==i); printf("%d ",li); }puts(""); }
注:读入text.in,正解1.cpp,输出1.out,你的错解是2.cpp,输出2.out
还不快谢谢我!
Keep it simple and stupid.