树形DP之王 balabalabala

题目:给你一棵树,要你求最大匹配和最大匹配的方案数。最大匹配就是选尽量多的边,使这些边的端点不重合。方案数要对1e9+7取模。n<=100000

这道题大概是我见过的树形DP中最难的,我称之为树P之王,首先,对于最大匹配,我们可以将边上放到点,即dp[i][0]表示不选i下面所连的边,dp[i][1]表示选i下面的边,那么我们可以知道dp[i][0]=西格玛max(dp[son[i]][1],dp[son[i]][0]),但是dp[i][1]就不好弄了,因为我们要找一个dp[son[i]][0]与i这个点连接,剩下的点所连的边就可选可不选了,那我们怎么保证最优呢?我们可以先将所有边变成可选可不选,然后再枚举每一条边,把它变成不选,得到一个匹配数,最后取一个max作为dp[i][1]的值。这样第1问我们就解决了。

而第二问才是最难的地方,我们容易想到用sum[i][0]和sum[i][1]分别表示i这个点选与不选时取最优值的方案数,sum[i][0]也比较好求,就是讨论一下dp[son[i]][0]和dp[son[i]][1]的关系,而sum[i][1]就比较复杂,因为dp[i][1]的转移是比dp[i][0]的转移复杂的,对于sum[i][1]的求法,有两种不同的做法,一种是我的,一种是APT的,先说我的做法,我是用了vector存了边,然后我们可以用两个辅助数组fz1[i]和fz2[i],其中fz1[i]表示方案数的前缀积,fz2[i]表示方案数的后缀积,那么对于一个可以被i连的son[i],设它在vector的位置是j,那么son[i]对答案的贡献为fz1[j-1]*fz2[j+1]*sum[son[i]][0],我们只需要统计每个可行的son[i]的和即可。再说说APT的做法,没有用vector,根据乘法原理和加法原理,对于每个可行的son[i],我们把先乘它应该乘的,再加上dp[son[i]][0],这样的话根据乘法原理算出来的答案是和用后缀积是一样的,但是这种做法速度快,但也不易理解,估计这个题做出来以后树形DP的题就不怕了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define maxn 100005
const long long mod=1000000007;
int T,opt,que[maxn],father[maxn],list[maxn];
int n,dp[maxn][2];
long long sum[maxn][2],fz1[maxn],fz2[maxn];
bool vis[maxn],flag[maxn],leaf[maxn];
vector<int> g[maxn]; 

void connect(int x,int y)
{
	g[x].push_back(y);
}

void bfs(void)
{
	memset(vis,0,sizeof vis);
	int h=1,t=1;
	que[1]=1;vis[1]=1;
	while (h<=t) 
	{
		int u=que[h];h++;
		leaf[u]=1;
		int siz=g[u].size();
		for (int j=0;j<siz;j++) 
		{
			int v=g[u][j];
			if (vis[v]) continue;
			leaf[u]=0;
			father[v]=u;
			que[++t]=v;
			vis[v]=1;	
		}
	}
}

int main()
{
	freopen("hungary.in","r",stdin);
	freopen("hungary.out","w",stdout);
	scanf("%d%d",&T,&opt);
	while (T--) 
	{
		memset(dp,0,sizeof dp);
		memset(fz1,0,sizeof fz1);
		memset(fz2,0,sizeof fz2);
		memset(leaf,0,sizeof leaf);
		scanf("%d",&n);
		for (int i=1;i<=n;i++) g[i].clear();
		for (int i=1;i<n;i++) 
		{
			int a,b;
			scanf("%d%d",&a,&b);
			connect(a,b);connect(b,a);
		}
		bfs();
		for (int i=n;i>=1;i--) 
		{
			int u=que[i];
			int ret=0;
			sum[u][0]=sum[u][1]=1;
			int siz=g[u].size();
			for (int j=0;j<siz;j++) 
			{
				int v=g[u][j];
				if (v==father[u]) continue;
				dp[u][0]+=max(dp[v][1],dp[v][0]);
				if (!leaf[v]&&dp[v][1]==dp[v][0]) sum[u][0]=(sum[u][0]*(sum[v][1]+sum[v][0]))%mod;
				if (!leaf[v]&&dp[v][1]>dp[v][0]) sum[u][0]=(sum[u][0]*sum[v][1])%mod;
				if (!leaf[v]&&dp[v][1]<dp[v][0]) sum[u][0]=(sum[u][0]*sum[v][0])%mod;
				ret+=max(dp[v][0],dp[v][1]);
				fz1[j]=sum[u][0];
			}
			long long temp=1;
			for (int j=siz-1;j>=0;j--) 
			{
				int v=g[u][j];	
				if (v==father[u]) continue;
				if (!leaf[v]&&dp[v][1]==dp[v][0]) temp=(temp*(sum[v][1]+sum[v][0]))%mod;
				if (!leaf[v]&&dp[v][1]>dp[v][0]) temp=(temp*sum[v][1])%mod;
				if (!leaf[v]&&dp[v][1]<dp[v][0]) temp=(temp*sum[v][0])%mod;
				fz2[j]=temp;
			}
			for (int j=0;j<siz;j++) 
			{
				int v=g[u][j];
				if (v==father[u]) continue;
				dp[u][1]=max(dp[u][1],ret-max(dp[v][0],dp[v][1])+dp[v][0]+1);	
			}
			temp=0;fz2[siz]=1;
			if (fz1[0]==0) fz1[0]=1;
			for (int j=0;j<siz;j++) 
			{
				int v=g[u][j];
				if (v==father[u]) continue;
				if (dp[u][1]==ret-max(dp[v][0],dp[v][1])+dp[v][0]+1) 
					if (j==0) temp=(temp+fz2[j+1]*sum[v][0])%mod;
				    else temp=(temp+((fz1[j-1]*fz2[j+1])%mod*sum[v][0])%mod)%mod;
			}
			sum[u][1]=temp;
			if (sum[u][1]==0) sum[u][1]=1;
		}
		if (opt==1) printf("%d\n",max(dp[1][1],dp[1][0]));
		else 
		{
			if (dp[1][1]>dp[1][0]) printf("%d %I64d\n",dp[1][1],sum[1][1]);
			if (dp[1][0]>dp[1][1]) printf("%d %I64d\n",dp[1][0],sum[1][0]);
			if (dp[1][0]==dp[1][1]) printf("%d %I64d\n",dp[1][1],(sum[1][0]+sum[1][1])%mod);	
		}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值