• 树形动态规划专题


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

    待更新。

  • 相关阅读:
    HTTP协议
    MySQL建立主-从服务器双机热备配置
    centOS7安装配置mysql5.7.21
    修改docker镜像的默认存储目录
    docker--学习笔记
    MySQL数据库操作
    zabbix注意事项和常用命令
    CnentOS6.5安装zabbix2.2
    如何查询一个进程下面的线程数(进程和线程区别)
    OSPF与ACL实例
  • 原文地址:https://www.cnblogs.com/iamCYY/p/4904572.html
Copyright © 2020-2023  润新知