• 【BZOJ】2286: [Sdoi2011消耗战


    http://www.lydsy.com/JudgeOnline/problem.php?id=2286

    题意:n个点的边加权树,m个询问,每次询问给出的k个点与结点1分离的最小代价。(n<=250000, sum{ki}<=500000)

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=250005, oo=0x7f7f7f7f;
    int dep[N], FF[N], f[N][21], mn[N][21], n, m, a[N], s[N], top, tot, nd[N];
    struct Gr {
    	int ihead[N], cnt;
    	struct E { int next, to, w; }e[N<<1];
    	void add(int x, int y, int c=0) {
    		e[++cnt]=(E){ihead[x], y, c}; ihead[x]=cnt;
    		e[++cnt]=(E){ihead[y], x, c}; ihead[y]=cnt;
    	}
    	void dfs(int x) {
    		FF[x]=++tot;
    		for(int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1], mn[x][i]=min(mn[x][i-1], mn[f[x][i-1]][i-1]);
    		for(int i=ihead[x]; i; i=e[i].next) if(e[i].to!=f[x][0]) {
    			f[e[i].to][0]=x; dep[e[i].to]=dep[x]+1; mn[e[i].to][0]=e[i].w;
    			dfs(e[i].to);
    		}
    	}
    	int LCA(int x, int y) {
    		if(dep[x]<dep[y]) swap(x, y);
    		int d=dep[x]-dep[y];
    		for(int i=20; i>=0; --i) if((d>>i)&1) x=f[x][i];
    		if(x==y) return x;
    		for(int i=20; i>=0; --i) if(f[x][i]!=f[y][i]) x=f[x][i], y=f[y][i];
    		return f[x][0];
    	}
    	int dist(int x, int y) {
    		int ret=oo;
    		if(dep[x]<dep[y]) swap(x, y);
    		int d=dep[x]-dep[y];
    		for(int i=20; i>=0; --i) if((d>>i)&1) ret=min(ret, mn[x][i]), x=f[x][i];
    		return ret;
    	}
    	ll dp(int x, int fa=0) {
    		ll ret=0;
    		for(int i=ihead[x]; i; i=e[i].next) if(e[i].to!=fa) 
    			ret+=min(nd[e[i].to]?(ll)oo:dp(e[i].to, x), (ll)dist(x, e[i].to));
    		return ret;
    	}
    	void clr(int x, int fa=0)  { for(int i=ihead[x]; i; i=e[i].next) if(e[i].to!=fa) clr(e[i].to, x); ihead[x]=0; }
    }g, G;
    bool cmp(const int &a, const int &b) { return FF[a]<FF[b]; }
    int main() {
    	scanf("%d", &n);
    	for(int i=0; i<n-1; ++i) { int x, y, c; scanf("%d%d%d", &x, &y, &c); G.add(x, y, c); }
    	memset(mn, 0x7f, sizeof mn);
    	G.dfs(1);
    	int T; scanf("%d", &T);
    	while(T--) {
    		scanf("%d", &m);
    		for(int i=0; i<m; ++i) scanf("%d", &a[i]), nd[a[i]]=1;
    		sort(a, a+m, cmp);
    		s[top=1]=1;	g.clr(1); g.cnt=0;
    		for(int i=0; i<m; ++i) {
    			int x=a[i], lca=G.LCA(x, s[top]);
    			while(FF[lca]<FF[s[top]]) {
    				if(FF[lca]>=FF[s[top-1]]) {
    					g.add(lca, s[top--]);
    					if(lca!=s[top]) s[++top]=lca;
    					break;
    				}
    				g.add(s[top], s[top-1]); --top;
    			}
    			if(s[top]!=x) s[++top]=x;
    		}
    		while(--top) g.add(s[top], s[top+1]);
    		printf("%lld
    ", g.dp(1));
    		for(int i=0; i<m; ++i) nd[a[i]]=0;
    	}
    	return 0;
    }
    

      

    学习了下虚树= =(为何啥玩意都喜欢加上一个名词?其实虚树 = dfs序 + lca + 单调栈

    首先容易想到树dp:$f(x) = sum_{y是孩子} min(y点必需割?w(x, y):f(y), w(x, y))$

    可是复杂度$O(nm)$无法承受...

    发现每一次计算很多点是不需要计算的= =比如说两个点之间的链= =用倍增就能得到链的最小值辣...

    于是将图重建一个包括x个点以及他们两两之间的lca的树。【可是这样最坏也是n个点都被加入了啊QAQ比如完全二叉树= =...感觉能分分钟卡啊....】(博主纯属sb,泥忘记了有sum ki<=500000的条件么= =

    方法就是先求出dfs序然后对于每次询问先对点进行dfs序排序,然后再维护一个dfs序递增的一条链(1点到x点的链)的栈,顺序加入每个点,维护以下性质(包括得到的性质):

    1、对任意$i>j$,$dfn(s[i]) > dfn(s[j])$,而排序后可知任意$x$都有$dfn(x)>dfn(s[top])$

    2、$f = lca(x, s[top])$在$s[1]~s[top]$中

    3、当$f eq s[top]$时,$f$到$s[top]$之间的点以及$s[top]$的子树都不再对要加入的$lca$有贡献了,因为可以用$f$替代。由前两点性质可证。

    所以具体算法就是先按dfs序排序给出的节点$a$,然后依次加入每个点。令$f = lca(s[top], a[now])$,如果$f$在$s[top]$和$s[top-1]$之间,那么$f$和$s[top]$连边,删掉$s[top]$。否则一直删栈顶且连边,直到刚刚满足$f$在$s[top]$和$s[top-1]$之间。最后再加入节点$a[now]$即可。

    然后注意最后树dp的时候不要忘记是新图啊!不是原来的father啊!一开始没判re了好多次啊!

    (还有写完代码发现和别人的代码相似度为99%是什么鬼啊!

    最坏复杂度$O(nm)$,但是数据你懂的= =(所以说虚树属于卡常啊!(博主纯属sb

  • 相关阅读:
    Java练习 标准输入,输出,以及switch判断
    Java练习 标准输入,输出,以及if else判断
    Java 语句和流程控制
    Java的运算符,Java的表达式
    理解 Linux 的硬链接与软链接(转)
    第一范式、第二范式、第三范式详解(转自知乎)
    TCP/IP协议图解
    POSIX条件变量
    自旋锁与读写锁
    POSIX信号量与互斥锁实现生产者消费者模型
  • 原文地址:https://www.cnblogs.com/iwtwiioi/p/4368652.html
Copyright © 2020-2023  润新知