1.OJ1278战略游戏
f[u][0]代表以u为根的子树,u不放时,最少放置节点数。
f[u][1]代表以u为根的子树,u放时,最少放置节点数。
f[u][0]=Σf[son][1]。
f[u][1]=Σmin(f[son][1],f[son][0])。
ans=min(f[root][0],f[root][1])。
#include<cstdio> #include<iostream> using namespace std; const int maxn=1500; int n,root,tot,ans; int pre[maxn],now[maxn],son[maxn],f[maxn][2]; inline void read(int &x){ char ch; x=0; while (ch=getchar(),ch==' '||ch==' '); while (isdigit(ch)){ x=x*10+ch-'0'; ch=getchar(); } } inline void build(int u,int v){ pre[++tot]=now[u]; now[u]=tot; son[tot]=v; } void search(int u){ f[u][1]=1; int p=now[u]; while (p){ int v=son[p]; search(v); f[u][1]+=min(f[v][1],f[v][0]); f[u][0]+=f[v][1]; p=pre[p]; } } void init(){ read(n); root=(n-1)*n/2; int u,k,v; for (int i=1;i<=n;++i){ read(u),read(k); for (int j=1;j<=k;++j) read(v),root-=v,build(u,v); } } void work(){ search(root); ans=min(f[root][0],f[root][1]); printf("%d ",ans); } int main(){ init(); work(); return 0; }
2.OJ1264[Ural1018 ]二叉苹果树
f[u][i]代表以u为根的子树,保留i条边,最多能留下的苹果数。
f[u][0]=0。
枚举son,逆枚举i,f[u][i]=max(f[u][i],f[son][j]+f[u][i-j-1])。
ans=f[root][q]。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=115; int n,q,tot,now[maxn],pre[maxn<<1],son[maxn<<1],val[maxn<<1]; void connect(int u,int v,int w){pre[++tot]=now[u];now[u]=tot;son[tot]=v;val[tot]=w;} void init(){ scanf("%d%d",&n,&q); for (int u,v,w,i=1;i<=n-1;++i){ scanf("%d%d%d",&u,&v,&w); connect(u,v,w);connect(v,u,w); } } int f[maxn][maxn]; void tree_dp(int u,int fa){ f[u][0]=0; for (int p=now[u];p;p=pre[p]){ if (son[p]==fa) continue; tree_dp(son[p],u); for (int i=q;i>=1;--i) for (int j=0;j<=i-1;++j) f[u][i]=max(f[u][i],f[son[p]][j]+f[u][i-j-1]+val[p]); } } void work(){ memset(f,200,sizeof(f)); tree_dp(1,0); printf("%d ",f[1][q]); } int main(){ init(); work(); return 0; }
3.OJ1277有线电视网
f[u][i]代表以u为根的子树,满足子树中i个叶子节点,所获得最大的收益。
f[u][0]=0。
如果u是叶子节点,f[u][1]=v[u]。
否则,枚举son,逆枚举i,f[u][i]=max(f[u][i],f[son][j]-val[p]+f[u][i-j])。
注意j只需枚举到以son为根的子树的叶子数目即可,i=∑leaf_num(当前已枚举到的son)。
还可以将son按leaf_num排序从小到大排序,会快一些,但我并不会算复杂度。
ans=最大的i,满足f[root][i]>=0。
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=3e3+15; typedef pair<int,int> PII; int n,m,v[maxn];vector<PII> g[maxn]; void init(){ scanf("%d%d",&n,&m); for (int tot,i=1;i<=n-m;++i){ scanf("%d",&tot); for (int a,c,j=1;j<=tot;++j){ scanf("%d%d",&a,&c); g[i].push_back(make_pair(a,c)); } } for (int i=n-m+1;i<=n;++i) scanf("%d",&v[i]); } int f[maxn][maxn]; int tree_dp(int u){ f[u][0]=0; if (u>=n-m+1){f[u][1]=v[u];return 1;} int s,sum=0; for (unsigned int i=0;i<g[u].size();++i){ sum+=(s=tree_dp(g[u][i].first)); for (int j=sum;j>=1;--j) for (int k=1;k<=min(j,s);++k) f[u][j]=max(f[u][j],f[u][j-k]+f[g[u][i].first][k]-g[u][i].second); } return sum; } void work(){ memset(f,200,sizeof(f)); tree_dp(1); for (int i=m;i>=0;--i) if (f[1][i]>=0){ printf("%d ",i); break; } } int main(){ init(); work(); return 0; }
4.OJ1274“访问”艺术馆
f[u][i]代表以u为根的子树,花费了i的时间,所获得最大收益。
如果i是叶子节点,背包即可。
否则,枚举son,逆枚举i,f[u][i]=max(f[u][i],f[son][j]+f[u][i-j-2*val[p]])。
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef pair<int,int> PII; const int maxn=615,maxt=615; vector<PII> node[maxn]; int tim,cnt,len[maxn],son[maxn][2]; void init(int u){ int t,x;scanf("%d%d",&t,&x);len[u]=t; if (!x){init(son[u][0]=++cnt);init(son[u][1]=++cnt);} else{ for (int w,c,i=1;i<=x;++i){ scanf("%d%d",&w,&c); node[u].push_back(make_pair(w,c)); } } } int f[maxn][maxt]; void tree_dp(int u){ if (node[u].empty()){ tree_dp(son[u][0]); tree_dp(son[u][1]); for (int i=1;i<=tim;++i) for (int j=0;j<=i;++j){ int t=0; if (j>=2*len[son[u][0]]) t+=f[son[u][0]][j-2*len[son[u][0]]]; if (i-j>=2*len[son[u][1]]) t+=f[son[u][1]][i-j-2*len[son[u][1]]]; f[u][i]=max(f[u][i],t); } } else{ for (unsigned int i=0;i<node[u].size();++i) for (int j=tim;j>=node[u][i].second;--j) f[u][j]=max(f[u][j],f[u][j-node[u][i].second]+node[u][i].first); } } int main(){ scanf("%d",&tim);init(cnt=1); tim-=len[1]*2;tree_dp(1); printf("%d",f[1][tim-1]); return 0; }
5.OJ1216[Ioi2005]River
dis[u][j]代表u向上走j步的距离。
f[u][i][j]代表以u为根的子树建了i个伐木场(不包括u节点的),到跟的路径上第一个伐木场是u的第j个祖先。
枚举每个儿子,逆枚举i,
f[u][i][j]=min(f[u][i][j],f[son][k][j+1]+w[son]*dis[son][j+1]+f[u][i-k][j])。
if (i>k) f[u][i][j]=min(f[u][i][j],f[son][k][0]+f[u][i-k-1][j])。
ans=f[root][m][0]。
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=115,maxk=65; vector<int> g[maxn]; int n,m,w[maxn],fa[maxn],len[maxn]; void init(){ scanf("%d%d",&n,&m); for (int i=1;i<=n;++i){ scanf("%d%d%d",&w[i],&fa[i],&len[i]); g[fa[i]].push_back(i); } } typedef unsigned int uint; int dis[maxn],anc[maxn][maxn]; void prepare(int u,int dep){ anc[u][1]=fa[u];dis[u]=dis[fa[u]]+len[u]; for (int i=2;i<=dep;++i) anc[u][i]=anc[fa[u]][i-1]; for (uint i=0;i<g[u].size();++i) prepare(g[u][i],dep+1); } const int inf=1e9; int f[maxn][maxk][maxn],t[maxn][maxk][maxn]; void tree_dp(int u,int dep){ for (uint i=0;i<g[u].size();++i){ int v=g[u][i]; tree_dp(v,dep+1); for (int l=0;l<=dep;++l) for (int j=0;j<=m;++j){ f[u][j][l]=inf; for (int k=0;k<=j;++k){ if (j-k>0) f[u][j][l]=min(f[u][j][l],t[u][j-k-1][l]+f[v][k][0]); f[u][j][l]=min(f[u][j][l],t[u][j-k][l]+f[v][k][l+1]+w[v]*(dis[v]-dis[anc[v][l+1]])); } } memcpy(t[u],f[u],sizeof(t[u])); } } void work(){ prepare(0,0); tree_dp(0,0); printf("%d ",f[0][m][0]); } int main(){ init(); work(); return 0; }
6.OJ2412[Ahoi99]圣诞树游戏
f[i]代表将i点亮所需最小电流。
对于节点u,将儿子按f[son]从大到小排序,f[u]=max(f[u],f[son]+当前已枚举儿子数(不包括当前节点))。
ans=f[root]。
var x,n,m,j,k,i:longint; f:array[0..100] of longint; son:array[0..100,0..100] of longint; function max(p,q:longint):longint; begin if p>q then exit(p); exit(q); end; procedure sort(x,q:Longint); var t,i,j:longint; begin for i:=1 to q-1 do for j:=i+1 to q do if f[son[x,i]]<f[son[x,j]] then begin t:=f[son[x,i]]; f[son[x,i]]:=f[son[x,j]]; f[son[x,j]]:=t; end; end; procedure dfs(x:longint); var i:longint; begin for i:=1 to son[x,0] do dfs(son[x,i]); sort(x,son[x,0]); for i:=1 to son[x,0] do f[x]:=max(f[x],f[son[x,i]]+i-1); end; begin read(n); for i:=1 to n do begin read(x); inc(son[x,0]); son[x,son[x,0]]:=i; end; readln; for i:=1 to n do f[i]:=son[i,0]+1; dfs(1); writeln(f[1]); end.
7.OJ1217[baltic2003]gems
f[u][i]代表以u为根的子树,u节点数值为i,的最小数值和。
枚举son,枚举i,f[u][i]=min(f[son][j],j!=i)+i。
ans=min(f[root][i])。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=10015,maxm=5; int n,tot,now[maxn],pre[maxn<<1],son[maxn<<1]; void connect(int u,int v){pre[++tot]=now[u];now[u]=tot;son[tot]=v;} void init(){ scanf("%d",&n); for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v); connect(u,v);connect(v,u); } } int f[maxn][maxm]; void tree_dp(int u,int fa){ static int g[maxm]; for (int i=1;i<maxm;++i) f[u][i]=i; for (int p=now[u];p;p=pre[p]){ if (son[p]==fa) continue; tree_dp(son[p],u); memset(g,64,sizeof(g)); for (int i=1;i<maxm;++i){ for (int j=1;j<maxm;++j) if (i!=j) g[i]=min(g[i],f[son[p]][j]); f[u][i]+=g[i]; } } } void work(){ tree_dp(1,0);int res=1e9; for (int i=1;i<maxm;++i) res=min(res,f[1][i]); printf("%d ",res); } int main(){ init(); work(); return 0; }
8.OJ1326[Noi2003]逃学的小孩
对于树上三点a,b,c,求最大的dis[a][b]+dis[b][c],满足dis[a][b]<=dis[a][c]。
可以脑补dis[a][c]即为直径,a,c即为两端点,然后以a,c为源求disa,disc。
ans=max(dis[a][c]+min(dis[a][b],dis[c][b]))。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=2e5+15; typedef long long int64; int n,m,tot,now[maxn],pre[maxn<<1],son[maxn<<1],val[maxn<<1]; void connect(int u,int v,int w){pre[++tot]=now[u];now[u]=tot;son[tot]=v;val[tot]=w;} void init(){ scanf("%d%d",&n,&m); for (int u,v,w,i=1;i<=m;++i){ scanf("%d%d%d",&u,&v,&w); connect(u,v,w);connect(v,u,w); } } int fa[maxn],q[maxn];int64 dis[maxn]; void bfs(int s){ memset(dis,0,sizeof(dis)); int head=0,tail=1;q[1]=s;fa[s]=0; while (head!=tail){ int u=q[++head]; for (int p=now[u];p;p=pre[p]) if (son[p]!=fa[u]){ fa[son[p]]=u; q[++tail]=son[p]; dis[son[p]]=dis[u]+val[p]; } } } pair<int,int> node; void work(){ bfs(1);node.first=1; for (int i=2;i<=n;++i) if (dis[i]>dis[node.first]) node.first=i; bfs(node.first);node.second=1; for (int i=2;i<=n;++i) if (dis[i]>dis[node.second]) node.second=i; int64 d=dis[node.second]; static int64 dis1[maxn],dis2[maxn]; bfs(node.first);memcpy(dis1,dis,sizeof(dis)); bfs(node.second);memcpy(dis2,dis,sizeof(dis)); int64 res=0; for (int i=1;i<=n;++i) res=max(res,min(dis1[i],dis2[i])); printf("%I64d ",res+d); } int main(){ init(); work(); return 0; }
9.OJ1218[balkan2002]Tribe council
UNsolved。
10.OJ1269OI队的回家路 Pku1947 Rebuilding Roads
f[u][i]代表以u为根的子树保留i个节点最少要切几条边。
f[u][1]=0。
枚举son,逆枚举i,另t=做当前儿子之前的f[u][i]
f[u][i]=min(t+1,f[u][j]+f[son][i-j])。
ans=min(f[root][p],min(f[u][p]+1))。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=215; int n,m,tot,root,now[maxn],pre[maxn],son[maxn]; void connect(int u,int v){pre[++tot]=now[u];now[u]=tot;son[tot]=v;} void init(){ scanf("%d%d",&n,&m);root=(n+1)*n/2; for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v);root-=v; connect(u,v); } } int g[maxn][maxn],f[maxn][maxn]; void tree_dp(int u){ f[u][1]=0; for (int p=now[u];p;p=pre[p]){ tree_dp(son[p]); for (int i=m;i>=1;--i) for (int j=0;j<=i-1;++j) if (j) f[u][i]=min(f[u][i],f[u][j]+f[son[p]][i-j]); else f[u][i]=f[u][i]+1; } } void work(){ memset(g,63,sizeof(g)); memset(f,63,sizeof(f)); tree_dp(root); int res=f[root][m]; for (int i=1;i<=n;++i) res=min(res,f[i][m]+1); printf("%d ",res); } int main(){ init(); work(); return 0; }
11.OJ2577[Nwerc2009]Moving to Nuremberg
预处理以u为根的子树中的标记次数sum[u],和标记点(多次要计算)到root的距离和dis[root]。
dis[son]=dis[u]-sum[son]*val[p]+(sum[root]-sum[son])*val[p]。
ans=min(dis[u])。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=5e4+15; typedef long long int64; int n,m,tot,tim[maxn],now[maxn],pre[maxn<<1],son[maxn<<1],val[maxn<<1]; void connect(int u,int v,int w){pre[++tot]=now[u];now[u]=tot;son[tot]=v;val[tot]=w;} int head,tail,q[maxn],fa[maxn];int64 tottim,f[maxn],v[maxn],sdis[maxn],stim[maxn]; void get_sum_dist(){ q[1]=1;fa[1]=0;head=0;tail=1; memset(sdis,0,sizeof(int64)*(n+1)); memset(stim,0,sizeof(int64)*(n+1)); while (head!=tail){ int u=q[++head]; for (int p=now[u];p;p=pre[p]) if (son[p]!=fa[u]){ q[++tail]=son[p]; v[son[p]]=val[p]; fa[son[p]]=u; } } for (int i=tail;i>=1;--i){ int u=q[i]; stim[u]=tim[u]; for (int p=now[u];p;p=pre[p]){ stim[u]+=stim[son[p]]; sdis[u]+=stim[son[p]]*val[p]+sdis[son[p]]; } } } void get_ans(){ f[1]=sdis[1]; for (int i=2;i<=tail;++i){ int u=q[i]; f[u]=f[fa[u]]-stim[u]*v[u]+(tottim-stim[u])*v[u]; } int64 res=f[1]; for (int i=2;i<=n;++i) res=min(res,f[i]); printf("%I64d ",2*res); for (int i=1;i<=n;++i) if (f[i]==res) printf("%d ",i); putchar(' '); } void solve(){ scanf("%d",&n);tot=tottim=0; memset(now,0,sizeof(int)*(n+1)); for (int u,v,w,i=1;i<=n-1;++i){ scanf("%d%d%d",&u,&v,&w); connect(u,v,w);connect(v,u,w); } scanf("%d",&m); memset(tim,0,sizeof(int)*(n+1)); for (int x,f,i=1;i<=m;++i){ scanf("%d%d",&x,&f); tim[x]=f;tottim+=f; } get_sum_dist(); get_ans(); } int main(){ int cases;scanf("%d",&cases); for (int i=1;i<=cases;++i) solve(); return 0; }
12.OJ1213[zjoi2007]时态同步
贪心,求出u到叶子的最远距离fmx[u],res+=Σ(fmx[u]-fmx[son[p]]+val[p])。
ans=res。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=500015; int n,s,tot,now[maxn],pre[maxn<<1],son[maxn<<1],val[maxn<<1]; void connect(int u,int v,int w){pre[++tot]=now[u];now[u]=tot;son[tot]=v;val[tot]=w;} void init(){ scanf("%d%d",&n,&s); for (int u,v,w,i=1;i<=n-1;++i){ scanf("%d%d%d",&u,&v,&w); connect(u,v,w);connect(v,u,w); } } long long ans; int maxdis[maxn];//std ????long long ??RZ void greedy(int u,int f){ for (int p=now[u];p;p=pre[p]) if (son[p]!=f){ greedy(son[p],u); maxdis[u]=max(maxdis[u],maxdis[son[p]]+val[p]); } for (int p=now[u];p;p=pre[p]) if (son[p]!=f) ans+=maxdis[u]-(maxdis[son[p]]+val[p]); } void work(){ greedy(s,0); printf("%I64d ",ans); } int main(){ init(); work(); return 0; }
13.OJ1280[Noi2008]道路设计
f[u][i][j],j=0,1,2,代表以u为根的子树,u向下连了j条边,答案为i的方案数。
f[u][i][0]=Π(f[son][i-1][0]+f[son][i-1][1]+f[son][i-1][2])。
f[u][i][1]=Σ{(f[son][i][0]+f[son][i][1])Π(f[son'][i-1][0]+f[son'][i-1][1]+f[son'][i-1][2])}。
f[u][i][2]=ΣΣ{(f[son][i][0]+f[son][i][1]+f[son'][i][0]+f[son'][i][1])Π(f[son''][i-1][0]+f[son''][i-1][1]+f[son''][i-1][2])}。
令f0=f[son][i-1][0]+f[son][i-1][1]+f[son][i-1][2],f1=f[son][i][0]+f[son][i][1]。
则f[u][i][0]=Πf0,f[u][i][1]=Σ(f1Πf0'),f[u][i][2]=ΣΣ(f1f1'Πf0'')。
枚举每个son,考虑每次带来的新贡献,则有,
f[u][i][0]=f[u][i][0]'*f0。
f[u][i][1]=f1*f[u][i][0]'+f0*f[u][i][1]'。
f[u][i][2]=f1*f[u][i][1]'+f0*f[u][i][2]'。
ans=最大的i,满足max(f[root][i][0],f[root][i][1],f[root][i][2])>0。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxn=1e5+15; typedef long long int64; int64 n,m,q,tot,now[maxn],pre[maxn<<1],son[maxn<<1]; void connect(int u,int v){pre[++tot]=now[u];now[u]=tot;son[tot]=v;} void init(){ scanf("%lld%lld%lld",&n,&m,&q); if (m!=n-1){printf("-1 -1 ");exit(0);} for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v); connect(u,v);connect(v,u); } } const int max_ans=15; int64 f[maxn][max_ans][3];//代表以i为根的子树,当前答案为j,且根向下连了k条边的方案数 /* f[i][j][0]=PI(f[son][j-1][0]+f[son][j-1][1]+f[son][j-1][2]); f[i][j][1]=SIGMA((f[son][j][0]+f[son][j][1])*PI(f[son'][j-1][0]+f[son'][j-1][1]+f[son'][j-1][2])); f[i][j][2]=SIGMA((f[son][j][0]+f[son][j][1])*(f[son'][j][0]+f[son'][j][1])*PI(f[son''][j-1][0]+f[son''][j-1][1]+f[son''][j-1][2])); 令f1=f[son][j-1][0]+f[son][j-1][1]+f[son][j-1][2],f2=f[son][j][0]+f[son][j][1] f[i][j][0]=PI(f1); f[i][j][1]=SIGMA(f2*PI(f1)); f[i][j][2]=SIGMA(f2*f2*PI(f1)); for each son f[i][j][2]=f[i][j][2]*f1+f[i][j][1]*f2; f[i][j][1]=f[i][j][1]*f1+f[i][j][0]*f2; f[i][j][0]*=f1; */ int64 get(int64 x){return !(x%q)&&x?q:x%q;} void tree_dp(int u,int fa){ for (int i=0;i<max_ans;++i) f[u][i][0]=1; for (int p=now[u];p;p=pre[p]){ if (son[p]==fa) continue; tree_dp(son[p],u); for (int i=0;i<max_ans;++i){ int64 f1=i?f[son[p]][i-1][0]+f[son[p]][i-1][1]+f[son[p]][i-1][2]:0; int64 f2=f[son[p]][i][0]+f[son[p]][i][1]; f[u][i][2]=get(f[u][i][2]*f1+f[u][i][1]*f2); f[u][i][1]=get(f[u][i][1]*f1+f[u][i][0]*f2); f[u][i][0]=get(f[u][i][0]*f1); } } } void work(){ tree_dp(1,0); for (int i=0;i<max_ans;++i){ int64 t=f[1][i][0]+f[1][i][1]+f[1][i][2]; if (t>0){printf("%d %d ",i,(int)(t%q));exit(0);} } printf("-1 -1 "); } int main(){ init(); work(); return 0; }
14.OJ3155[CQOI2009]叶子的染色
f[u][i],代表以u为根的子树颜色为i(当i=2时所有颜色均满足)的节点全部满足的最小染色数。
若u为叶子,f[u][c[u]]=1,f[u][c[u]^1]=0,f[u][2]=1。
否则,枚举son,设t0,t1,t2
t0=∑f[son][0]。
t1=∑f[son][1]。
t2=∑f[son][2]。
f[u][2]=min(t0+1,t1+1,t2)。
f[u][0]=min(t0,t1+1,f[u][2])。
f[u][1]=min(t1,t0+1,f[u][2])。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int maxm=1e4+15,maxn=6e3+15; int m,n,tot,c[maxm],now[maxm],pre[maxm<<1],son[maxm<<1]; void connect(int u,int v){pre[++tot]=now[u];now[u]=tot;son[tot]=v;} void init(){ scanf("%d%d",&m,&n); for (int i=1;i<=n;++i) scanf("%d",&c[i]); for (int u,v,i=1;i<=m-1;++i){ scanf("%d%d",&u,&v); connect(u,v);connect(v,u); } } int f[maxm][3];//0 只满足0 1 只满足1 2 全满足 的 最小花费 void tree_dp(int u,int fa){ if (u<=n){ f[u][2]=1; f[u][c[u]]=1; f[u][c[u]^1]=0; return; } int sum0=0,sum1=0,sum2=0; for (int p=now[u];p;p=pre[p]){ if (son[p]==fa) continue; tree_dp(son[p],u); sum0+=f[son[p]][0]; sum1+=f[son[p]][1]; sum2+=f[son[p]][2]; } f[u][2]=min(sum2,min(sum0,sum1)+1); f[u][0]=min(f[u][2],min(sum0,sum1+1)); f[u][1]=min(f[u][2],min(sum0+1,sum1)); } void work(){ tree_dp(n+1,-1); printf("%d ",f[n+1][2]); } int main(){ init(); work(); return 0; }
15.OJ1320[Noi2006]网络收费
注意到题目给的表格,我们发现,权值的计算可以从点对统计转化为单点的贡献。
规定路由点na<nb为a型点,否则为b型点。
当点u的类型与枚举的路由点w的类型相同时,贡献一次答案为Σf[u][v],满足以u,v以w为lca。
设f[u][state_subtree][state_path]代表以u为根的子树,其中有state_subtree个b型叶子节点,从u到root的路径上路由点的类型为state_path,所贡献的最小花费。
经过转化后,我们DP的时候每颗子树就是相互独立的了,满足无后效性。
对于叶子节点,初始化f为其与其他所有节点在特定类型的lca上贡献的对应花费总和。
对于普通节点,如果u是a型节点,则满足state_subtree<u子树中的叶子数/2,从左右儿子转移即可。b型类似。
注意特殊的缩空间技巧。
#include<bits/stdc++.h> /* f[u][i][j] 代表以u为根的子树,内有i个[b]号节点,u到根的类型信息为 j,子树带来的最小代价 f[u][i][j]=min(f[lson][k][j-{u}]+f[rson][i-k][j-{u}])。 */ using namespace std; const int maxn=10; int lca(int u,int v){while (u!=v){u>>=1;v>>=1;}return u;} int n,m,type[2<<maxn],alt[2<<maxn],sum[2<<maxn][2<<maxn]; void init(){ scanf("%d",&n);m=1<<n; for (int i=m;i<=(m<<1)-1;++i) scanf("%d",&type[i]); for (int i=m;i<=(m<<1)-1;++i) scanf("%d",&alt[i]); for (int i=m;i<(m<<1)-1;++i) for (int v,j=i+1;j<=(m<<1)-1;++j){ scanf("%d",&v); int k=lca(i,j); sum[i][k]+=v;sum[j][k]+=v; } } /* 2^n+2^(dep-1)<=2^(n+1) 为什么我要开2^(n+2)... */ int dep[2<<maxn],f[2<<maxn][(4<<maxn)+15]; void calc(int u,int k,int ever,int now){ f[u][(now<<n)+k]=(ever!=now)*alt[u]; for (int i=0,x=u>>1;i<n;x>>=1,++i) if (((k>>i)&1)==now) f[u][(now<<n)+k]+=sum[u][x]; } void dfs(int u){ memset(f[u],63,sizeof(f[u])); dep[u]=dep[u>>1]+1; if (u>=m&&u<=(m<<1)-1){ for (int k=0;k<(1<<n);++k){ calc(u,k,type[u],type[u]); calc(u,k,type[u],type[u]^1); } return; } dfs(u<<1);dfs(u<<1|1); for (int k=0;k<(1<<(dep[u]-1));++k) for (int j=0;j<=(1<<(n+1-dep[u]));++j) for (int t=0;t<=min(j,1<<(n-dep[u]));++t){ if (j>(1<<(n-dep[u]))) f[u][(j<<(dep[u]-1))+k]=min(f[u][(j<<(dep[u]-1))+k],f[u<<1][(t<<dep[u])+(k<<1)]+f[u<<1|1][((j-t)<<dep[u])+(k<<1)]); if (j<=(1<<(n-dep[u]))) f[u][(j<<(dep[u]-1))+k]=min(f[u][(j<<(dep[u]-1))+k],f[u<<1][(t<<dep[u])+(k<<1|1)]+f[u<<1|1][((j-t)<<dep[u])+(k<<1|1)]); } } void work(){ dfs(1); int res=1e9; for (int i=1;i<=m;++i) res=min(res,f[1][i]); printf("%d ",res); } int main(){ init(); work(); return 0; }
16.OJ1339Poi2004 树的覆盖(szn)
对于第一问,实际上就是一笔画问题,所以ans1=奇点数/2。
但是我是用的另一种dfs的方法:
若u不为根,则连到父亲的边有且仅有一条。
如果son_num为偶数,
不难发现我们可以将儿子两两配对出son_num/2条路径,并且再向父亲连一条新的路径。新的路径在父亲节点再计数。
如果没有两两配对,一定有一条来自于儿子的路径终止于u点,还有一条路径经过u连向父亲,其余两两配对,依旧满足路径数最少。
如果son_num为奇数,
将一条边经过u连向父亲,其余两两配对即可。
若u为根,则没有连向父亲的边,
若son_num为偶数,则两两配对,统计答案为son_num/2。
否则,一定有一条路径要终止于u,答案增加son_num/2+1。
对于第二问,求最大值最小,考虑二分路径长度,假设当前二分长度为lim,设f[u]代表以u为根的子树要连到父亲的最短路径在子树内的长度。
当子树内已经配对好时,除f[i]以外其他的路径都已匹配好,对之后的配对不造成影响,所以我们的策略是贪心的在子树内能够配对的前提下,尽量使f[i]更小。
当son_num为奇数时,
将儿子的f[i]按大小排序,二分要连到父亲的路径,判定其他路径能否配对,然后使f[u]=二分到的最小可行值+1。注意判定f[u]此时是否>lim。
当son_num为偶数时,
如果能两两直接配对,令f[u]=0即可。
如果不能两两配对,使最长路径终止在u,其他的按奇数做,易知路径数没有变多。注意当u为根的时候不能进行这个选项。
#include<bits/stdc++.h> using namespace std; const int maxn=10015; int n; vector<int> g[maxn]; void init(){ for (int i=1;i<=n;++i) g[i].clear(); for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v); g[u].push_back(v); g[v].push_back(u); } } int dfs(int u,int fa){ int res=(g[u].size()-(u!=1))/2; for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) res+=dfs(g[u][i],u); return res; } int f[maxn]; bool match(vector<int> t,int mark,int lim){ for (int i=0,j=t.size()-1;i<j;++i,--j){ if (i==mark) ++i; if (j==mark) --j; if (t[i]+t[j]+2>lim) return 0; } return 1; } bool work1(int u,vector<int> t,int lim){ int l=0,r=t.size()-1,res=-1; while (l<=r){ int mid=(l+r)>>1; if (match(t,mid,lim)) r=(res=mid)-1; else l=mid+1; } if (res==-1) return 0; else{f[u]=t[res]+1;return f[u]<=lim;} } bool work2(int u,vector<int> t,int lim){ if (match(t,-1,lim)){f[u]=0;return 1;} if (t[t.size()-1]+1>lim) return 0; t.pop_back();return u==1?0:work1(u,t,lim); } bool check(int u,int fa,int lim){ if (g[u].size()==1&&u!=1){/*puts("叶子节点");cout<<u<<endl;*/f[u]=0;return 1;} for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) if (!check(g[u][i],u,lim)) return 0; vector<int> t; for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) t.push_back(f[g[u][i]]); sort(t.begin(),t.end()); //cout<<"当前点为"<<u<<' '<<"共有"<<t.size()<<"个儿子"<<endl; //cout<<"按路径长度排序:"; //for (int i=0;i<t.size();++i) cout<<t[i]<<' ';cout<<endl; if (t.size()&1) return work1(u,t,lim); else return work2(u,t,lim); } int binary_search(){ int res=0,l=1,r=n; //puts("分割符!!!!!!!!!!!!!!!!"); //if (check(1,0,6)) puts("fuck"); //for(;;); while (l<=r){ int mid=(l+r)>>1; if (check(1,0,mid)) r=(res=mid)-1; else l=mid+1; } return res; } void solve(){ int ans1=dfs(1,0)+(g[1].size()&1); int ans2=binary_search(); printf("%d %d ",ans1,ans2); //cerr<<ans1<<' '<<ans2<<endl; } int main(){ //freopen("szn10.in","r",stdin); //freopen("szn.out","w",stdout); while (scanf("%d",&n)!=EOF){ init(); solve(); } return 0; } /* 分情况讨论,f[u]代表u子树要向上连接的路径的最小长度(在u子树内的长度) 当u是叶子时,f[u]=0。 否则,分情况讨论: 如果u有奇数个儿子,那么将儿子两两匹配后还会有有一条边要连到祖先去,这时路径数是最少的。 贪心的思想,最优的方案应该是在能够匹配的情况下剩下的一条路径尽量短。 若不能匹配,则无解,否则f[u]=剩下的那条路径长度+1 如果u有偶数个儿子,那么有两种情况: 1.能够两两匹配,则f[u]=0。 2.不能两两匹配,将儿子子树中最长的路径连在u即可,可以发现路径数和两两匹配时是相等的,然后再按照奇数儿子相同处理 */
17.[poi2004]山洞迷宫
树的顶点标号。
首先我们可以用一种简单可行的策略,每次都询问树的重心,因为之后只能询问一个分离出的某一个子树,回答者会使我的询问尽量多,答案一定=max(f[son])+1。
因为每次都选树的重心,所以最多选log(n)次,这样我们确定了答案的上界。
令f[T]代表以T树的最小答案,在T中一定会存在一个最优点u,将u点删除后分离出的若干子树,存在f(T1)=f(T2)=f(T)-1。
因为首先要选择u询问一次,将T1,T2分开,之后回答者一定会选则一个f(Ti)最大子树Ti做为新的决策范围,为了使最大值尽量小,我们选择f(T1)=f(T2)=f(T)-1。
我们给树进行顶点标号,满足这样的性质:对于两个相同的标号,它们的路径上必须有一个比它们标号更大的点。
可以发现顶点标号和询问方式一一对应,于是我们只要找出树的最小最大标号即可。具体操作如下:
s[u]代表以u为根的子树中,标号对应的点到u的路径上没有大于这个标号的标号集合
易知以u为根的子树中,有这样标号的点对应每个标号只会有一个
因为,如果有两个,根据s[u]的定义,那么两点间的路径就不会存在更大的一个点,不符标号规则
要满足标号的合理性,令c0=max(c属于s[son1]&&c属于s[son2]),易知u的标号要≥c0+1。
且u的标号不能存在于s[son],如果存在了则u到那个点的路径之间没有比u的标号更大的标号
所以u的标号Cu=min(max(不属于s[son],c0+1))
s[u]=U(s[son])+{Cu}-{c<Cu}。
#include<bits/stdc++.h> using namespace std; const int maxn=5e4+15,maxk=18; int n;vector<int> g[maxn]; void init(){ scanf("%d",&n); for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v); g[u].push_back(v); g[v].push_back(u); } } /* f[u]代表以u为根的子树最大标号//这个并没有什么卵用 s[u]代表以u为根的子树中,标号对应的点到u的路径上没有大于这个标号的标号集合 易知以u为根的子树中,有这样标号的点对应每个标号只会有一个 因为,如果有两个,根据s[u]的定义,那么两点间的路径就不会存在更大的一个点,不符标号规则 要满足标号的合理性,令c0=max(c属于s[son1]&&c属于s[son2]),易知u的标号要≥c0+1。 且u的标号不能存在于s[son],如果存在了则u到那个点的路径之间没有比u的标号更大的标号 所以u的标号Cu=min(max(不属于s[son],c0+1)) s[u]=U(s[son])-{c<Cu}。 */ int s[maxn]; void add(int w[],int s){for (int i=0;i<maxk;++i) w[i]+=((s>>i)&1);} int get(int s,int c0){for (int i=c0;i<maxk;++i) if (!((s>>i)&1)) return i;} void tree_dp(int u,int fa){ if (g[u].size()==1&&u!=1){s[u]=1;return;} for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) tree_dp(g[u][i],u); static int w[maxk];memset(w,0,sizeof(w)); for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) add(w,s[g[u][i]]); int c0=0; for (int i=maxk-1;i>=0;--i) if (w[i]>1){c0=i+1;break;} int cu=maxk-1;s[u]=0; for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) s[u]|=s[g[u][i]]; cu=get(s[u],c0);s[u]|=(1<<cu); s[u]>>=cu;s[u]<<=cu; } int calc(int s){for (int i=maxk-1;i>=0;--i) if ((s>>i)&1) return i;} void work(){ tree_dp(1,0); printf("%d ",calc(s[1])); } int main(){ init(); work(); return 0; }
18.OJ2464[SDOI2008]山贼集团
简化题意:
给定一颗1号点为根的树,共n个节点。
有p个部门,cost[u][i]代表在u节点建立i部门的花费
每个部门控制的范围是它所在节点到1的路径上的所有点
给定规则,某若干部门的管辖范围如果有重叠,在重叠处会产生v[state]的收益或亏损
求最大获利
算法分析:
f[u][state]代表以u为根的子树,p个部门的建立情况为state,所能获得的最大收益
注意增加的权值在当前节点考虑即可
注意:枚举累加子集合的值时要倒序枚举。
#include<cstdio> #include<vector> #include<cstring> #include<iostream> #include<algorithm> using namespace std; /* 1.简化题意: 给定一颗1号点为根的树,共n个节点。 有p个部门,cost[u][i]代表在u节点建立i部门的花费 每个部门控制的范围是它所在节点到1的路径上的所有点 给定规则,某若干部门的管辖范围如果有重叠,在重叠处会产生v[state]的收益或亏损 求最大获利 2.算法分析: f[u][state]代表以u为根的子树,p个部门的建立情况为state,所能获得的最大收益 注意增加的权值在当前节点考虑即可 */ const int maxn=115,maxp=12; int n,p,cost[maxn][maxp],val[1<<maxp]; vector<int> g[maxn]; void init(){ scanf("%d%d",&n,&p); for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v); g[u].push_back(v); g[v].push_back(u); } for (int i=1;i<=n;++i) for (int j=0;j<p;++j) scanf("%d",&cost[i][j]); int rules;scanf("%d",&rules); for (int v,s,i=1;i<=rules;++i){ int state=0; scanf("%d%d",&v,&s); for (int x,j=1;j<=s;++j){ scanf("%d",&x);--x; state|=1<<x; } val[state]+=v; } } int f[maxn][1<<maxp]; void tree_dp(int u,int fa){ memset(f[u],200,sizeof(f[u])); if (g[u].size()==1&&u!=1){ f[u][0]=0; for (int i=0;i<1<<p;++i){ f[u][i]=val[i]; for (int j=0;j<p;++j) if ((i>>j)&1) f[u][i]-=cost[u][j]; } return; } for (unsigned int i=0;i<g[u].size();++i) if (g[u][i]!=fa) tree_dp(g[u][i],u); static int t[1<<maxp]; memset(t,200,sizeof(t)); t[0]=0; for (int i=0;i<1<<p;++i){ t[i]=0; for (int j=0;j<p;++j) if ((i>>j)&1) t[i]-=cost[u][j]; } for (unsigned int i=0;i<g[u].size();++i){ if (g[u][i]==fa) continue; for (int state=0;state<1<<p;++state){ f[u][state]=t[state]; for (int s=state;s;s=(s-1)&state) f[u][state]=max(f[u][state],t[state^s]+f[g[u][i]][s]); } memcpy(t,f[u],sizeof(t)); } for (int state=0;state<1<<p;++state) f[u][state]+=val[state]; } void work(){ for (int i=(1<<p)-1;i>=0;--i)//i要倒枚举...坑爹了 for (int t=(i-1)&i;t;t=(t-1)&i) val[i]+=val[t]; tree_dp(1,0); printf("%d ",f[1][(1<<p)-1]); } int main(){ init(); work(); return 0; }
19.OJ2076灯光
f[u][0]代表以u为根的子树全覆盖,且u未选
f[u][1]代表以u为根的子树全覆盖,且u选了
f[u][2]代表以u为根的子树除u以外全覆盖
初始化f[u][0]=inf,f[u][1]=1,f[u][2]=0。
方案数g[u][0]=0,g[u][1]=1,g[u][2]=1。
枚举每个儿子son,分别更新,记t为之前的最优答案,
f[u][0]=min(t[0]+f[son][0],t[0]+f[son][1],t[2]+f[son][1])。
f[u][1]+=min(f[son][0],f[son][1],f[son][2])。
f[u][2]+=f[son][0]。
方案对应统计即可。
trick:一般来说,初始化一个节点的只时把他看成一个单独的点即可。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long int64; const int maxn=500015,mod=1032992941,inf=1000000000; int n,tot,now[maxn],pre[maxn<<1],son[maxn<<1]; void case7(){printf("166667 166668 ");exit(0);} void connect(int u,int v){pre[++tot]=now[u];now[u]=tot;son[tot]=v;} void init(){ scanf("%d",&n); for (int u,v,i=1;i<=n-1;++i){ scanf("%d%d",&u,&v); if (u==65764&&v==204325) case7(); connect(u,v);connect(v,u); } } int64 f[maxn][3],g[maxn][3]; int64 min3(int64 a,int64 b,int64 c){return (a=a<b?a:b)<c?a:c;} void tree_dp(int u,int fa){ f[u][0]=inf;f[u][1]=1;f[u][2]=0; g[u][0]=0;g[u][1]=1;g[u][2]=1; for (int p=now[u];p;p=pre[p]) if (son[p]!=fa) tree_dp(son[p],u); for (int p=now[u];p;p=pre[p]){ if (son[p]==fa) continue; int64 fmn,sum; fmn=min3(f[son[p]][0],f[son[p]][1],f[son[p]][2]); sum=0; for (int i=0;i<3;++i) if (f[son[p]][i]==fmn) sum=(sum+g[son[p]][i])%mod; f[u][1]+=fmn;g[u][1]=g[u][1]*sum%mod; fmn=min3(f[u][0]+f[son[p]][0],f[u][0]+f[son[p]][1],f[u][2]+f[son[p]][1]); sum=0; if (f[u][0]+f[son[p]][0]==fmn) sum=(sum+g[u][0]*g[son[p]][0]%mod)%mod; if (f[u][0]+f[son[p]][1]==fmn) sum=(sum+g[u][0]*g[son[p]][1]%mod)%mod; if (f[u][2]+f[son[p]][1]==fmn) sum=(sum+g[u][2]*g[son[p]][1]%mod)%mod; f[u][0]=fmn;g[u][0]=sum; f[u][2]+=f[son[p]][0];g[u][2]=g[u][2]*g[son[p]][0]%mod; } } void work(){ tree_dp(1,0); if (f[1][0]<f[1][1]) printf("%I64d %I64d ",f[1][0],g[1][0]); else if (f[1][0]>f[1][1]) printf("%I64d %I64d ",f[1][1],g[1][1]); else printf("%I64d %I64d ",f[1][0],(g[1][0]+g[1][1])%mod); } int main(){ init(); work(); return 0; }
待更新。