• 51nod1673 树有几多愁


    传送门

    题目大意:

    给一颗重新编号,叶子节点的值定义为他到根节点编号的最小值,求所有叶子节点值的乘积的最大值。

    题目分析:

    为什么我觉得这道题最难的是贪心啊。。首先要想到

    1. 在一条链上,深度大的编号要小于深度小的编号(保证它影响的节点是最小的)
    2. 有了1过后,一颗子树的编号应该是以叶子节点为最小的连续整数,也就是说必须对一个节点的所有子树编完号才能对该节点编号。

    这两点我已经想了很久了,接下来还有难关:
    知道了叶子节点编号要最小,但是叶子节点的编号顺序会对答案产生巨大影响。注意到题目中保证叶子节点数(le 20),-------->状压,用(f[i])表示染完i这个状态的叶子所得到的最优答案(取模),由于中途转移时会产生巨大的中间量,为了避免使用高精度,再新建一个(g[i])表示最优答案(未取模)。这样当枚举到某一状态时,计算出下一个叶子节点的编号应该是多少,并进行转移。

    还没完,计算下一个叶子节点的编号需要对树进行一次遍历,由于n巨大,如果对原树进行遍历的话,总时间复杂度会达到(O(2^{20}*n)),这就远超出了范围。有前面得知一条链上的编号是连续的,那么就是说只要知道链的长度就可以知道编号的增量,也就是说我们只用保留叶节点、根节点、包含多颗子树的节点这些关键点,这不就是颗虚树吗?
    建完虚树后,进行如上转移,即可得到答案。

    code

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 100050
    #define limit (1<<21)
    const int mod = 1e9 + 7;
    namespace IO{
    	inline int read(){
    		int i = 0, f = 1; char ch = getchar();
    		for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
    		if(ch == '-') f = -1, ch = getchar();
    		for(; ch >= '0' && ch <= '9'; ch = getchar()) i = (i << 3) + (i << 1) + (ch - '0');
    		return i * f;
    	}
    	inline void wr(int x){
    		if(x < 0) x = -x, putchar('-');
    		if(x > 9) wr(x / 10);
    		putchar(x % 10 + '0');
    	} 
    }using namespace IO;
    
    int n;
    int ecnt, adj[maxn], go[maxn*2], nxt[maxn*2], fa[maxn][25];
    int dep[maxn], sze[maxn], num[maxn], leaf[maxn], tot;
    int vir[maxn], virCnt, vecnt, vadj[maxn], vgo[maxn], vnxt[maxn], par[maxn], rt;
    int dfn[maxn], clk, sum;
    int f[limit];
    double g[limit];
    
    inline void addEdge(int u, int v){
    	nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v;
    }
    
    inline void addvEdge(int u, int v){
    	vnxt[++vecnt] = vadj[u], vadj[u] = vecnt, vgo[vecnt] = v;
    }
    
    inline void pre(int u, int f){
    //	cout<<u<<"->";
    	dep[u] = dep[f] + 1;
    	dfn[u] = ++clk;
    	fa[u][0] = f;
    	sze[u] = 1;
    	for(int i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
    	int cnt = 0;
    	for(int e = adj[u]; e; e = nxt[e]){
    		int v = go[e];
    		if(v == f) continue;
    		pre(v, u);
    		sze[u] += sze[v];
    		cnt++;
    	}
    	if(u == 1) return;
    	if(cnt == 0) leaf[tot++] = u, vir[++virCnt] = u;
    	else if(cnt >= 2) vir[++virCnt] = u;
    }
    
    inline int getLca(int u, int v){
    	if(dep[u] < dep[v]) swap(u, v);
    	int delta = dep[u] - dep[v];
    	for(int i = 20; i >= 0; i--) if(delta & (1 << i)) u = fa[u][i];
    	if(u == v) return u;
    	for(int i = 20; i >= 0; i--)
    		if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    	return fa[u][0];
    }
    
    inline bool cmp(int u, int v){
    	return dfn[u] < dfn[v];
    }
    
    inline int buildVir(){
    	static int stk[maxn], top;
    	top = 0;
    	sort(vir + 1, vir + virCnt + 1, cmp);
    	int oriSze = virCnt;
    	for(int i = 1; i <= oriSze; i++){
    		int u = vir[i];
    		if(!top){
    			stk[++top] = u;
    			par[u] = 0;
    			continue;
    		}
    		int lca = getLca(u, stk[top]);
    //		cout<<u<<" "<<stk[top]<<" "<<lca<<endl;
    		while(dep[lca] < dep[stk[top]]){
    			if(dep[stk[top - 1]] < dep[lca]) par[stk[top]] = lca;
    			top--;
    		}
    		if(lca != stk[top]){
    			vir[++virCnt] = lca;
    			stk[++top] = lca;
    			par[lca] = stk[top];
    		}
    		par[u] = lca;
    		stk[++top] = u;
    	}
    	sort(vir + 1, vir + virCnt + 1, cmp);
    	for(int i = 1; i <= virCnt; i++)
    		if(par[vir[i]]) addvEdge(par[vir[i]], vir[i]);
    	return vir[1];
    }
    
    inline void number(int u){
    	if(!vadj[u]){
    		sum += num[u];
    		return;
    	}
    	num[u] = 0;
    	for(int e = vadj[u]; e; e = vnxt[e]){
    		int v = vgo[e];
    		number(v);
    		if(num[v] == sze[v]){  //该子树已经全部染完 
    			num[u] += num[v] + dep[v] - dep[u] - 1;
    			sum += dep[v] - dep[u] - 1;  //更新已经染到的编号 
    		}
    	}
    	if(num[u] == sze[u] - 1){   //该根的子树已经全部染完 
    		num[u]++, sum++;
    	}
    }
    
    inline void print(int t){
    	cout<<t<<"->";
    	for(int e = vadj[t]; e; e = nxt[e]){
    		int v = vgo[e];
    		print(v);
    	}
    }
    
    int main(){
    	n = read();
    	for(int i = 1; i < n; i++){
    		int x = read(), y = read();
    		addEdge(x, y), addEdge(y, x);
    	}
    	vir[virCnt = 1] = 1;
    	pre(1, 0);
    	rt = buildVir();
    	for(int i = 0; i < tot; i++) f[1 << i] = 1, g[1 << i] = 1.0;
    	int limi = (1 << tot);
    	for(int i = 1; i < limi; i++){
    		for(int j = 0; j < tot; j++){
    			if(i & (1 << j)) num[leaf[j]] = 1;
    			else num[leaf[j]] = 0;
    		}
    		sum = 0;
    		number(rt);  //得到下一个叶子节点的编号 
    		double ret = g[i] * (sum + 1);    //用double来做中间的比较,避免高精度 
    		int ans = 1ll * f[i] * (sum + 1) % mod;
    		for(int j = 0; j < tot; j++){
    			if(((i & (1 << j)) == 0) && g[i | (1 << j)] < ret){
    				g[i | (1 << j)] = ret;
    				f[i | (1 << j)] = ans;
    			}
    		}
    	}
    	wr(f[limi - 1]);
    	return 0;
    }
    
  • 相关阅读:
    有关token
    JQuery自定义resize事件代码解析
    vue--为网页添加动态响应背景
    如何查询数据库中所有表格,或者查询是否存在某个表格-mysql和oracle
    Foxmail: 错误信息::ssl连接错误, errorCode: 5,各种解决方案的大杂烩。
    [转] Hadoop入门系列(一)Window环境下搭建hadoop和hdfs的基本操作
    java连数据库和数据库连接池踩坑日记(二)-------数据库连接池c3p0
    java实现将汉字转为拼音
    Sublime 如何修改默认编码格式
    JSP 表单处理
  • 原文地址:https://www.cnblogs.com/CzYoL/p/7718528.html
Copyright © 2020-2023  润新知