【2019.10.18】luogu TG5动态规划进阶

树形dp

P1352 没有上司的舞会

P2607 骑士(review)

对于每一个"联通快" 只有根节点有机会形成环

强制不选(rt)(rt)的父亲 各跑一遍

P1131 时态同步(review)

贪心 显然增加深度约小的边越优

从下到上来调整 先将同一个点的儿子们延伸到一样 再往上进行一样的操作

//apio 烟火

树上背包?

一棵(n)个点的树,有点权。选择一个大小不超过(K)的联通块,使得点权和最大。(n ≤ 2000)

(f(x, i))表示(x)的子树里选择(i)个点,并且(x)必须选的最大权值。 依次用(x)的每个儿子来更新(f(x, ∗))

枚举儿子(y),把(g)置为(∞)

再枚举(i, j),用(f(x, i) + f(y, j))更新(g(i + j))

然后用(g(i))更新(f(x, i))

重复直到所有儿子都被加入

可以看成每次把(x)的一个儿子合并到(x)上,并求出新的(f(x, ∗))

用枚举点的子树大小限制枚举范围,复杂度(O(n^2 ))

证明:每两个点都只会在最近公共祖先处被合并一次。

vector<int> G[N];
int f[N][N],g[N],siz[N];

void dfs(int x,int pa){
	siz[x]=1;
	for(auto y:G[x])if(y!=pa){
		dfs(y,x),siz[x]+=siz[y];
	}
	for(int i=0;i<=siz[x];i++)f[x][i]=INF;
	f[x][0]=0,f[x][1]=val[x];
	siz[x]=1;
	for(int i=0;i<G[x].size();i++){
		int y=G[x][i];
		for(int i=0;i<=siz[x]+siz[y];i++)g[i]=INF;
		for(int i=0;i<=siz[x];i++)for(int j=0;j<=siz[y];j++)
			g[i+j]=max(g[i+j],f[x][i]+f[y][j]);
		for(int i=0;i<=siz[x]+siz[y];i++)f[x][i]=max(f[x][i],g[i]);
		siz[x]+=siz[y];
	}
}

POI2017 Sabota

叛徒一定为一棵子树 第一个叛徒一定为叶子

(v)越小越容易变成叛徒 越大越不容易变成叛徒 找 使(x)变成叛徒的最小&最大(v)

最坏情况下,第一个叛徒一定是叶子,所以最终叛徒一定是一棵子树。

(f(x))表示使得(x)不变成叛徒的最小的(v)

(f(x))同时是使得(x)变成叛徒的最大的(v)

枚举叛变的子树,(sizex)表示(x)的子树大小

[f(x)=max_{yin son_x}{min{f(y),frac{size_y}{size_x-1}} ]

void dfs(int u){
	sz[u]=1,f[u]=0.0;
	for(int i=head[u],v;i;i=e[i].nxt) dfs((v=e[i].v)),sz[u]+=sz[v];
	for(int i=head[u],v;i;i=e[i].nxt)
		f[u]=Max(f[u],Min(f[v=e[i].v],(double)sz[v]/((double) sz[u]-1)));
	if(f[u]==0.0) f[u]=1.0;
	if(sz[u]>k) ans=Max(ans,f[u]);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
	rd(n),rd(k);
	for(int i=2,u;i<=n;++i) rd(u),add(u,i);
	dfs(1);
	if(ans-0.0<1e-9) puts("0");
	else printf("%.10lf",ans);
    return 0;
}

COCI2009 podjela

一棵(n)个点的树,每个点上有一个农民。初始时每个农民有(X)的钱。 每一次操作,一个农民可以从它自己的钱中,取出任意数量的钱交给某 一个相邻的农民。对于每个农民给定一个值(v_i),问至少操作多少次,可以使得每个农民的钱(≥)给定的值

(n ≤ 2000, X ≤ 10000), 保证有解。

(f(x, i))表示(x)的子树,进行操作后可以往上运/必须往下运(i)元,所需 的最小操作次数。(O(nX^2 ))

发现: 每一条边最多进行一次操作 总操作数不超过(n − 1)

(f(x, i))表示(x)的子树内部进行(i)次操作,根节点最多能给出多少钱。(f(x, i))为负数表示还需要运进的钱数。 背包转移,每次把(y)加入(x)的子树中,并枚举((x, y))这条边是否操作。

(f(x, i) + f(y, j) → g(x, i + j + 1))

(f(x, i) → g(x, i + j) (f(y, j) ≥ 0))总复杂度(O(n^2 ))

树上背包

int f[N][N],g[N],sz[N];
void dfs(int u,int ff){
	f[u][0]=K-a[u],sz[u]=1;
	for(int i=head[u],v;i;i=e[i].nxt)
	if((v=e[i].v)!=ff){
		dfs(v,u);
		for(int j=0;j<=sz[u]+sz[v];++j) g[j]=-inf;
		for(int j=0;j<sz[u];++j)
		for(int k=0;k<sz[v];++k){
			g[j+k+1]=Max(g[j+k+1],f[u][j]+f[v][k]);
			if(f[v][k]>=0) g[j+k]=Max(g[j+k],f[u][j]);
		}
		sz[u]+=sz[v];
		for(int j=0;j<=sz[u];++j) f[u][j]=g[j];
	}
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
	rd(n),rd(K);
	memset(f,~inf,sizeof(f));
	for(int i=1;i<=n;++i) rd(a[i]);
	for(int i=1,u,v;i<n;++i) rd(u),rd(v),add(u,v);
	dfs(1,0);
	printf("%d",lower_bound(f[1],f[1]+n,0)-f[1]);
    return 0;
}

P3479 灭火器

从下往上贪心,不得不配对的时候再配对。

(f(x, i))表示(x)下面与(x)的距离为(i)的多余灭火器数量。

(g(x, i))表示(x)下面与(x)的距离为(i)的需要灭火器的点数。

假设子树内最优,如果(g(x, K) > 0),那么需要在(x)处放灭火器,增加(f(x, 0))进行匹配。

距离为(K)的两个点需要匹配,即 f(x, i) 覆盖掉 g(x, K − i)。否则在上 面匹配不会更优。 距离为 K − 1 的两个点也需要匹配,即 f(x, i) 覆盖掉 g(x, K − i − 1)。 否则无法在深度更小的点匹配。(距离变为 K + 1) 根节点特殊处理。

??? 我咕辽

状压dp

P1896 互不侵犯

P4163 排列

如果结果串为 T,我们要求出它对 d 取模的值。

记录值 val,从高到低扫描T的每一位(T_i)(val = 10 × val + T_i)

状压dp,f(S, i) 表示下标集合 S 内的数被用过了,当前的数模 d 为 i 的方案数

每次枚举一个不在 S 内的数加入 T 转移。

(f(S, i) → f(S ∪ {k},(i × 10 + sk) mod d))最后答案是 $f({1, 2, · · · , n}, 0) $

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
	int T;rd(T);
	while(T--){
		memset(f,0,sizeof(f));
		scanf("%s %d",s+1,&d);
		n=strlen(s+1);
		for(int i=1;i<=n;++i) a[i]=s[i]-'0';
		f[0][0]=1;
		for(int st=0;st<(1<<n)-1;++st){
			memset(vis,0,sizeof(vis));
			for(int i=1,nw;i<=n;++i)
			if(!(st&(1<<(i-1)))&&!vis[a[i]]){
				vis[a[i]]=1,nw=st|(1<<(i-1));
				for(int j=0;j<d;++j) f[nw][(j*10+a[i])%d]+=f[st][j];
			}
		}
		printf("%d
",f[(1<<n)-1][0]);
	}
    return 0;
}

P2831 愤怒的小鸟(review)

两只小猪可以确定一条抛物线。

f(S) 表示消灭 S 内的小猪所需的最小小鸟数。枚举两只小猪转移。

预处理:s(i, j) 表示经过小猪 i 和 j 的抛物线会经过哪些猪。

转移:f(S) + 1 → f(S/s(i, j)) 复杂度:(O(2^nn^2 ))

优化:在 S 内任选一只猪,枚举另一只猪,而不需要枚举两只猪。 复杂度:$O(2^nn) $

P3959 宝藏(review)

树是一个分层结构,可以根据层数进行dp。

(f(i, S))表示当前已经打通(S)内的点,树的最大深度为(i )的最小代价。

预处理(g(S, T))表示将(T)中所有点挂到(S)上的最小代价。 枚举下一层的点集(T)转移。

(f(i, S) + g(S, T) × (i + 1) → f(i + 1, S ∪ T))

原来写的是记搜

#include <bits/stdc++.h>
#define FIO "race"
#define mset(a,b) memset(a,b,sizeof a)
#define mcpy(a,b) memcpy(a,b,sizeof b)
#define xx first
#define yy second
#define pb push_back
#define mp make_pair
#define pii pair<int,int> 
#define lb(x) ((x)&(-(x)))
#define dalao 1000000007
#define inf 0x3f3f3f3f
#define M 4097
#define N 12
using namespace std;
typedef long long ll;
int n,m,S,T,a[N][N],f[2][M],g[M][M],mi,val,ans=inf;
int main(){
	scanf("%d%d",&n,&m);
	mset(a,0x3f);
	while(m--){
		int x,y,c;scanf("%d%d%d",&x,&y,&c),x--,y--;
		if(a[x][y]>c)a[x][y]=a[y][x]=c;
	}
	for(int i=0;i<n;i++)a[i][i]=0;
	for(int i=1;i<(1<<n);i++)for(int j=(i-1)&i;j;j=(j-1)&i){
		S=j,T=i^j;
		int tot=0;
		for(int x=0;x<n;x++)if(T>>x&1){
			mi=inf;
			for(int y=0;y<n;y++)if(S>>y&1)mi>a[x][y]?mi=a[x][y]:0;
			if(mi==inf){tot=50000000;break;}
			tot+=mi;
		}
		g[S][T]=tot;
	}
	for(int x=0;x<n;x++){
		mset(f,0x3f),f[0][1<<x]=0;
		for(int i=1,z=1;i<n;i++,z^=1){
			for(int j=1;j<(1<<n);j++)if(j>>x&1){
				f[z][j]=inf;
				for(int k=(j-1)&j;k;k=(k-1)&j)if(k>>x&1)
					f[z][j]>(val=f[z^1][k]+g[k][k^j]*i)?f[z][j]=val:0;
			}
			ans>f[z][(1<<n)-1]?ans=f[z][(1<<n)-1]:0;
		}
	}
	printf("%d",ans);
	return 0;
}

S&T=0

[Sigma_{i=0}^n2^iC_n^i =(2+1)^n =3^n ]

P2157 学校食堂

f(i, j, S) 表示前 i − 1 个人已经全部做好,第 i 个到第 i + 7 个人是否做 好的状态为 S,上一道菜是 j 的最少时间。 枚举下一个服务的人转移。 如果下一个人是 i,那么 f(i, j, S) + cost(j, i) → f(i + 1, i, S >> 1) 否则设为 k,f(i, j, S) + cost(j, k) → f(i, k, S ∪ {k − i})

不想做,鸽掉!

for(int S=0;S<(1<<n);S++){
	for(int i=0;i<n;i++)for(int j=0;j<n;j++)if((S>>i&1)&&(S>>j&1))
		if(d[i][j]!=-1)val[S]+=d[i][j];
}

S&T=0

[Sigma_{i=0}^n2^iC_n^i =(2+1)^n =3^n ]

a|b-a&b=a xor b

0 0 0-0 = 0
0 1 1-0 = 1
1 1 1-1 = 0

AT 078F

数位dp

(f(i,j,k,0,x) ightarrow f(i+1,k,s,0,x|(s==k||s==j)) (0le sle 9))

(f(i,j,k,1,x) ightarrow f(i+1,k,s,[s=r_i],x|(s==k||s==j)) (0le sle R_i))

单调队列优化

P1725

未完==

原文地址:https://www.cnblogs.com/lxyyyy/p/11700579.html