不定根问题专题
不定根问题专题
此类问题多用树形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;
}