• 【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

    未完==

  • 相关阅读:
    HDU6060 RXD and dividing
    Knapsack in a Globalized World --多重完全背包
    hdu 6058 Kanade's sum
    矩形面积 HDU
    Bridge Across Islands POJ
    Manors HDU
    Harry Potter and J.K.Rowling HDU
    Polygons HDU
    Jungle Outpost HDU
    CRB and Farm HDU
  • 原文地址:https://www.cnblogs.com/lxyyyy/p/11700579.html
Copyright © 2020-2023  润新知