树形dp
1
n n 个点的树,边有边权,把其中 个点涂成黑色,其余的点涂成白色,最大化:每两个黑点间距离和+每两个白点间的距离和。
直接设 f[i][j] f [ i ] [ j ] 表示 i i 子树里选 个黑点的最大值。然后发现不能转移…
考虑到每条边是独立的,因此这样设状态:令 f[i][j] f [ i ] [ j ] 表示 i i 的子树里选 个黑点最多对答案有多少贡献。
一条边对答案的贡献就是 权值 × × (左边黑点数 × × 右边黑点数 + + 左边白点数 右边白点数)。
void dfs(int u,int fa)
{
size[u]=1;
memset(f[u],-1,sizeof(f[u]));
f[u][0]=f[u][1]=0;
for(int i=head[u];i;i=ed[i].next)
{
int v=ed[i].to;
if(v==fa) continue;
dfs(v,u);
size[u]+=size[v];
}
for(int i=head[u];i;i=ed[i].next)
{
int v=ed[i].to;
if(v==fa) continue;
for(int j=min(size[u],k);j>=0;j--)
{
for(int l=0,lim=min(size[v],j);l<=lim;l++)
{
if(~f[u][j-l])
{
ll val=1ll*ed[i].w*(1ll*l*(k-l)+1ll*(size[v]-l)*(n-size[v]-k+l));
f[u][j]=max(f[u][j],f[u][j-l]+f[v][l]+val);
}
}
}
}
}
2
【SDOI 2008】山贼集团
题意:
n
n
个点的树,根为 ,设置
p
p
个黑点(有编号) ,每个黑点可以管理它到根的路径上的每一个点,某些黑点同时管理同一个点会获得一定价值/损失,在不同的点设置不同的黑点要花费不同的代价,要求最大化收益。
p<=12
p
<=
12
状压。
f[i][j]
f
[
i
]
[
j
]
表示
i
i
为根的子树内选择 的集合的最大收益。转移的时候先不考虑
i
i
,令 表示
i
i
的子树中除去 的点选择
j
j
的集合的最大价值(类似一个背包)。然后考虑 这个点放那些黑点,转移一下。
void dfs(int u,int fa)
{
f[u][0]=0;
for(int i=head[u];i;i=ed[i].next)
{
int v=ed[i].to;
if(v==fa) continue;
dfs(v,u);
for(int j=0;j<=fff;j++) tmp[j]=-INF;
tmp[0]=0;
for(int j=0;j<=fff;j++)
for(int k=j;k<=fff;k=(k+1)|j) tmp[k]=max(tmp[k],f[u][k^j]+f[v][j]);
for(int j=0;j<=fff;j++) f[u][j]=tmp[j];
}
for(int j=0;j<=fff;j++) tmp[j]=f[u][j];
memset(cost,0,sizeof(cost));
for(int j=0;j<=fff;j++)
for(int i=1;i<=p;i++)
if(j&(1<<i-1)) cost[j]+=a[u][i];
for(int i=0;i<=fff;i++) f[u][i]=-INF;
for(int j=0;j<=fff;j++)
{
for(int k=j;k;k=(k-1)&j) f[u][j]=max(f[u][j],tmp[k^j]+w[j]-cost[k]);
f[u][j]=max(f[u][j],tmp[j]+w[j]);
}
}
int main()
{
scanf("%d%d",&n,&p);
fff=(1<<p)-1;
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=p;j++) scanf("%d",&a[i][j]);
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
int v,c,sta=0;
scanf("%d%d",&v,&c);
int ss=0;
if(c==0) while(1);
for(int j=1;j<=c;j++)
{
int x;
scanf("%d",&x);
sta|=(1<<x-1);
}
val[sta]+=v;
}
for(int j=0;j<=fff;++j)
for(int k=j;k;k=j&(k-1)) w[j]+=val[k];
for(int i=1;i<=n;i++)
for(int j=0;j<=fff;j++) f[i][j]=-INF;
dfs(1,1);
cout<<f[1][fff];
return 0;
}
3
【JSOI 2018】潜入行动
题意:
n
n
个点的树,设置 个监察点,每个监察点可以监视与它相邻的点,但不能监视自己。要监视所有点,求方案数。
令 f[i][j][1/0][1/0] f [ i ] [ j ] [ 1 / 0 ] [ 1 / 0 ] 表示 i i 子树里设置 个监察点,根有/没有设置监察点,有/没有被监视。然后分情况转移。
void dfs(int u,int fa)
{
size[u]=1,f[u][0][0][0]=1,f[u][1][1][0]=1;
for(int tt=head[u];tt;tt=ed[tt].next)
{
int v=ed[tt].to;
if(v==fa) continue;
dfs(v,u);
int lim1=min(k,size[u]);
for(int j=0;j<=lim1;j++)
{
g[j][0][0]=f[u][j][0][0],f[u][j][0][0]=0;
g[j][0][1]=f[u][j][0][1],f[u][j][0][1]=0;
g[j][1][0]=f[u][j][1][0],f[u][j][1][0]=0;
g[j][1][1]=f[u][j][1][1],f[u][j][1][1]=0;
}
for(int i=0;i<=lim1;i++)
{
int lim2=min(size[v],k);
for(int j=0;j<=lim2&&i+j<=k;j++)
{
f[u][i+j][0][0]+=(ll)1ll*g[i][0][0]*f[v][j][0][1]%mod;
f[u][i+j][0][0]%=mod;
f[u][i+j][0][1]+=(ll)1ll*g[i][0][1]*(f[v][j][0][1]+f[v][j][1][1])%mod+1ll*g[i][0][0]*f[v][j][1][1]%mod;
f[u][i+j][0][1]%=mod;
f[u][i+j][1][0]+=(ll)1ll*g[i][1][0]*(f[v][j][0][0]+f[v][j][0][1])%mod;
f[u][i+j][1][0]%=mod;
f[u][i+j][1][1]+=(ll)1ll*g[i][1][1]*(f[v][j][0][0]+f[v][j][1][0]+f[v][j][0][1]+f[v][j][1][1])%mod+1ll*g[i][1][0]*(f[v][j][1][0]+f[v][j][1][1])%mod;
f[u][i+j][1][1]%=mod;
}
}
size[u]+=size[v];
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v),add(v,u);
}
dfs(1,0);
cout<<(f[1][k][1][1]+f[1][k][0][1])%mod;
return 0;
}