zoukankan      html  css  js  c++  java
  • 树形DP

    树形dp

    summary

    树形dp的主要实现形式是dfs,在dfs中dp,主要的实现形式是(dp[i] [j] [0/1]),i 是以 i 为根的子树,j 是表示在以 i 为根的子树中选择 j 个子节点,0表示这个节点不选,1表示选择这个节点。有的时候 j 或0/1这一维可以压掉

    选择节点/边类

    [dp[i][0]=dp[j][1]\ dp[i][1]=max/min(dp[j][0],dp[j][1]) ]

    没有上司的舞会

    三种状态

    1、上司去,而剩下的这个上司的职员就不会去。
    2、不去,而那个职员去
    3、上司不去,而那个职员也不去。

    #include<iostream>
    #include<cstdio>
    #include<vector>
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    const int N =6010;
    int n,st,r[N],f[N][2];//0不 1 go 
    vector< int >g[N];
    bool vis[N];
    void dfs(int x,int fa){
    	f[x][0]=0;f[x][1]=r[x];
    	int siz=g[x].size();
    	for(int i=0;i<siz;i++){
    		int y=g[x][i];
    		if(y==fa)continue;
    		dfs(y,x);
    		f[x][0]+=max(f[y][0],f[y][1]);
    		f[x][1]+=f[y][0];
    	}
    }
    int main(){
    	n=read();
    	for(int i=1;i<=n;i++)r[i]=read();
    	for(int i=1,x,y;i<n;i++){
    		x=read();
    		y=read();
    		vis[x]=1;
    		g[y].push_back(x);
    	}
    	for(int i=1;i<=n;i++){
    		if(!vis[i]){
    			st=i,dfs(i,0);break;
    		}
    	} 
    	printf("%d
    ",max(f[st][0],f[st][1]));
    	return 0;
    }
    

    最大子树和

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    using namespace std;
    #define N 16050
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int f[N];
    int n,ans;
    vector< int >g[N];
    void dfs(int x,int fa){
    	for(int i=0;i<g[x].size();i++){
    		int y=g[x][i];
    		if(y==fa)continue;
    		dfs(y,x);
    		f[x]+=max(f[y],0);
    	}
    	ans=max(ans,f[x]);
    }
    int main(){
    	n=read();
    	for(int i=1;i<=n;i++)f[i]=read();
    	for(int i=1,x,y;i<n;i++){
    		x=read();y=read();
    		g[x].push_back(y);
    		g[y].push_back(x);
    	}
    	dfs(1,0);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    战略游戏

    #include<iostream>
    #include<cstdio>
    #include<vector>
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    const int N = 2000;
    int n,ans,f[N][2];
    vector< int >g[N]; 
    bool vis[N];
    void dfs(int x,int fa){
    	f[x][1]=1;f[x][0]=0;
    	for(int i=0;i<g[x].size();i++){
    		int y=g[x][i];
    		if(y==fa)continue;
    		dfs(y,x);
    		f[x][0]+=f[y][1];
    		f[x][1]+=min(f[y][1],f[y][0]);
    	}
    }
    int main(){
    	n=read();
    	for(int i=1,x,y,num;i<=n;i++){
    		x=read();num=read();
    		while(num--){
    			y=read();
    			g[x].push_back(y);
    			g[y].push_back(x);
    		}
    	} 
    	dfs(0,-1); 
    	printf("%d",min(f[0][0],f[0][1]));
    	return 0;
    }
    
    

    消防局的设立

    贪心
    树上的最小点覆盖
    按照反方向的bfs序(从叶子到根)来进行贪心.每检查一个结点,覆盖他的爷爷,然后对爷爷的儿孙打标记

    dfs序
    2 4 5 7 6 8 3
    bfs序:是一种层次遍历算法
    2 3 4 5 6 7 8
    用queue维护bfs序的同时维护一个栈——得到反向bfs序

    #include<iostream>
    #include<cstdio>
    #include<queue>
    #include<cstring>
    #include<vector>
    #include<stack>
    #define N 10005
    using namespace std;
    int n,m;
    int fa[N],cnt,ans=0,dad;
    bool vis[N];
    vector< int > g[N];
    queue <int> q; 
    stack <int> s;
    void dfs(int x,int dis){
    	if (dis>2) return;
        vis[x]=1;
        for(int i=0;i<g[x].size();i++)
            dfs(g[x][i],dis+1);
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=2,x;i<=n;i++){
    		scanf("%d",&fa[i]);
    		g[i].push_back(fa[i]);
    		g[fa[i]].push_back(i);
    	}
    	q.push(1);
        s.push(1);
        while (!q.empty()){
            int x=q.front();
            q.pop();
            for (int i=0;i<g[x].size();i++) {
                int y=g[x][i];
                if (y==fa[x]) continue;
                q.push(y);
                s.push(y); 
            }
        }
    	while(!s.empty()){
    		int x=s.top();s.pop();
    		if(!vis[x]){
    			++ans;
    			dfs(fa[fa[x]],0);
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    保安站岗

    (以下全部都是对于要覆盖任意一个以x为根的子树来说的)

    (其中我们设y节点为y的儿子,fa为x的父亲)

    1.x节点被自己覆盖,即选择x点来覆盖x点—— f[x] [0]

    2.x节点被儿子y覆盖,即选择y点来覆盖x点 —— f[x] [1]

    3.x节点被父亲fa覆盖,即选择fa点来覆盖x点 —— f[x] [2]

    [对于三种状态的转移方程\ f[x][0]=sum(f[y][0],f[y][1],f[y][2]);\ 选了自己,那么儿子肯定被覆盖,无脑取min\ f[x][1]=sum(f[y][0],f[y][1]);\ 儿子覆盖自己,那么儿子肯定不能从父亲被覆盖。然后注意!!!\ 如果一直取f[y][1]最后就到叶子也没人覆盖x,所以最后要特判,加上min(f[y][0]-f[y][1])\ f[x][2]=sum(f[y][0],f[y][1]);\ 父亲覆盖自己,显然覆盖不了自己的儿子,那么要么y被y的儿子覆盖,要么被y自己覆盖\ ]

    #include<iostream>
    #include<cstdio>
    #include<vector>
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    const int N = 3055;
    int n,m,w[N],f[N][3],add=0x3f3f3f3f;
    vector<int >g[N]; 
    inline void dfs(int x,int from){
    	int sz=g[x].size();
    	bool add=true;
    	int minn=0x3f3f3f3f;
    	for(int i=0;i<sz;++i){
    		int y=g[x][i];
    		if(y==from)continue;
    		dfs(y,x);
    		int t=min(f[y][1],f[y][0]);
    		f[x][0]+=min(t,f[y][2]);
    		f[x][2]+=t;
    		f[x][1]+=t;
    		minn=min(minn,f[y][0]-f[y][1]);
    		if(f[y][0]<=f[y][1])add=false;
    	}
    	f[x][0] += w[x];
    	if(add)	f[x][1] += minn; 
    }
    int main(){
    	n=read();
    	for(int i=1,x,y;i<=n;i++){
    		x=read();w[x]=read();m=read();
    		while(m--){
    			y=read();
    			g[x].push_back(y);
    			g[y].push_back(x);
    		}
    	}
    	dfs(1,0);
    	printf("%d
    ",min(f[1][0],f[1][1]));
    	return 0;
     } 
    

    联合权值

    树形dp

    距离差2的话,要么是祖父和儿子,要么是两个儿子之间

    维护联合权值最大值只需记录每个点儿子的最大值,次大值

    儿子之间联合权值之和等于权值和的平方减去权值的平方和( son中w总和,平方一下,再减去son[i]与son[i](自己配自己)这样不合法的情况即可 )

    儿子和祖父就直接 w相乘再乘2

    #include <vector>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    const int N=200005;
    const int md=10007;
    inline int read() {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return f*x;
    }
    typedef long long ll;
    inline void plu(ll &x,ll y){x+=y,x>=md&&(x-=md);}
    ll n,w[N];
    ll ans1,ans2;
    int hd[N],to[N<<1],nxt[N<<1],tot;
    inline void add(int x,int y) {
    	to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
    }
    void dfs(int x,int fa,int g) {
    	ll fir=0,sec=0,sum1=0,sum2=0;//1是和的平方,2平方的和
    	for(int i=hd[x];i;i=nxt[i]) {
    		int y=to[i];
    		if(y==fa) continue;
    		plu(sum1,w[y]);
    		plu(sum2,w[y]*w[y]%md);
    		if(w[y]>fir) 
    			sec=fir,fir=w[y];
    		else if(w[y]>sec)
    			sec=w[y];
    		dfs(y,x,fa);
    	}
    	ans1=max(ans1,max(fir*sec,w[g]*w[x]));
    	plu(ans2,((sum1*sum1%md-sum2+md)%md+(w[x]*w[g]*2)%md)%md);
    }
    int main() {
    	n=read();
    	int x,y;
    	for(int i=1;i<n;i++) {
    		x=read();y=read();
    		add(x,y),add(y,x);
    	}
    	for(int i=1;i<=n;i++) w[i]=read();
    	dfs(1,0,0);
    	printf("%lld %lld
    ",ans1,ans2);
    	return 0;
    }
    	
    

    CF633F The Chocolate Spree

    https://www.cnblogs.com/zwfymqz/p/9759346.html

    #include <vector>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    #define int long long
    inline int read() {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    const int N=100006;
    inline void Max(int &x,int y) {if(x<y)x=y;}
    int n,ans,w[N];
    vector<int> G[N];
    inline void add(int x,int y) {
    	G[x].push_back(y);G[y].push_back(x);
    }
    int f[N][2],g[N],d[N],h[N];
    //f[x][0]以x为根的子树选两条,1选1条
    void dfs(int x,int fa) {
    	f[x][0]=f[x][1]=g[x]=d[x]=w[x];
    	for(auto y:G[x]) {
    		if(y==fa) continue;
    		dfs(y,x);
    		Max(f[x][0],f[y][0]);
    		Max(f[x][0],f[y][1]+f[x][1]);
    		Max(f[x][0],d[y]+g[x]);
    		Max(f[x][0],d[x]+g[y]);
    
    		Max(f[x][1],f[y][1]);
    		Max(f[x][1],d[y]+d[x]);
    
    		Max(g[x],g[y]+w[x]);
    		Max(g[x],d[x]+f[y][1]);
    		Max(g[x],d[y]+w[x]+h[x]);
    		Max(h[x],f[y][1]);
    		Max(d[x],w[x]+d[y]);
    	}
    }
    signed main() {
    	n=read();
    	for(int i=1;i<=n;i++) w[i]=read();
    	for(int i=1;i<n;i++) 
    		add(read(),read());
    	dfs(1,0);
    	printf("%lld
    ",f[1][0]);
    	return 0;
    }
    

    树形背包类

    [dp[v][k]=dp[u][k]+val\ dp[u][k]=max(dp[u][k],dp[v][k−1]) ]

    选课

    类似于有依赖的背包

    设f(i)(j)表示在以 i 为根的子树中,选择 j 个点并且一定选择 i 的最大价值。

    这里以0节点为开始的根

    把选课前需要学的课与其连边(若没有要学的和0连边)

    很明显,f(i)(1)就是其自己学分

    转移类似01背包(倒序)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    
    #define N 1005
    using namespace std;
    int n,m,f[N][N];
    vector<int>g[N];
    int maxx(int x,int y){
        return x>y?x:y;
    }
    void dfs(int x){
        int siz=g[x].size();
        for(int i=0;i<siz;i++){
            int y=g[x][i];dfs(y);
            for(int j=m+1;j>=1;j--)
                for(int k=0;k<j;k++)
                    f[x][j]=maxx(f[x][j],f[y][k]+f[x][j-k]);
        }
    }
    int main(){
        scanf("%d%d",&n,&m) ;
        for(int i=1,x;i<=n;i++){
            scanf("%d",&x);
            scanf("%d",&f[i][1]);
            g[x].push_back(i);
        }
        dfs(0);
        printf("%d
    ",f[0][m+1]);
        return 0;
    }
    

    高明的另解

    之后模拟赛的t4用另解的思想可过,第一个会TLE

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    typedef long long ll;
    const int N=1005;
    inline int read() {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return f*x;
    }
    int n,m;
    int f[N][N];
    int to[N<<1],nxt[N<<1],hd[N],tot,fa[N];
    inline void add(int x,int y) {
        to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
    }
    int s[N],rt;
    int dfn_cnt,rev[N],siz[N];//反dfn序
    int dfs(int x) {
        int size=1;
        for(int i=hd[x];i;i=nxt[i]) 
            size+=dfs(to[i]);
        rev[++dfn_cnt]=x;siz[dfn_cnt]=size;
        return size;
    }
    int main() {
        n=read();m=read();
        for(int i=1;i<=n;i++) {
            int k=read();s[i]=read();
            if(k) add(k,i);
            else add(rt,i);
        }
        for(int i=hd[rt];i;i=nxt[i])
            dfs(to[i]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                f[i][j]=max(f[i-siz[i]][j],f[i-1][j-1]+s[rev[i]]);
        printf("%d
    ",f[n][m]);
        return 0;
    }
    

    二叉苹果树

    一棵二叉树,边上有苹果,给定需要保留的边数量,求出最多能留住多少苹果。

    维护子树大小siz[ ]

    为什么是f[u] [i-j-1]而不是f[u] [i-j]

    为前文提到了,保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从u的子节点v的子树上留边的话,也要留下u,v之间的连边

    那么取值范围k为什么要小于等于i-1而不是i呢?

    同上,因为要保留u,v连边

    对了,别忘了i,j要倒序枚举因为这是01背包

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<utility>
    using namespace std;
    #define N 105
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int f[N][N],siz[N];
    int n,m;
    vector< pair<int,int> >g[N];
    void dfs(int x,int fa){
    	for(int i=0;i<g[x].size();i++){
    		int y=g[x][i].first;
    		if(y==fa)continue;
    		dfs(y,x);
    		siz[x]+=siz[y]+1;
    		for(int j=min(siz[x],m);j>0;j--)
    			for(int k=min(j-1,siz[y]);k>=0;k--)
    				f[x][j]=max(f[x][j],f[x][j-k-1]+f[y][k]+g[x][i].second);
    		}
    	
    }
    int main(){
    	n=read();m=read();
    	for(int i=1,x,y,z;i<n;i++){
    		x=read();y=read();z=read();
    		g[x].push_back(make_pair(y,z));
    		g[y].push_back(make_pair(x,z));
    	}
    	dfs(1,-1);
    	printf("%d
    ",f[1][m]);
    	return 0;
    }
    

    时态同步

    模拟:mx【x】 以x为根的到终点的路径长,然后ans统计做差就好

    #include<iostream>
    #include<cstdio>
    #include<vector>
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    typedef long long LL;
    const int N = 1000005;
    LL ans,mx[N];
    int n,s;
    vector< pair<int,LL> >g[N]; 
    bool vis[N];
    void dfs(int x,int fa){
    	for(int i=0;i<g[x].size();i++){
    		int y=g[x][i].first;
    		if(y!=fa){
    			dfs(y,x);
    			mx[x]=max(mx[x],mx[y]+g[x][i].second);
    		}
    	}
    	for(int i=0;i<g[x].size();i++)
    		if(g[x][i].first!=fa)ans+=mx[x]-(g[x][i].second+mx[g[x][i].first]);
    }
    int main(){
    	n=read();s=read(); 
    	for(int i=1,x,y,z;i<n;i++){
    		x=read();y=read();z=read();
    		g[x].push_back(make_pair(y,z));
    		g[y].push_back(make_pair(x,z));
    	} 
    	dfs(s,0); 
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    

    [HAOI2010]软件安装

    #include <iostream>
    #include <cstdio>
    #include <cctype>
    #include <vector>
    using namespace std;
    const int N=105;
    const int M=505;
    inline int read() {
    	int x=0;int f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int n,m;
    int st[N],tp,dfn[N],low[N],dfn_cnt,col[N],co_cnt;
    bool vis[N];
    int sw[N],w[N],sv[N],v[N],in[N],d[N];
    vector<int> G[N];
    void tarjan(int x) {
    	vis[x]=1;
    	st[++tp]=x;
    	dfn[x]=low[x]=++dfn_cnt;
    	for(auto y:G[x]) {
    		if(!dfn[y]) {
    			tarjan(y);
    			low[x]=min(low[x],low[y]);
    		} else if(vis[y])
    			low[x]=min(low[x],dfn[y]);
    	}
    	if(dfn[x]==low[x]) {
    		co_cnt++;
    		while(st[tp+1]!=x) {
    			col[st[tp]]=co_cnt;
    			sw[co_cnt]+=w[st[tp]];
    			sv[co_cnt]+=v[st[tp]];
    			vis[st[tp--]]=0;
    		}
    	}
    }
    int hd[N],to[N*N],nxt[N*N],tot;
    inline void add(int x,int y) {
    	to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
    }
    int f[N][M];
    void dfs(int x) {
    	for(int i=sw[x];i<=m;i++)
    		f[x][i]=sv[x];
    	for(int i=hd[x];i;i=nxt[i]) {
    		dfs(to[i]);
    		for(int j=m;j>=sw[x];j--)
    			for(int k=0;k<=j-sw[x];k++)
    				f[x][j]=max(f[x][j],f[x][j-k]+f[to[i]][k]);
    	}
    }
    int main()
    {
    	n=read(),m=read();
    	for(int i=1;i<=n;i++)
    		w[i]=read();
    	for(int i=1;i<=n;i++)
    		v[i]=read();
    	for(int i=1;i<=n;i++) {
    		d[i]=read();
    		if(d[i]) G[d[i]].push_back(i);
    	}
    	for(int i=1;i<=n;i++)
    		if(!dfn[i])
    			tarjan(i);
    
    	for(int i=1;i<=n;i++)
    		if(d[i]&&col[d[i]]!=col[i])
    			add(col[d[i]],col[i]),in[col[i]]++;
    	for(int i=1;i<=co_cnt;i++)
    		if(!in[i]) add(n+1,i);
    	dfs(n+1);
    	printf("%d
    ",f[n+1][m]);
    	return 0;
    }
    

    题:

    https://www.luogu.com.cn/problem/P1272

    https://www.luogu.com.cn/blog/nofind/p3761-tjoi2017-cheng-shi-shu-xing-dp

    https://www.luogu.com.cn/problem/P3647

    https://www.luogu.com.cn/problem/P1453

    https://www.luogu.com.cn/problem/P1273

  • 相关阅读:
    牛客寒假6-I 导航系统
    牛客寒假6-C汉诺塔
    P1282 多米诺骨牌【dp】
    VisualStudio中C++程序输出一闪而过的解决方案
    【网络流24题】【LOJ6224】深海机器人问题(最大费用最大流)
    【网络流24题】【LOJ6013】负载平衡(环形纸牌均分,最小费最大流)
    【网络流24题】【LOJ6010】数字梯形(费用流)
    【网络流24题】【LOJ6000】搭配飞行员(二分图最大匹配,最大流Dinic)
    路由器相关
    PC启动原理
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13507937.html
Copyright © 2011-2022 走看看