树形DP

树形DP

没有上司的舞会

Ural大学有N名职员,编号为1~N。

他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。

每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。

现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。

在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有
参会职员的快乐指数总和最大,求这个最大值。

输入格式
第一行一个整数N。

接下来N行,第 i 行表示 i 号职员的快乐指数Hi。

接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。

输出格式
输出最大的快乐指数。

数据范围
1≤N≤6000,
128≤Hi≤127
输入样例:

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5

输出样例:

输出样例:

5

每个人只有两种状态,则设dp[0][i]dp[0][i]为第ii个人不来,他的下属所能获得的最大快乐值;dp[1][i]dp[1][i]为第ii个人来,他的下属所能获得的最大快乐值。

所以容易推出状态转移方程:

\(dp[0][i]=∑~u=sons~max(dp[1][u],dp[0][u])\)当前节点不选,那么子节点随意

\(dp[1][i]=∑~u=sons~dp[0][u]+happy[i]\)当前节点选,子节点不能选

#include<bits/stdc++.h> 
using namespace std;
#define INF 0x3ffffff
#define maxn 6005

int n,root,h[maxn],f[2][maxn];
deque<int>s[maxn];
bool vis[maxn];

void dfs(int x)
{
	f[0][x]=0;
	f[1][x]=h[x];
	for(int i=0;i<s[x].size();++i)
	{
		int v=s[x][i];
		dfs(v);
		f[0][x]+=max(f[1][v],f[0][v]);
		f[1][x]+=f[0][v];
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&h[i]),f[0][i]=f[1][i]=-INF;
	for(int i=1,a,b;i<n;++i)
	{
		scanf("%d %d",&a,&b);
		s[b].push_back(a);
		vis[a]=1;
	}
	
	for(int i=1;i<=n;++i)
	if(!vis[i])
	{
		root=i;
		break;
	}
	
	dfs(root);
	
	printf("%d",max(f[0][root],f[1][root]));
}

选课

学校实行学分制。

每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。

学校开设了 N 门的选修课程,每个学生可选课程的数量 M 是给定的。

学生选修了这 M 门课并考核通过就能获得相应的学分。

在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程的基础上才能选修。

例如《Windows程序设计》必须在选修了《Windows操作基础》之后才能选修。

我们称《Windows操作基础》是《Windows程序设计》的先修课。

每门课的直接先修课最多只有一门。

两门课可能存在相同的先修课。

你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修条件。

假定课程之间不存在时间上的冲突。

输入格式
输入文件的第一行包括两个整数N、M(中间用一个空格隔开)其中1≤N≤300,1≤M≤N。

接下来N行每行代表一门课,课号依次为1,2,…,N。

每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。

学分是不超过10的正整数。

输出格式
输出一个整数,表示学分总数。

输入样例:

7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2

输出样例:

13
#include<bits/stdc++.h>
using namespace std;
#define maxn 305
int n,m,f[maxn][maxn],a[maxn];
deque<int>q[maxn];

void dfs(int x)
{
	for(int i=1;i<=m;++i) f[x][i]=a[x];
	
	for(int i=0;i<q[x].size();++i)
	{
		int y=q[x][i];
		
		dfs(y);
		
		for(int j=m;j>=1;--j)
		for(int k=1;k<j;++k)
		f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
	}
}


void dfss(int x)
{
	for(int i=0;i<q[x].size();++i)
	{
		int y=q[x][i];
		
		dfs(y);
		
		for(int j=m;j>=0;--j)
		for(int k=1;k<=j;++k)
		f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1,x;i<=n;i++)
	{
		scanf("%d %d",&x,&a[i]);
		q[x].push_back(i);
	}
	
	dfss(0);
	
	printf("%d",f[0][m]);
}

积蓄程度(二次扫描和换根法)

有一个树形的水系,由 N-1 条河道和 N 个交叉点组成。

我们可以把交叉点看作树中的节点,编号为 1~N,河道则看作树中的无向边。

每条河道都有一个容量,连接 x 与 y 的河道的容量记为 c(x,y)。

河道中单位时间流过的水量不能超过河道的容量。

有一个节点是整个水系的发源地,可以源源不断地流出水,我们称之为源点。

除了源点之外,树中所有度数为 1 的节点都是入海口,
可以吸收无限多的水,我们称之为汇点。

也就是说,水系中的水从源点出发,沿着每条河道,最终流向各个汇点。

在整个水系稳定时,每条河道中的水都以单位时间固定的水量流向固定的方向。

除源点和汇点之外,其余各点不贮存水,也就是流入该点的河道水量之和等于从该点流出的河道水量之和。

整个水系的流量就定义为源点单位时间发出的水量。在流量不超过河道容量的前提下,求哪个点作为源点时,整个水系的流量最大,输出这个最大值。

输入格式
输入第一行包含整数T,表示共有T组测试数据。

每组测试数据,第一行包含整数N。

接下来N-1行,每行包含三个整数x,y,z,表示x,y之
间存在河道,且河道容量为z。

节点编号从1开始。

输出格式
每组数据输出一个结果,每个结果占一行。

数据保证结果不超过231-1。

数据范围
N≤231-1
输入样例:

1
5
1 2 11
1 4 13
3 4 5
4 5 10

输出样例:

输出样例:

26

D[x]表示以x为根的子树中把x作为源点,从x流向子树的流量

D[x]=∑y∈son(x)【if(y的度数>1)】,c(x,y)【if(y的度数=1)】

1588079150009

#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
int t,n,ans,tot;
int d[maxn],f[maxn],tail[maxn],dep[maxn];
bool vis[maxn];
struct op
{
	int v,w,pre;
}eg[maxn*2];

void add(int u,int v,int w)
{
	eg[++tot].v=v;
	eg[tot].w=w;
	eg[tot].pre=tail[u];
	tail[u]=tot;
	dep[u]++;
}

void dp(int u)
{
	vis[u]=1;
	
	for(int i=tail[u];i;i=eg[i].pre)
	{
		int v=eg[i].v,w=eg[i].w;
		if(vis[v]) continue;
		
		if(dep[v]==1) d[u]+=w;
		else {dp(v);d[u]+=min(d[v],w);}
	}
}

void dfs(int u)
{
	vis[u]=1;
	
	for(int i=tail[u];i;i=eg[i].pre)
	{
		int v=eg[i].v,w=eg[i].w;
		if(vis[v]) continue;
		
		if(dep[u]==1) f[v]=d[v]+w;
		else f[v]=d[v]+min(w,f[u]-min(w,d[v]));
		
		dfs(v);
	}
	
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		
		memset(vis,0,sizeof(vis));
		memset(d,0,sizeof(d));
		memset(tail,0,sizeof(tail));
		memset(dep,0,sizeof(dep));
		memset(f,0,sizeof(f));
		
		
		tot=0;
		
		for(int i=1,a,b,c;i<n;++i)
		{
			scanf("%d %d %d",&a,&b,&c);
			add(a,b,c);
			add(b,a,c);
		}
		
		dp(1);
		memset(vis,0,sizeof(vis));
		f[1]=d[1];
		dfs(1);
		
		ans=0;
		for(int i=1;i<=n;++i) ans=max(ans,f[i]);
		printf("%d\n",ans);
	}
}

战略游戏

鲍勃喜欢玩电脑游戏,特别是战略游戏,但有时他找不到解决问题的方法,这让他很伤心。

现在他有以下问题。

他必须保护一座中世纪城市,这条城市的道路构成了一棵树。

每个节点上的士兵可以观察到所有和这个点相连的边。

他必须在节点上放置最少数量的士兵,以便他们可以观察到所有的边。

你能帮助他吗?

例如,下面的树:

1463_1.jpg.gif

只需要放置1名士兵(在节点1处),就可观察到所有的边。

输入格式

输入包含多组测试数据,每组测试数据用以描述一棵树。

对于每组测试数据,第一行包含整数N,表示树的节点数目。

接下来N行,每行按如下方法描述一个节点。

节点编号:(子节点数目) 子节点 子节点 …

节点编号从0到N-1,每个节点的子节点数量均不超过10,每个边在输入数据中只出现一次。

输出格式

对于每组测试数据,输出一个占据一行的结果,表示最少需要的士兵数。

数据范围

0<N≤15000<N≤1500

输入样例:

4
0:(1) 1
1:(2) 2 3
2:(0)
3:(0)
5
3:(3) 1 4 2
1:(1) 0
2:(0)
0:(0)
4:(0)

输出样例:

1
2

状态可以表示为\(f[i][j]\)
\(f[i][0]\)表示不选当前结点
\(f[i][1]\)表示选择当前结点

因为每条边都要被观察到
所以父结点不选子节点一定要选
父结点选子节点可以选或不选
表示为转移方程就是
\(f[u][0] += f[v][1]\)
\(f[u][1] += min(f[v][1],f[v][0])\)

#include <iostream>
#include <vector>
#include <cstring>

using namespace std;

const int N = 1510,M = N * 2,inf = 0x3f3f3f3f;

int n,f[N][2];
int head[N],nxt[M],ver[M],idx;
bool st[N];

void add(int u,int v)
{
    nxt[++ idx] = head[u];
    ver[idx] = v;
    head[u] = idx;
}

void dfs(int u)
{
    f[u][0] = 0;
    f[u][1] = 1;
    for(int i = head[u]; i ;i = nxt[i])
    {
        int v = ver[i];
        dfs(v);
        f[u][1] += min(f[v][0],f[v][1]);
        f[u][0] += f[v][1];
    }
}

贿赂FIPA

FIPA(国际国际计划协会联合会)近期将进行投票,以确定下一届IPWC(国际规划世界杯)的主办方。钻石大陆的代表本内特希望通过以赠送钻石买通国家的方式,获得更多的投票。当然,他并不需要买通所有的国家,因为小国家会跟随着他们附庸的大国进行投票。换句话说,只要买通了一个大国,就等于获得了它和它统治下所有小国的投票。

例如,C在B的统治下,B在A的统治下,那么买通A就等于获得了三国的投票。

请注意,一个国家最多附庸于一个国家的统治下,附庸关系也不会构成环。

请你编写一个程序,帮助本内特求出在至少获得m个国家支持的情况下的最少花费是多少。

输入格式
输入包含多组测试数据。

第一行包含两个整数n和m,其中n表示参与投票的
国家的总数,m表示获得的票数。

接下来n行,每行包含一个国家的信息,形式如下:

CountryName DiamondCount DCName DCName …

其中CountryName是一个长度不超过100的字符串,表示这个国家的字,DiamondCount是一个整数,表示买通该国家需要的钻石DCName是一个字符串,表示直接附庸于该国家的一个国家的名字。

一个国家可能没有任何附庸国家。

当读入一行为#时,表示输入终止。

输出格式
每组数据输出一个结果,每个结果占一行。

数据范围
1≤n≤200,
0≤m≤n
输入样例:

3 2
Aland 10
Boland 20 Aland
Coland 15
#

输出样例:

20

输出样例:
20

LA3797.jpg

#include<bits/stdc++.h>
using namespace std;
#define maxn 205

bool bz[maxn];
int tot,n,m,val[maxn],f[maxn][maxn],siz[maxn];
deque<int>q[maxn];
map<string,int>co;

void dp(int u)
{
	f[u][0]=0;
	siz[u]=1;
	
	for(int i=0;i<q[u].size();++i)
	{
		int v=q[u][i];
		
		dp(v);
		siz[u]+=siz[v];
		
		for(int j=min(m,siz[u]);j>=0;--j)
		for(int k=j;k>=0;--k)
			f[u][j]=min(f[u][j],f[u][k]+f[v][j-k]);
	}
	
	if(u)
		for(int j=0;j<=min(m,siz[u]);++j)
		f[u][j]=min(f[u][j],val[u]);
}


int main()
{
	string s;
	stringstream ss;
	while(getline(cin,s))
	{
		if(s[0]=='#') return 0;
		
		co.clear();
		memset(bz,0,sizeof(bz));
		memset(f,0x3f,sizeof(f));
		for(int i=0;i<=n;++i) q[i].clear();
		
		
		ss.clear();
		ss<<s;
		ss>>n>>m;
		
		tot=0;
		for(int i=1;i<=n;++i)
		{
			getline(cin,s);
			ss.clear();
			ss<<s;
			ss>>s;
			
			int u=co[s]?co[s]:(co[s]=++tot);
			ss>>val[u];
			
			while(ss>>s)
			{
				int v=co[s]?co[s]:(co[s]=++tot);
				q[u].push_back(v);
				bz[v]=1;
			}
		}

		for(int i=1;i<=tot;++i)
		if(!bz[i]) q[0].push_back(i);
	
		dp(0);
		
		printf("%d\n",f[0][m]);
	}
}

P3047 Nearby Cows

一棵树,点带权,相连两点距离为1,求每个点距离不超过k的范围内的点权和。

1<=n<=1e5,1<=k<=20,0<=c<=1000.

更新时:\(V[u][j]+=V[fa][j-1]-V[u][j-2]\)\(j\)用倒序,否则\(V[u][j]\)值改变会影响\(V[u][j+2]\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5,K=22;
int n,k,te,tail[N],val[N][K];
struct e_
{
	int v,pre;
}e[N<<1];

inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}

void dfs1(int u,int fa)
{
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(v==fa) continue;
		
		dfs1(v,u);
		
		for(int j=1;j<=k;++j) val[u][j]+=val[v][j-1];
	}
}

void dfs2(int u,int fa)
{
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(v==fa) continue;
		
		for(int j=k;j>=2;--j) val[v][j]+=val[u][j-1]-val[v][j-2];
		val[v][1]+=val[u][0];
		dfs2(v,u);
	}
} 
int sum(int x)
{
	int res=0;
	for(int i=0;i<=k;++i) res+=val[x][i];
	return res;
}
int main()
{
	scanf("%d %d",&n,&k);
	for(int i=1,u,v;i<n;++i) scanf("%d %d",&u,&v),add(u,v),add(v,u);
	for(int i=1;i<=n;++i) scanf("%d",&val[i][0]);
	
	dfs1(1,0);//每个点向下第k层的距离
	dfs2(1,0);//ANS 
	
	for(int i=1;i<=n;++i) printf("%d\n",sum(i));
}

基环树DP

P6417 Mafija

题目描述

有 n个人,其中有一些人是平民,有一些人是坏蛋。

现在,平民们想揪出所有的坏蛋,于是 n 个人都指认了一个人是坏蛋。

如果一个人是平民,他会随便乱指认,否则,他会指认一个平民。

求出最多的坏蛋个数。

输入格式

第一行一个整数 n。

接下来 n 行,每行一个整数 k,第 i 行表示第 i个人指认了第 k个人。

输出格式

仅一行一个整数,表示最多的坏蛋个数。

输入输出样例

输入 #1复制

3
2
1
1

输出 #1复制

2

输入 #2复制

3
2
3
1

输出 #2复制

1

输入 #3复制

7
3
3
4
5
6
4
4

输出 #3复制

4

说明/提示

样例解释

样例输入输出 1 解释

坏蛋可以是第 2个人和第 3个人。

样例输入输出 2 解释

坏蛋可能是所有人,但是只能是其中的一个人,因为再多一个坏蛋的话会有坏蛋指控坏蛋的情况发生。

数据范围与限制

  • 对于 40 分的数据,保证 n≤15。
  • 对于 80 分的数据,保证 n≤2×104
  • 对于 100% 的数据,保证 1≤n≤5×105,1≤kn
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5e5+5;
int n,f[N];
ll ans,dp[N][2],dp2[N][2];
bool vis[N];
vector<int>vv[N],H,Q;

int find(int x)
{
	return f[x]!=x?f[x]=find(f[x]):x;
}
bool dfs(int u)
{
	vis[u]=1;
	for(int i=0;i<vv[u].size();++i)
	{
		int v=vv[u][i];
		if(vis[v]||dfs(v)){H.push_back(u);return 1;}
	}
	return 0;
}

void dfs_(int u)
{
	dp[u][1]=1;
	vis[u]=1;
	for(int i=0;i<vv[u].size();++i)
	{
		int v=vv[u][i];
		if(vis[v]) continue;
		dfs_(v);
		dp[u][1]+=dp[v][0];
		dp[u][0]+=max(dp[v][0],dp[v][1]);	
	}
}
int S;
ll calc(int x)
{
	dp2[2][0]=dp[H[x]][1]+dp[H[x+1]][0];
	dp2[2][1]=-1;
	
	for(int j=3;j<=S;++j)
	dp2[j][0]=max(dp2[j-1][0],dp2[j-1][1])+dp[H[x+j-1]][0],
	dp2[j][1]=dp2[j-1][0]+dp[H[x+j-1]][1];

	return dp2[S][0];
}
ll work()
{
	memset(vis,0,sizeof(vis));
	
	S=H.size()-1;
	for(int i=1;i<=S;++i) vis[H[i]]=1;
	for(int i=1;i<=S;++i) dfs_(H[i]),H.push_back(H[i]);
	
	ll V=0;
	for(int i=1;i<=S;++i) V=max(V,calc(i));
	
	return V;
}

int main()
{
	freopen("mafija.in","r",stdin);
	freopen("mafija.out","w",stdout);
	
	scanf("%d",&n);
	for(int i=1,u;i<=n;++i) f[i]=i;
	for(int i=1,u;i<=n;++i) 
	{
		scanf("%d",&u);
		vv[u].push_back(i);
		
		int x=find(i),y=find(u);
		
		if(x==y) Q.push_back(i);
		else f[x]=y;
	}
	
	for(int i=0;i<Q.size();++i)
	{
		H.clear();
		H.push_back(Q[i]);
		dfs(Q[i]);
		ans+=work(); 
	}
	printf("%lld",ans);
}
posted @ 2020-10-23 19:28  林生。  阅读(115)  评论(0)    收藏  举报