dp的练习总结(7)

CF440D Berland Federalization

1.本题为树形dp,用f(u,i)​ 表示以 u 为根,取出 i 个节点子树的最少删边次数。

        则有f(u,i)=min( f(u,i-k),f(v,k) ),当k=0时,f(u,i)加一

2.要想输出方案,可以用 g(u,i) 记录一下 f(u,i)​ 的后继状态,转移时从 g(u,i-k)​ 复制。后继状态中显然当 i=0 时 u 要被删去。

PS:当根节点不为1时,ans++

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=405;
int n,k;
int id[maxn][maxn];
vector<pair<int,int>> g[maxn][maxn];
struct node{
	int v,nxt;
}edge[maxn*2];
int head[maxn],cntE;
void add(int u,int v){
	edge[++cntE]=(node){v,head[u]};
	head[u]=cntE;
}
int sz[maxn],fa[maxn];
int f[maxn][maxn];
void cpy(int u,int j,int t){
	g[u][j].clear();
	for(int i=0;i<g[u][t].size();i++){
		g[u][j].push_back(g[u][t][i]);
	}
}
void init(int u,int p){
	sz[u]=1;
	fa[u]=p;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(v==p) continue;
		init(v,u);
		sz[u]+=sz[v];
	}
}
void dfs(int u,int p){
	f[u][1]=f[u][sz[u]]=0;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(v==p) continue;
		dfs(v,u);
		for(int j=sz[u];j>=0;j--){
			for(int k=0;k<=min(sz[v],j);k++){
				if(!k){
					f[u][j]++;
					g[u][j].push_back(make_pair(v,0));
				}
				else if(f[u][j-k]+f[v][k]<f[u][j]){
					f[u][j]=f[u][j-k]+f[v][k];
					cpy(u,j,j-k);
					g[u][j].push_back(make_pair(v,k));
				}
			}
		}
	}
}
void get_ans(int u,int w){
	for(int i=0;i<g[u][w].size();i++){
		if(g[u][w][i].second==0){
			if(g[u][w][i].first!=1) printf("%d ",id[u][g[u][w][i].first]);
		}
		else get_ans(g[u][w][i].first,g[u][w][i].second);
	}
}
int main(){
	scanf("%d%d",&n,&k);
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n-1;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		id[u][v]=id[v][u]=i;
		add(u,v);
		add(v,u);
	}
	init(1,1);
	dfs(1,1);
	int ans=f[1][k],rt=1;
	for(int u=2;u<=n;u++){
		if(k>sz[u]) continue;
		if(f[u][k]+1<ans){
			ans=f[u][k]+1;
			rt=u;
		}
	}
	printf("%d\n",ans);
	if(rt!=1){
		printf("%d ",id[fa[rt]][rt]);
	}
	if(k!=sz[rt])
	get_ans(rt,k);
}

P4516 潜入行动

1.用状态 f(0/1,0/1,u,i)​ 表示以 u 为根的子树中有 i 个被选取的点,第一个0/1 表示 u 是否选取,第二个 0/1 表示 u 是否被覆盖。

2.状态转移:

        (1) u没有被选取且没有被覆盖:

                f(0,0,u.i+j)​ = \sum_{i=0}^{k}( f(0,0,u,i)*f(0,1,v,j) )

        (2) u没有被选取但被覆盖:

                f(0,1,u,i+j)=\sum_{i=0}^{k}( f(0,1,u,i)*( f(1,1,v,j)+f(0,1,v,j) )+f(0,0,u,i)*f(1,1,v,j) )

        (3) u被选取但没有被覆盖:

               f(1,0,u,i+j)= \sum_{i=0}^{k}( f(1,0,u,i)*( f(0,1,v,j)*f(0,0,v,j) ) )

        (4) u被选取且被覆盖:

                f(1,1,u,i+j)=\sum_{i=0}^{k}( f(1,1,u,i)*( f(1,1,v,j)+f(0,1,v,j)+f(1,0,v,j)+f(0,0,v,j))+f(1,0,u,i)*( f(1,1,v,j)+f(1,0,v,j) ) )

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,P=1e9+7;
int n, k;
vector<int> g[N];
int f[2][2][N][101], siz[N], cmp[101][2][2];
void dfs(int u, int fa) {
	siz[u] = f[0][0][u][0] = f[1][0][u][1] = 1;
	for(int i = 0;i < g[u].size();i ++) {
		int j = g[u][i];
		if(j == fa) continue;
		dfs(j, u);
		for(int k1 = 0;k1 <= min(k, siz[u]);k1 ++) {
			cmp[k1][0][0] = f[0][0][u][k1];
			f[0][0][u][k1] = 0;
           	cmp[k1][0][1] = f[0][1][u][k1]; 
			f[0][1][u][k1] = 0;
            cmp[k1][1][0] = f[1][0][u][k1]; 
			f[1][0][u][k1] = 0;
            cmp[k1][1][1] = f[1][1][u][k1]; 
			f[1][1][u][k1] = 0;
		}
		for(int k1 = 0;k1 <= k && k1 <= siz[u];k1 ++) {
			for(int k2 = 0;k2 + k1 <= k && k2 <= siz[j];k2 ++) {
				f[0][0][u][k1+k2] = (1ll * f[0][0][u][k1+k2] + 1ll * cmp[k1][0][0] * f[0][1][j][k2]) % P;
				f[1][0][u][k1+k2] = ((1ll * f[0][1][j][k2] + 1ll * f[0][0][j][k2]) % P * 1ll * cmp[k1][1][0] % P + 1ll * f[1][0][u][k1+k2]) % P;
				f[0][1][u][k1+k2] = (((1ll * f[1][1][j][k2] + f[0][1][j][k2]) % P * 1ll * cmp[k1][0][1]) % P  + 1ll * f[0][1][u][k1+k2] + 1ll * cmp[k1][0][0] * f[1][1][j][k2] % P) % P;
				f[1][1][u][k1+k2] = (1ll * f[1][1][u][k1+k2] + (1ll * f[1][1][j][k2] + f[0][1][j][k2] + f[1][0][j][k2] + f[0][0][j][k2]) % P * 1ll * cmp[k1][1][1] % P + cmp[k1][1][0] * (1ll * f[1][1][j][k2] + f[1][0][j][k2]) % P) % P;
			}
		}
		siz[u] += siz[j];
	}
}
int main() {
	cin >> n >> k;
	for(int i = 1, a, b;i < n;i ++) {
		cin >> a >> b;
		g[a].push_back(b), g[b].push_back(a);
	}
	dfs(1, 0);
	cout << (1ll * f[1][1][1][k] + f[0][1][1][k]) % P;
	return 0;
} 

  • 9
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值