【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\)的子树大小
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
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
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
未完==