树形动态规划专题

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=='\n');
    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\n",ans);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",f[1][q]);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",i);
            break;
        }
}
int main(){
    init();
    work();
    return 0;
}
my code

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;
}
my code

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\n",f[0][m][0]);
}
int main(){
    init();
    work();
    return 0;
}
my code

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.
my code

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\n",res);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",res+d);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",res);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",2*res);
    for (int i=1;i<=n;++i) if (f[i]==res) printf("%d ",i);
    putchar('\n');
}
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;
}
my code

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\n",ans);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n-1\n");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\n%d\n",i,(int)(t%q));exit(0);}
    }
    printf("-1\n-1\n");
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",f[n+1][2]);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",res);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",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即可,可以发现路径数和两两匹配时是相等的,然后再按照奇数儿子相同处理 
*/
my code

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\n",calc(s[1]));
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n",f[1][(1<<p)-1]);
}
int main(){
    init();
    work();
    return 0;
}
my code

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\n166668\n");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\n%I64d\n",f[1][0],g[1][0]);
    else if (f[1][0]>f[1][1]) printf("%I64d\n%I64d\n",f[1][1],g[1][1]);
    else printf("%I64d\n%I64d\n",f[1][0],(g[1][0]+g[1][1])%mod);
}
int main(){
    init();
    work();
    return 0;
}
my code

待更新。

posted @ 2015-10-23 15:02  iamCYY  阅读(282)  评论(0编辑  收藏  举报