与普通dp类似,但是状态遍历不能简单的++i,而应该按由儿子到父亲的顺序完成。可以通过DFS递归完成,也可以按DFS(BFS)序从大到小完成,以保证先完成儿子再完成父亲。
例题一 皇宫看守 tyvj_3384/vijos_1144
每个节点有三个状态
- f[i][0]代表i子树全都被染色的代价,由每个儿子的min(0,2)转移而来,且有一个儿子必须为2;
- f[i][1]代表i子树(不包括i)全部被染色的代价,由每个儿子的min(0,2)转移而来;
- f[i][2]代表i子树全部被染色,且i处有守卫 的代价,由每个儿子的min(0,1,2)+cost[i]转移而来。
上代码
//tyvj 3384 皇宫看守 vijos1144
//by cyd
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define N 1510
#define min(x,b) (x<b?x:b) //要加括号的
#define min2(a,b,c) min(min(a,b),c)
using namespace std;
struct branch{
int sn,ne;
}t[N]; //邻接表
int n,head[N],w[N],tmp,tmp2,cnt,bfn[N],num[N];
long long f[N][3];
void addson(int fa,int son){
cnt++;
bfn[son]=1;
t[cnt].sn=son;
t[cnt].ne=head[fa];
head[fa]=cnt;
return ;
}
void bfs(){ //求bfs序 bfn先用来判断哪个点是根节点 之后bfn[i]=j表示_bfs序为i的节点是j
cnt=0;
queue<int >q;
for(int i=1;i<=n;++i){
if(!bfn[i]){
q.push(i);
break;
}
}
memset(bfn,0,sizeof bfn);
while(!q.empty()){
int u=q.front();
q.pop();
bfn[++cnt]=u;
for(int i=head[u];i;i=t[i].ne){
q.push(t[i].sn);
}
}
return ;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i){
scanf("%d",&tmp);
scanf("%d",&w[tmp]);
scanf("%d",&tmp2);
num[tmp]=tmp2;
while(tmp2--){
int a;
scanf("%d",&a);
addson(tmp,a);
}
}
bfs();
for(int i=n;i;--i){ //按bfs序从大到小 保证儿子在父亲之前被访问
int u=bfn[i];
long long minn=2100000000,flag=0;
for(int j=head[u];j;j=t[j].ne){
int v=t[j].sn;
f[u][2]+=min2(f[v][0],f[v][1],f[v][2]);
f[u][1]+=min(f[v][0],f[v][2]);
f[u][0]+=min(f[v][0],f[v][2]);
if(f[v][2]<=f[v][0])flag=1;
else minn=min(minn,f[v][2]-f[v][0]); //这里很重要 因为状态0有一个儿子必须为2,我们先使所有儿子状态为0,找出“状态0与状态2代价差最小的儿子”, 加上那个差
}
f[u][2]+=w[u];
if(!flag)
f[u][0]+=minn;
if(!num[u])f[u][2]=f[u][0]=w[u]; //如果这个节点是叶子节点 他的0和2代价都为w[u]
}
cout<< min(f[bfn[1]][0],f[bfn[1]][2]);
}
下一题!
IOI 2005 河流 (bzoj 1812)
先附上我当时看的题解
题解
f[i][j][k]表示当前为i节点 ,i的下游(不包括i)里i最近的伐木场是j,当前子树有k个伐木场。
感觉真的很玄学,不知道神犇们怎么想出来的。
直接上代码吧
//ioi 2005 河流
//bzoj 1812
//by cyd
#include<cstdio>
#include<iostream>
#include<cstring>
#define min(a,b) (a<b?a:b)
using namespace std;
struct tree{
int l,r,fa;
}t[110];
int n,k,w[110],v[110],d[110],cnt=-1,dfn[110],dis[110];
int f[110][110][60];
void dfs(int k,int now){
dfn[++cnt]=k;
now+=d[k];
dis[k]=now;
for(int i=t[k].l;i;i=t[i].r){
dfs(i,now);
}
return ;
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;++i){
scanf("%d%d%d",&w[i],&v[i],&d[i]);
t[i].r=t[v[i]].l;
t[v[i]].l=i;
t[i].fa=v[i]; //将多叉树转化为二叉树方便遍历第三维 左孩子右兄弟
}
dfs(0,0);
memset(f,0x7f,sizeof f);
memset(f[0],0,sizeof f[0]); //这个0其实不是根节点 是叶子节点的下一层 也会访问到0
t[0].fa=-1; //作为终止点
for(int i=n;i;--i){
int p=dfn[i];
//cout<<p<<endl;
for(int j=t[p].fa;j!=-1;j=t[j].fa){ //第二维 下游离i最近(不包括i)的伐木场位置
for(int k1=0;k1<=k;++k1){ //暴力i子树伐木场数量
//如果p点是伐木场
for(int l=0;l<k1;++l){
if(f[ t[p].l ][p][l]!=0x7f7f7f7f&&f[t[p].r][j][k1-l-1]!=0x7f7f7f7f)
f[p][j][k1]=min(f[p][j][k1],f[ t[p].l ][p][l]+f[t[p].r][j][k1-l-1] );
}
//如果p点不是伐木场
for(int l=0;l<=k1;++l){
if(f[ t[p].l ][j][l]!=0x7f7f7f7f&&f[t[p].r][j][k1-l]!=0x7f7f7f7f)
f[p][j][k1]=min(f[p][j][k1],f[ t[p].l ][j][l]+f[t[p].r][j][k1-l]+w[p]*(dis[p]-dis[j]) );
//将一个节点的孩子和兄弟都遍历完后才会访问到他,
//也就是说 他是原来那棵子树中最左上方的节点
}
}
}
}
cout<<f[t[0].l][0][k];
}
应该自己按照样例画一颗河流树更方便理解,因为加图太麻烦,我就不画了。
——下一道!
HAOI 2015 树上染色 (bzoj 4033)
要算贡献的啊,f[i][j]代表子树i中有j个黑点时对答案的最大贡献,最终答案就是f[root][k]
上代码
//haoi 2015 树上染色
//bzoj 4033
//by cyd
#include<cstdio>
#include<iostream>
#define N 2010
#define ll long long
#define max(a,b) (a>b?a:b)
using namespace std;
int n,K,size[N],head[N],cnt;
bool vis[N];
ll f[N][N];
struct edge{
int to,next;
ll w;
}e[2*N];
void add(int a,int b,ll w){
cnt++;
e[cnt].w=w;
e[cnt].to=b;
e[cnt].next=head[a];
head[a]=cnt;
return ;
}
void dfs(int k){
vis[k]=1;
size[k]=1;
for(int i=head[k];i;i=e[i].next){
int t=e[i].to;
int w=e[i].w;
if(vis[t])continue; //防止重复访问(无向图)
dfs(t);
for(int j=size[k];j>=0;--j){// 跟背包类似 防止重复使用
for(int l=size[t];l>=0;--l){//因为j+0=j会影响后来的答案 所以把0放在最后
if(j+l>K)continue;//小剪枝
f[k][j+l]=max(f[k][j+l],f[k][j]+f[t][l]+(ll)w*l*(K-l) +(ll)w*(size[t]-l)*(n-K-(size[t]-l))); //要强转longlong ,不然会少20分
//f[k][j+l]的贡献等于 大树f[k][j]的贡献加上小树f[t][l]的贡献再加上小树并进大树那条边的贡献(贡献等于权值*访问次数)
}
}
size[k]+=size[t];// 访问后再加
}
return ;
}
int main(){
scanf("%d%d",&n,&K);
for(int i=1;i<n;++i){
int a,b;
ll w;
scanf("%d%d%lld",&a,&b,&w);
add(a,b,w);
add(b,a,w);
}
dfs(n);
cout<<f[n][K];
}
The End.