码迷,mamicode.com
首页 > 其他好文 > 详细

【2019.10.18】luogu TG5动态规划进阶

时间:2019-10-18 20:40:05      阅读:90      评论:0      收藏:0      [点我收藏+]

标签:pod   必须   还需要   最小   tor   队列   span   最坏情况   amp   

树形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_{y\in 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\n",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)\rightarrow f(i+1,k,s,0,x|(s==k||s==j)) (0\le s\le 9)\)

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

单调队列优化

P1725

未完==

【2019.10.18】luogu TG5动态规划进阶

标签:pod   必须   还需要   最小   tor   队列   span   最坏情况   amp   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!