树形 dp
「SHOI2017」摧毁「树状图」
转移式可以复杂,但也只有转移式复杂
开一个 \([u][4][4]\) 数组,记录两条路线的状态,\(0\) 表示无路线,\(1\) 表示可延伸的路线,\(2\) 表示存在路线但不可延伸,\(4\) 表示路线完全在子树中结束。类似树上背包,dfs逐子节点处理,从而推36个从子节点到缓存数组的式子,再推到15种父节点的式子(\([1][1]\) 不存在)。只是非常容易遗漏情况。
transform
0,0 =0,0
0,1 =0,0 +son
=0,1 +son-1
0,2 =0,0 +son
=0,1 +son-1
=0,1-0,1 +son-2
0,3 =0,2
=0,3
1,2 =0,0 +son
=0,1 +son-1
=1,0 +son-1
=0,1-0,1 +son-2
=0,1-1,0 +son-2
=0,1-0,1-1,0 +son-3
1,3 =0,2 +son-1
=0,3 +son
=1,2 +son-1
=1,3 +son-1
=0,2-1,0 +son-2
=0,3-1,0 +son-1
2,2 =0,0 +son
=0,1 +son-1
=0,1-1,0 +son-2
=0,1-0,1-1,0 +son-3
=0,1-0,1-1,0-1,0 +son-4
2,3 =0,2 +son-1
=0,3 +son
=1,2 +son-1
=1,3 +son-1
=0,2-1,0 +son-2
=0,3-1,0 +son-1
=1,2-1,0 +son-2
=1,3-1,0 +son-2
=0,2-1,0-1,0 +son-3
=0,3-1,0-1,0 +son-2
3,3 =2,2
=2,3
=3,3
=0,2-2,0
=0,2-3,0
=0,3-3,0
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
int n,T,x;
int head[100010],to[200010],nex[200010],edge=0;
vector<array<array<int,4>,4> >dp;
void dfs(int u,int fa){
int f[40];
f[0]=0;
for(int i=1;i<=36;i++)f[i]=-2147483648;
int son=0;
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(v==fa)continue;
son++;
dfs(v,u);
f[36]=max({dp[v][1][0]+f[5],dp[v][1][2]+f[4],f[36]});
f[35]=max({dp[v][0][1]+f[8],dp[v][2][1]+f[1],f[35]});
f[34]=max({dp[v][0][3]+f[11],dp[v][3][0]+f[3],f[34]});
f[33]=max({dp[v][0][3]+f[7],dp[v][2][0]+f[3],f[33]});
f[32]=max({dp[v][0][2]+f[11],dp[v][3][0]+f[2],f[32]});
f[31]=max({dp[v][1][0]+f[6],dp[v][1][3]+f[4],f[31]});
f[30]=max({dp[v][0][1]+f[12],dp[v][3][1]+f[1],f[30]});
f[29]=max({dp[v][0][2]+f[7],dp[v][2][0]+f[2],f[29]});
f[28]=max({dp[v][0][1]+f[23],dp[v][1][0]+f[22],f[28]});
f[27]=max({dp[v][0][3]+f[19],dp[v][1][0]+f[18],f[27]});
f[26]=max({dp[v][0][2]+f[19],dp[v][1][0]+f[17],f[26]});
f[25]=max({dp[v][0][1]+f[21],dp[v][3][0]+f[15],f[25]});
f[24]=max({dp[v][0][1]+f[20],dp[v][2][0]+f[15],f[24]});
f[23]=max({dp[v][0][1]+f[19],dp[v][1][0]+f[16],f[23]});
f[22]=max({dp[v][0][1]+f[16],dp[v][1][0]+f[15],f[22]});
f[21]=max({dp[v][0][1]+f[11],dp[v][3][0]+f[1],f[21]});
f[20]=max({dp[v][0][1]+f[7],dp[v][2][0]+f[1],f[20]});
f[19]=max(dp[v][1][0]+f[4],f[19]);
f[18]=max({dp[v][0][3]+f[4],dp[v][1][0]+f[3],f[18]});
f[17]=max({dp[v][0][2]+f[4],dp[v][1][0]+f[2],f[17]});
f[16]=max({dp[v][0][1]+f[4],dp[v][1][0]+f[1],f[16]});
f[15]=max(dp[v][0][1]+f[1],f[15]);
f[14]=max(dp[v][3][3],f[14]);
f[13]=max(dp[v][3][2],f[13]);
f[12]=max(dp[v][3][1],f[12]);
f[11]=max(dp[v][3][0],f[11]);
f[10]=max(dp[v][2][3],f[10]);
f[9]=max(dp[v][2][2],f[9]);
f[8]=max(dp[v][2][1],f[8]);
f[7]=max(dp[v][2][0],f[7]);
f[6]=max(dp[v][1][3],f[6]);
f[5]=max(dp[v][1][2],f[5]);
f[4]=max(dp[v][1][0],f[4]);
f[3]=max(dp[v][0][3],f[3]);
f[2]=max(dp[v][0][2],f[2]);
f[1]=max(dp[v][0][1],f[1]);
}
dp[u][0][0]=f[0];
dp[u][0][1]=max(f[0]+son,f[1]+son-1);
dp[u][0][2]=max({f[0]+son,f[1]+son-1,f[15]+son-2});
dp[u][0][3]=max(f[2],f[3]);
dp[u][1][0]=max(f[0]+son,f[4]+son-1);
dp[u][1][2]=max({f[0]+son,f[1]+son-1,f[4]+son-1,f[15]+son-2,f[16]+son-2,f[22]+son-3});
dp[u][1][3]=max({f[2]+son-1,f[3]+son,f[5]+son-1,f[6]+son-1,f[17]+son-2,f[18]+son-1});
dp[u][2][0]=max({f[0]+son,f[4]+son-1,f[15]+son-2});
dp[u][2][1]=max({f[0]+son,f[1]+son-1,f[4]+son-1,f[16]+son-2,f[19]+son-2,f[23]+son-3});
dp[u][2][2]=max({f[0]+son,f[1]+son-1,f[4]+son-1,f[16]+son-2,f[22]+son-3,f[23]+son-3,f[28]+son-4});
dp[u][2][3]=max({f[2]+son-1,f[3]+son,f[5]+son-1,f[6]+son-1,f[17]+son-2,f[18]+son-1,f[36]+son-2,f[31]+son-2,f[26]+son-3,f[27]+son-2});
dp[u][3][0]=max(f[7],f[11]);
dp[u][3][1]=max({f[7]+son-1,f[11]+son,f[8]+son-1,f[12]+son-1,f[20]+son-2,f[21]+son-1});
dp[u][3][2]=max({f[7]+son-1,f[11]+son,f[8]+son-1,f[12]+son-1,f[20]+son-2,f[21]+son-1,f[35]+son-2,f[30]+son-2,f[24]+son-3,f[25]+son-2});
dp[u][3][3]=max({f[9],f[10],f[13],f[14],f[29],f[32],f[33],f[34]});
}
void init(){
n=read(),edge=0;
for(int i=1;i<=2*x;i++)read();
dp.resize(n+10,{});
memset(head+1,0,sizeof(int)*n);
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
nex[++edge]=head[u];
to[head[u]=edge]=v;
nex[++edge]=head[v];
to[head[v]=edge]=u;
}
dfs(1,0);
printf("%lld\n",max({dp[1][1][2],dp[1][1][3],dp[1][2][1],dp[1][2][2],dp[1][2][3],dp[1][3][1],dp[1][3][2],dp[1][3][3]+1}));
return;
}
signed main(){
T=read(),x=read();
for(int i=1;i<=T;i++)init();
return 0;
}
ABC416F Paint Tree 2
不得不说,相对于邻近的题目,这一道非常纯粹。没有树上背包,也没有换根。
每个点仅能包含于一条路径中,因而状态可分为此节点 未被选中/与一个子结点相连(可向上传递)/与两个子结点相连(不可向上传递)
操作次数与染色状态各一维,依然逐子节点处理。
可以通过控制更新顺序避免覆盖需使用的节点信息。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
int n,V;
int head[200010],to[400010],nex[400010],edge=0;
int w[200010];
int dp[200010][7][3];
void dfs(int u,int fa){
for(int i=0;i<=V+1;i++)dp[u][i][0]=dp[u][i][1]=dp[u][i][2]=-21474836480000000000;//注意数据范围!!!
dp[u][1][1]=w[u],dp[u][0][0]=0;
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(v==fa)continue;
dfs(v,u);
for(int m=V;m>=1;m--){
for(int k=m;k>=0;k--){//倒叙枚举,防止覆盖信息
tie(dp[u][m][0],dp[u][m][1],dp[u][m][2])=
make_tuple(max({dp[u][k][0]+dp[v][m-k][0],dp[u][m][0]}),
max({dp[u][k][0]+dp[v][m-k][1]+w[u],dp[u][k][1]+dp[v][m-k][0],dp[u][m][1]}),
max({dp[u][k][2]+dp[v][m-k][0],dp[u][k][1]+dp[v][m-k+1][1],dp[u][m][2]}));
}
}
}
for(int m=1;m<=V;m++)dp[u][m][0]=max({dp[u][m][0],dp[u][m][1],dp[u][m][2]});
}
int init(){
n=read(),V=read();
for(int i=1;i<=n;i++)w[i]=read();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
nex[++edge]=head[u];
to[head[u]=edge]=v;
nex[++edge]=head[v];
to[head[v]=edge]=u;
}
dfs(1,0);
int ans=0;
for(int i=0;i<=V;i++)ans=max(dp[1][i][0],ans);
printf("%lld",ans);
return 2;
}
int Mouska_Mouska=init();
signed main(){
return 0;
}
ARC179D Portable Gate
转移起始位置的换根dp。
显然,依照dfs序遍历一定不劣,因而可转化为子树中的问题。
状态:\(dp[i][j][k]\) :在\(i\)子树中的最小步数 \(i\) :当前位置,\(j\) :是否有门,\(k\) :是否返回\(i\)
换根可以用前后缀和优化为线性复杂度。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
int n;
int head[200010],to[400010],nex[400010],edge=0;
array<array<array<int,2>,2>,200010> dp;
int ans=2147483648999999,son[200010];
void dfs(int u,int fa){
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
son[u]++;
if(v==fa)continue;
dfs(v,u);
tie(dp[u][1][0],dp[u][0][0],dp[u][1][1],dp[u][0][1])=make_tuple(
min(dp[u][1][1]+min(dp[v][1][0]+1,dp[v][0][0]+1),dp[u][1][0]+min(dp[v][1][1]+2,dp[v][0][0]+1)),
min(dp[u][0][1]+dp[v][0][0]+1,dp[u][0][0]+dp[v][0][1]+2),
dp[u][1][1]+min(dp[v][1][1]+2,dp[v][0][0]+1),
dp[u][0][1]+dp[v][0][1]+2);
}
}
void dfs2(int u,int fa){
vector<array<array<int,2>,2> >pre,pos;
pre.resize(son[u]+2,{}),pos.resize(son[u]+2,{});
for(int te=head[u],v,k=0;te;te=nex[te]){
v=to[te];
k++;
pre[k]=pos[k]=dp[v];
}
for(int i=1;i<=son[u];i++){
tie(pre[i][1][0],pre[i][0][0],pre[i][1][1],pre[i][0][1])=make_tuple(
min(pre[i-1][1][0]+min(pre[i][1][1]+2,pre[i][0][0]+1),pre[i-1][1][1]+min(pre[i][1][0]+1,pre[i][0][0]+1)),
min(pre[i-1][0][0]+pre[i][0][1]+2,pre[i-1][0][1]+pre[i][0][0]+1),
pre[i-1][1][1]+min(pre[i][1][1]+2,pre[i][0][0]+1),
pre[i-1][0][1]+pre[i][0][1]+2);
}
for(int i=son[u];i>=1;i--){
tie(pos[i][1][0],pos[i][0][0],pos[i][1][1],pos[i][0][1])=make_tuple(
min(pos[i+1][1][0]+min(pos[i][1][1]+2,pos[i][0][0]+1),pos[i+1][1][1]+min(pos[i][1][0]+1,pos[i][0][0]+1)),
min(pos[i+1][0][0]+pos[i][0][1]+2,pos[i+1][0][1]+pos[i][0][0]+1),
pos[i+1][1][1]+min(pos[i][1][1]+2,pos[i][0][0]+1),
pos[i+1][0][1]+pos[i][0][1]+2);
}
//printf("%lld %lld %lld %lld %lld\n",u,pos[1][1][0],pos[1][1][1],pos[1][0][0],pos[1][0][1]);
ans=min({pos[1][1][0],pos[1][1][1],pos[1][0][0],pos[1][0][1],ans});
for(int te=head[u],v,k=0;te;te=nex[te]){
v=to[te];
++k;
if(v==fa)continue;
dp[u][1][0]=min(pre[k-1][1][0]+pos[k+1][1][1],pre[k-1][1][1]+pos[k+1][1][0]);
dp[u][0][0]=min(pre[k-1][0][0]+pos[k+1][0][1],pre[k-1][0][1]+pos[k+1][0][0]);
dp[u][1][1]=pre[k-1][1][1]+pos[k+1][1][1];
dp[u][0][1]=pre[k-1][0][1]+pos[k+1][0][1];
dfs2(v,u);
}
}
int init(){
n=read();
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
nex[++edge]=head[u];
to[head[u]=edge]=v;
nex[++edge]=head[v];
to[head[v]=edge]=u;
}
dfs(1,0);
dfs2(1,0);
printf("%lld",ans);
return -1;
}
int Score700points = init();
signed main(){
return 0;
}
NOI 2012 迷失游乐园
表面上看是一道期望 dp ,但转移似乎并不复杂。
总的来说 \(\displaystyle EX_u={1\over |son[u]|}\sum_{v\in son[u]}EX_v+1\) (1放里外都一样)
如果只是树,如此换根 dp 即可,但是 \(n=m\) 时是基环树,就需要特殊处理:
- 找环(无向连通图上)
- 对环上每个节点的子树做一次 dfs
- 求环上节点的答案,进而对其子树做第二次 dfs
思路很明晰,但细节处理较复杂
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
int n,m;
int head[100010],to[200010],nex[200010],we[200010],edge=0;
long double dp[100010];
int son[100010];
long double ans=0;
int dic[100010];
int ma[100010];
vector<int> cy;
long double pre[50],pos[50];
int li[50],ri[50];
int cn=0;
void dfst(int u,int fa){
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(v==fa)continue;
dfst(v,u);
son[u]++;
dp[u]+=dp[v]+we[te];
}
if(son[u])dp[u]/=son[u];
// printf("%lld %Lf\n",u,dp[u]);
}
void dfst2(int u,int fa){
long double sum=0;
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
sum+=dp[v]+we[te];
// printf("%lld %lld %Lf %lld\n",u,v,dp[v],we[te]);
}
//printf("%lld %lf %lld %lf\n",u,sum,son[u],sum/son[u]);
ans+=sum/son[u];
cn+=son[u];
// printf("%lld %Lf %lld\n",u,sum,son[u]);
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(v==fa)continue;
if(son[u]>1)dp[u]=(sum-dp[v]-we[te])/(son[u]-1);
else dp[u]=0;
son[v]++;
dfst2(v,u);
}
}
void tree(){
dfst(1,0);
dfst2(1,0);
}
int fc(int u,int fa){
ma[u]=1;
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(v==fa)continue;
if(ma[v]==1){
cy.push_back(u);
return v;
}else if(ma[v]==2){
while(ma[v]==2)cy.insert(++cy.begin(),v);
cy.push_back(u);
return v;
}else{
int tmp=fc(v,u);
if(tmp){
if(tmp!=-1)cy.push_back(u);
if(tmp==u)return -1;
else return tmp;
}
}
}
return 0;
}
void pseudotree(){
cy.emplace_back(0);
fc(1,0);
int cl=cy.size()-1;
for(int i=1;i<=cl;i++)cy.emplace_back(cy[i]),dic[cy[i]]=1;
for(int i=1,u;i<=cl;i++){
u=cy[i];
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
son[u]++;
if(dic[v]){
if(cy[i+cl-1]==v)li[i]=li[i+cl]=we[te];
else ri[i]=ri[i+cl]=we[te];
continue;
}
dfst(v,u);
dp[u]+=dp[v]+we[te];
}
}
for(int i=1,u;i<=cl;i++){
u=cy[i];
pre[i+1]=(son[cy[i+1]]-2)?dp[cy[i+1]]/(son[cy[i+1]]-2):dp[cy[i+1]];
pos[i+cl-1]=(son[cy[i+cl-1]]-2)?dp[cy[i+cl-1]]/(son[cy[i+cl-1]]-2):dp[cy[i+cl-1]];
// printf("%Lf %Lf ",pre[i+1],pos[i+cl-1]);
for(int j=2;j<cl;j++)pre[i+j]=(pre[i+j-1]+dp[cy[i+j]]+li[i+j])/(son[cy[i+j]]-1);
for(int j=cl-2;j>0;j--)pos[i+j]=(pos[i+j+1]+dp[cy[i+j]]+ri[i+j])/(son[cy[i+j]]-1);
ans+=(pre[i+cl-1]+pos[i+1]+dp[u]+li[i]+ri[i])/son[u];
double tmp=dp[u];
cn+=son[u];
printf("%lld %Lf %Lf %lld %Lf %Lf ",u,pre[i+cl-1],pos[i+1],son[u],dp[u],ans);
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(dic[v])continue;
dp[u]=(pre[i+cl-1]+pos[i+1]+li[i]+ri[i]+tmp-dp[v]-we[te])/(son[u]-1);
son[v]++;
dfst2(v,u);
}
dp[u]=tmp;
printf("%Lf\n",ans);
}
printf("%lld\n",cl);
// printf("%lld\n",cn);
if(cn!=2*n)throw runtime_error("dfsd");
}
signed main(){
//freopen("test.in","r",stdin);
//freopen("test.out","w",stdout);
n=read(),m=read();
for(int i=1,u,v,w;i<=m;i++){
u=read(),v=read(),w=read();
nex[++edge]=head[u];
to[head[u]=edge]=v,we[edge]=w;
nex[++edge]=head[v];
to[head[v]=edge]=u,we[edge]=w;
}
if(n==m)pseudotree();
else tree();
printf("%Lf",ans/n);
return 0;
}
「COCI 2020.11」Svjetlo
抽象的朴素树形 dp。
这是一个树上的路径序列。因为是路径,所以可以就端点处理。
不妨设 :
- \(dp[i][j][k]\) 为 \(i\) 子树中使除 \(i\) 外所有节点均打开的最短路径长度,\(j=0/1/2\) 代表 \(i\) 子树中路径 没有端点/有一个端点/有两个端点,\(k=0/1\) 表示经过此路径后 \(i\) 的明灭情况。
- \(sta[i]\)为 \(i\) 初始明灭情况。
那么,对于每个节点 \(i\) ,其初始 \(dp[i][0][sta[i] \oplus 1]=1\) ,其余为 \(+\infin\) 。(在其子树节点均打开的情况下,无需经过此子树,但 \(dp\) 数组描述的是子树被经过的情况,因而不能令 \(dp[i][0][sta[i]]=0\))
基本的转移有:
另:对于 \(dp[v][2][1]\) ,若其余节点均打开,应作为整体的路径,因而需特殊转移。
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
int n;
int head[500010],to[1000010],nex[1000010],edge=0;
bool sta[500010];
int dp[500010][3][2];
int dfs(int u,int fa){
dp[u][0][sta[u]]=2147483647;
dp[u][0][sta[u]^1]=1;
dp[u][1][0]=2147483647;
dp[u][1][1]=2147483647;
dp[u][2][0]=2147483647;
dp[u][2][1]=2147483647;
int mps=2147483647;
for(int te=head[u],v;te;te=nex[te]){
v=to[te];
if(v==fa)continue;
int mp=dfs(v,u);
if(dp[v][0][0]==1)continue;
if(mps<2147483647)mps=0;
tie(dp[u][0][0],dp[u][0][1],dp[u][1][0],dp[u][1][1],dp[u][2][0],dp[u][2][1])=make_tuple(
min({dp[u][0][1]+dp[v][0][1]+1,dp[u][0][0]+dp[v][0][0]+3}),
min({dp[u][0][0]+dp[v][0][1]+1,dp[u][0][1]+dp[v][0][0]+3}),
min({dp[u][0][1]+dp[v][0][0]+2,dp[u][0][0]+dp[v][1][1],dp[u][0][1]+dp[v][1][0]+2,dp[u][1][1]+dp[v][0][1]+1,dp[u][1][0]+dp[v][0][0]+3}),
min({dp[u][0][0]+dp[v][0][0]+2,dp[u][0][1]+dp[v][1][1],dp[u][0][0]+dp[v][1][0]+2,dp[u][1][0]+dp[v][0][1]+1,dp[u][1][1]+dp[v][0][0]+3}),
min({dp[u][0][0]+dp[v][1][0]+1,dp[u][1][0]+dp[v][0][0]+1,dp[u][1][0]+dp[v][1][1],dp[u][1][1]+dp[v][1][0]+2,dp[u][2][1]+dp[v][0][1]+1,dp[u][2][0]+dp[v][0][0]+3,dp[u][0][0]+dp[v][2][0]+1,dp[u][0][1]+dp[v][2][1]+3}),
min({dp[u][0][1]+dp[v][1][0]+1,dp[u][1][1]+dp[v][0][0]+1,dp[u][1][1]+dp[v][1][1],dp[u][1][0]+dp[v][1][0]+2,dp[u][2][0]+dp[v][0][1]+1,dp[u][2][1]+dp[v][0][0]+3,dp[u][0][1]+dp[v][2][0]+1,dp[u][0][0]+dp[v][2][1]+3}));
if(mp&&mps)mps=mp;
}
dp[u][1][0]=min(dp[u][1][0],dp[u][0][0]);
dp[u][1][1]=min(dp[u][1][1],dp[u][0][1]);
dp[u][2][0]=min(dp[u][2][0],dp[u][1][0]);
dp[u][2][1]=min(dp[u][2][1],dp[u][1][1]);
// printf("%lld %lld %lld %lld %lld %lld %lld %lld\n",u,dp[u][0][0],dp[u][0][1],dp[u][1][0],dp[u][1][1],dp[u][2][0],dp[u][2][1],mps);
if(mps&&sta[u])return mps;
else return dp[u][2][1];
}
signed main(){
//freopen("test.in","r",stdin);
//freopen("test.out","w",stdout);
n=read();getchar();
for(int i=1;i<=n;i++)sta[i]=(getchar()=='1');
for(int i=1,u,v;i<n;i++){
u=read(),v=read();
nex[++edge]=head[u];
to[head[u]=edge]=v;
nex[++edge]=head[v];
to[head[v]=edge]=u;
}
int mp=dfs(1,0);
printf("%lld",min({dp[1][0][1],dp[1][1][1],dp[1][2][1],mp}));
return 0;
}
浙公网安备 33010602011771号