不定根问题专题

不定根问题专题

此类问题多用树形DP来解决,先指定一个根,快速求出其答案,然后再将当前已算出的答案重复利用,\(O(1)\)的向子节点转移,求出以子节点为根的答案。


T1 [Coci2015]Kamp

因为举行聚会的地点不确定,所以假设在\(x\)点举行,容易算得从\(x\)\(k\)个人送回家的最小时间为\(x\)到每个点的距离的\(2\)倍-到最远的关键点的距离。

这个答案通过\(dp\)很好求,那么转移根的时候,考虑到需要维护最远距离,所以需要保存下当前点到子树中关键点的最长距离和次长距离,以及最长距离的转移点,这样就可以做到轻松转移了,详情看代码:

#include<bits/stdc++.h>
#define N 600000
#define M 1010000
#define ll long long
using namespace std;
const ll inf=1e16;
int n,k,head[N],cnt=1,book[N];
struct note{
	int to,nxt;
	ll w;
}a[M];
void add(int x,int y,ll z)
{
	a[cnt].to=y;
	a[cnt].w=z;
	a[cnt].nxt=head[x];
	head[x]=cnt++;
}
ll f[N],sum[N],mx[N][2];
ll md[N],sz[N],nxt[N],ans,st[N],top;
void dfs(int x,int fa)
{
	int i;
	f[x]=fa,mx[x][0]=mx[x][1]=md[x]=-inf;
	if(book[x]) mx[x][0]=0,sz[x]=1;
	st[++top]=x;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(to==fa) continue;
		dfs(to,x);
		sz[x]+=sz[to];
		if(!sz[to]) continue;
	    sum[1]+=a[i].w;
		if(mx[to][0]+a[i].w>=mx[x][0])
	    {
	    	nxt[x]=to;
	    	mx[x][1]=mx[x][0];
	    	mx[x][0]=mx[to][0]+a[i].w;
	    }
	    else if(mx[to][0]+a[i].w>mx[x][1])
	    	mx[x][1]=mx[to][0]+a[i].w;
	}
}
int main()
{
	int i,j,x,u,v;
	ll w;
	scanf("%d%d",&n,&k);
	for(i=1;i<n;i++)
	{
		scanf("%d%d%lld",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	for(i=1;i<=k;i++)
	{
		scanf("%d",&x);
		book[x]=1;
	}
	dfs(1,0);
	for(i=1;i<=n;i++)
	{
		ll x=st[i];
		for(j=head[x];j;j=a[j].nxt){
			int y=a[j].to,z=a[j].w;
			if(y==f[x])continue;
			if(sz[y]==k) sum[y]=sum[x]-z;
			else if(sz[y]==0) sum[y]=sum[x]+z;
			else sum[y]=sum[x];
			if(nxt[x]==y)md[y]=max(md[x],mx[x][1])+z;
			else md[y]=max(md[x],mx[x][0])+z;
		}
	}
	for(i=1;i<=n;i++){
		printf("%lld\n",sum[i]*2-max(md[i],mx[i][0]));
	}
	return 0;
}

T2 [CQOI2009]叶子的染色

首先可以证明(猜)出一个结论,以任意一个非叶子节点为根的答案是一样的。

所以我们随意选择一个节点为根,每一个节点染的颜色值域它的儿子节点有关,所以设状态为\(dp[x][0/1]\)为当前节点染黑色或白色时,当前节点子树中着色节点个数最小值。

易得转移方程:

\[dp[x][0]=\sum_{to}min(dp[to][0]-1,dp[to][1]) \]

\[dp[x][1]=\sum_{to}min(dp[to][1]-1,dp[to][0]) \]

#include<bits/stdc++.h>
#define N 600000
#define M 1010000
#define ll long long
using namespace std;
int n,m,c[N],head[N],cnt=1;
int dp[N][2],inf=1e9;
struct note{
	int to,nxt;
}a[M];
void add(int x,int y)
{
	a[cnt].to=y;
	a[cnt].nxt=head[x]; 
	head[x]=cnt++;
}
void dfs(int x,int fa)
{
	int i;
	if(x<=n)
	{
		dp[x][c[x]]=1;
		dp[x][!c[x]]=inf;
		return;
	}
	dp[x][1]=dp[x][0]=1;
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(to==fa) continue;
		dfs(to,x);
		dp[x][0]+=min(dp[to][1],dp[to][0]-1);
		dp[x][1]+=min(dp[to][0],dp[to][1]-1); 
	}
}
int main()
{
	int i,u,v;
	scanf("%d%d",&m,&n);
	for(i=1;i<=n;i++) scanf("%d",&c[i]);
	for(i=1;i<m;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	dfs(n+1,0);
	printf("%d\n",min(dp[n+1][0],dp[n+1][1]));
	return 0;
}

T3 [SHOI2014]概率充电器

一道概率水题。

首先每个元件充电个数的期望,等价于每个元件充电概率之和。

于是问题变成了求每个元件充电的概率,即\(1-未充电的概率\)

于是定根,列转移方程,然后按套路换根转移即可。

#include<bits/stdc++.h>
#define N 600000
#define M 1010000
#define ll long long
using namespace std;
int n,head[N],cnt=1;
struct note{
	int to,nxt;
	double w;
}a[M];
void add(int x,int y,double z)
{
	a[cnt].to=y;
	a[cnt].w=z;
	a[cnt].nxt=head[x];
	head[x]=cnt++;
}
double dp[N],ans[N],Ans,p[N];
void dfs(int x,int fa)
{
	int i,j,pd=0;
	dp[x]=1-p[x];
	for(i=head[x];i;i=a[i].nxt)
	{
		int to=a[i].to;
		if(to==fa) continue;
	    dfs(to,x);
	    dp[x]*=(1-a[i].w+a[i].w*dp[to]);
	}
}
void dfs2(int x,int fa,int e)
{
	if(x==1) ans[x]=dp[x];
	else
	{
		double P=ans[fa]/(1-a[e].w+a[e].w*dp[x]);
		ans[x]=dp[x]*(1-a[e].w+a[e].w*P);
	}
	for(int i=head[x];i;i=a[i].nxt)
	{
		int v=a[i].to;
		if(v==fa)continue;
		dfs2(v,x,i);
	}
}
int main()
{
	int i,j,u,v,w;
	scanf("%d",&n);
	for(i=1;i<n;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,1.0*w/100);
		add(v,u,1.0*w/100);
	}
	for(i=1;i<=n;i++) 
	{
		scanf("%lf",&p[i]); 
		p[i]/=100;
	}
	dfs(1,0);ans[1]=dp[1];
	dfs2(1,0,0);
	for(i=1;i<=n;i++)
	{
		Ans+=1-ans[i];
	}
	printf("%.6lf\n",Ans);
	return 0;
}
posted @ 2020-10-30 11:42  yzxx_qwq  阅读(106)  评论(0编辑  收藏  举报