zoukankan      html  css  js  c++  java
  • 「HDU6566」The Hanged Man

    题意

    (n) 个点的树 每个点有一个体积 (v_i) 和收益 (w_i)

    只能选择不相邻的物品 要求你输出 (forall i in [1,m]),容量为 (i) 的背包收益最大的方案数

    (n leq 50,m leq 5000)

    题解

    很 naive 的 dp 大概是 $f[i][j][0/1] $ 表示处理 (i) 号点子树 已经用了 (j) 容量,是否选择这个点的方案数 转移的时候搞个树形背包 复杂度 (O(nm^2))

    官方题解看起来很强大。。。

    我们考虑沿用树形背包的一贯做法:按照 dfs 序来 dp。发现在 dp 过程中 一个点能不能选只取决于它的 father 是否选了 但是如果只记录 father 的值的话根本转移不了((i o i+1)的时候你不知道dfs序是 (i+1) 的点上面的情况)、暴力的记录整个树复杂度就爆炸了,一个稍微好一点的想法是注意到 dfn 相邻的点的移动轨迹一定是往上跳若干步然后往下走一步(这个性质很有用),所以我们只需要记录这个点到根的路径上是否被选择就好了。

    但是这样显然还是会被链卡没。

    官方题解告诉我们先进行轻重链剖分 然后划定 dfs 序的时候最后遍历重儿子 这样你在 (i o i+1) 的时候会直接跳过上面的一段重链,所以我们发现对于每个点只需要记录每个重链的链底就好了 根据轻重链剖分的性质 这个数量就是一个点到根轻边的数量 是 (O(log n)) 的。

    于是设 (f[i][S][j]) 表示考虑到了 dfn 为 (i) 的点 当前到根的链底的点的状态是 (S) ,已经用了容量 (j)(S)的状态数是 (O(2^{log n}) = O(n)) 的,转移可以写成 (O(1)) 所以复杂度变成了 (O(n^2m))

    转移不会写 看了个代码 于是决定加一加注释加深理解

    Code:

    #include<bits/stdc++.h>
    
    #define fi first
    #define se second
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 100+5;
    const int MAXM = 5000+5;
    
    int n,m;
    int a[MAXN],b[MAXN];// 重量 收益
    
    struct Edge{
    	int to,nxt;
    }e[MAXN<<1];
    int head[MAXN],cnt;
    
    inline void add(int u,int v){
    	e[++cnt] = (Edge){v,head[u]};head[u] = cnt;
    	e[++cnt] = (Edge){u,head[v]};head[v] = cnt;
    }
    
    int sz[MAXN],son[MAXN];
    
    inline void dfs1(int v,int fa=0){
    	sz[v] = 1;son[v] = 0;
    	for(int i = head[v];i;i = e[i].nxt){
    		if(e[i].to == fa) continue;
    		dfs1(e[i].to,v);sz[v] += sz[e[i].to];
    		if(sz[son[v]] < sz[e[i].to]) son[v] = e[i].to;
    	}
    }
    
    int ts;
    int dfn[MAXN],nfd[MAXN],tp[MAXN],fa[MAXN];
    
    inline void dfs2(int v,int ntp,int fa=0){
    	dfn[v] = ++ts;nfd[dfn[v]] = v;tp[v] = ntp;
    	::fa[v] = fa;
    	for(int i = head[v];i;i = e[i].nxt){
    		if(e[i].to != fa && e[i].to != son[v]){
    			dfs2(e[i].to,e[i].to,v);
    		}
    	}
    	if(son[v]) dfs2(son[v],ntp,v);
    }
    
    struct Node{
    	int f;LL g;
    	Node(int f=0,LL g=0) : f(f),g(g) {}
    }f[2][MAXN][MAXM],ans[MAXM];
    
    inline void update(Node &x,const Node &y){
    	if(x.f > y.f) return;
    	if(x.f < y.f) x = y;
    	else x.g += y.g;
    }
    
    int now;
    std::vector<int> cha[MAXN];
    int t1[MAXN];
    
    inline void Solve(){
    	scanf("%d%d",&n,&m);
    	FOR(i,1,n) scanf("%d%d",a+i,b+i);
    	FOR(i,2,n){
    		int u,v;scanf("%d%d",&u,&v);
    		add(u,v);
    	}
    	dfs1(1);dfs2(1,1);
    	FOR(i,1,n){
    		cha[i].clear();
    		int t = i;
    		while(t) cha[i].pb(t),t = fa[tp[t]];
    	}
    	CLR(f,0);
    	f[now][0][0] = Node(0,1);
    	FOR(i,1,n){
    		int t = 0;
    		int fat = -1;
    		FOR(S,0,(1<<cha[nfd[i]].size())-1) FOR(k,0,m) f[now^1][S][k] = Node(0,0);
    		for(auto x:cha[nfd[i-1]]){
    			if(x == fa[nfd[i]]) fat = t;
    			t1[t] = -1;
    			FOR(j,0,(int)cha[nfd[i]].size()-1){
    				if(cha[nfd[i]][j] == x){
    					t1[t] = j;
    					break;
    				}
    			}
    			++t;
    		}
            // 转移时主要目的是得到 S 的后继状态 nxt 
            // 首先处理出任意一个点需要记录的点的集合 记做 cha[i] 那么cha[i-1]和cha[i]的交的状态就一定确定好了 现在就是确定当前点的状态和 father 的状态(判断是否转移)
            // 于是 t1[i] 存 nfd[i-1] 的第 i 位是 nfd[i] 的哪一位(或者不存在)转移即可
    		FOR(S,0,(1<<cha[nfd[i-1]].size())-1){
    			int nxt = 0;
    			FOR(j,0,(int)cha[nfd[i-1]].size()-1)
    				if((S>>j)&1 && t1[j] != -1) nxt |= (1<<t1[j]);
    			FOR(k,0,m){
    				if(f[now][S][k].g){
    					update(f[now^1][nxt][k],f[now][S][k]);
    					if(!((S>>fat)&1) && k+a[nfd[i]] <= m){
    						update(f[now^1][nxt|1][k+a[nfd[i]]],Node(f[now][S][k].f+b[nfd[i]],f[now][S][k].g));
    					}
    				}
    			}
    		}
    		now ^= 1;
    	}
    	FOR(i,0,m) ans[i] = Node(0,0);
    	FOR(S,0,(1<<cha[nfd[n]].size())-1){
    		FOR(i,0,m){
    			update(ans[i],f[now][S][i]);
    		}
    	}
    	FOR(i,1,m) printf("%lld%c",ans[i].g," 
    "[i==m]);
    	FOR(i,0,n) head[i] = dfn[i] = nfd[i] = 0;cnt = 0;ts = 0;
    }
    
    int main(){
    	int T;scanf("%d",&T);
    	FOR(i,1,T) printf("Case %d:
    ",i),Solve();
    	return 0;
    }
    

    More Things

    我看到知乎上出题人说希望这个题成为以后题目的一种套路。。

    这类套路大概就是将树 dp 转移到 dfn 上去做 如果只和父亲有关的话就可以用这个优化

    所以以后做树 dp 的时候还是要考虑 dfn

  • 相关阅读:
    投影机的选择
    IOS 检测摇动
    MARK 我的第一个app完成
    2013年会抽奖程序
    给phpcms v9增加类似于phpcms 2008中的关键词表
    codeigniter 用.htcaccess做动态二级域名解析
    获取优酷、土豆、搜狐视频 HTML5视频m3u8格式源
    发现一个好的文学素材类网站
    mac MAMP下安装mongdb & RockMongo
    给artDialog 5.02 增加ajax get功能
  • 原文地址:https://www.cnblogs.com/rainair/p/14471622.html
Copyright © 2011-2022 走看看