zoukankan      html  css  js  c++  java
  • P4649

    好神仙的 IOI 题。。。这个不仅往儿子转移,而往更深层的后代转移的树形 DP 就很 nb。

    首先注意到,破坏树上环显然只能破坏非树边。那么对所有偶树上环都直接要把非树边破坏掉,剩下来一些奇树上环。如果奇树上环有交,那么显然能叠成偶环;否则显然无偶环。所以我们就是要删掉一些奇树上环使剩下的所有环不交,并且最大化剩下的权值和。

    最大权独立集?先不说二分图最大权独立集能不能做,首先这就不是二分图,随便三个互相重叠的环就废了。所以我们需要利用树结构的特殊性 DP 来搞。

    考虑在子树 (i)​ 内决策 LCA 等于 (i)​​ 的环然后转移下去。如果加入一条链的话,我们将这条链上的边全删掉,剩下来若干个连通块,除了一个其它每个连通块都是某子树删或不删某个儿子树的状态,特例是 (i)​ 所在连通块,是子树 (i)​ 删两个儿子树。它们显然是独立的(因不连通),所以可以分别求然后加起来。那么我们要记录再 (j,k)​ 分别表示 (i)​ 删除的儿子树吗?如果那样,那对于每个状态还可能再删,删到四个,永无止境地迭代下去。但注意到 (n,m,2^{deg})​ 这三者都是 1e3 级别,两两相乘加起来不会炸。(deg)​ 就是儿子树的个数,那么我们可以对删掉的儿子树集合进行状压,这样就可以了。

    转移的话分两种,一种是不取任何 LCA 等于 (i)​ 的环,那么就往 mask 里的儿子转移。另一种就取一个转移到自己的不同 mask 以及后代们。这样复杂度对每个环处理的时间是 (mathrm O(n))​,总复杂度是 (mathrm O!left(nm2^{deg} ight))​,爆炸。不过我们可以在枚举 (2^{deg})​​ 外面把每个环的代价预处理好,这样就把 (2^{deg})(m) 平行化掉了。

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define pb push_back
    const int N=5010,LOG_N=15;
    int n,m;
    vector<int> nei[N];
    int cx[N],cy[N],cv[N],ca[N];
    int fa[N][LOG_N],dep[N];
    int id[N/5][N/5];
    void dfs(int x=1){
    	for(int i=1;i<LOG_N;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    	int now=0;
    	for(int i=0;i<nei[x].size();i++){
    		int y=nei[x][i];
    		if(y==fa[x][0])continue;
    		id[x][y]=now++;
    		fa[y][0]=x;
    		dep[y]=dep[x]+1;
    		dfs(y);
    	}
    }
    int lca(int x,int y){
    	if(dep[x]<dep[y])swap(x,y);
    	for(int i=LOG_N-1;~i;i--)if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    	if(x==y)return x;
    	for(int i=LOG_N-1;~i;i--)if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    	return fa[x][0];
    }
    int dp[N/5][1<<10];
    vector<int> vv[N];
    int add[N],msk[N];
    void dfs0(int x=1){
    	for(int i=0;i<nei[x].size();i++){
    		int y=nei[x][i];
    		if(y==fa[x][0])continue;
    		dfs0(y);
    	}
    	for(int i=0;i<vv[x].size();i++){
    		int y=vv[x][i],u=cx[y],v=cy[y];
    		add[y]=cv[y];
    		if(u!=x){
    			add[y]+=dp[u][0];
    			while(fa[u][0]!=x)add[y]+=dp[fa[u][0]][1<<id[fa[u][0]][u]],u=fa[u][0];
    			msk[y]|=1<<id[x][u];
    		}
    		if(v!=x){
    			u=v;
    			add[y]+=dp[u][0];
    			while(fa[u][0]!=x)add[y]+=dp[fa[u][0]][1<<id[fa[u][0]][u]],u=fa[u][0];
    			msk[y]|=1<<id[x][u];
    		}
    	}
    	for(int i=(1<<nei[x].size()-(x!=1))-1;~i;i--){
    		for(int j=0;j<nei[x].size();j++){
    			int y=nei[x][j];
    			if(y==fa[x][0])continue;
    			if(!(i>>id[x][y]&1))dp[x][i]+=dp[y][0];
    		}
    		for(int j=0;j<vv[x].size();j++){
    			int y=vv[x][j];
    			if(!(msk[y]&i))dp[x][i]=max(dp[x][i],add[y]+dp[x][i|msk[y]]);
    		}
    	}
    }
    int main(){dep[1]=1;
    	cin>>n>>m;
    	int now=0;
    	for(int i=1;i<=m;i++){
    		int x,y,z;
    		scanf("%d%d%d",&x,&y,&z);
    		if(!z)nei[x].pb(y),nei[y].pb(x);
    		else cx[++now]=x,cy[now]=y,cv[now]=z;
    	}
    	m=now;
    	dfs();
    	int ans=0;
    	for(int i=1;i<=m;i++)ca[i]=lca(cx[i],cy[i]),ans+=cv[i],dep[cx[i]]%2==dep[cy[i]]%2&&(vv[ca[i]].pb(i),0);
    	dfs0();
    	cout<<ans-dp[1][0];
    	return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    C++格式化输入输出
    算法的时间复杂度和空间复杂度
    C++编程中const和#define的区别
    C#中结构体和类的区别
    SQL之删除触发器
    Windows添加和取消右键管理员权限
    SQL之trigger(触发器)
    SQL VIEW(视图)
    二分查找的实现
    C++中this指针
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p4649.html
Copyright © 2011-2022 走看看