[JZOJ5939]【NOIP2018模拟10.30】阻击计划

29 篇文章 0 订阅
29 篇文章 0 订阅

Description

小R和小Z打算在这个周末一起骑车在G国的城市看风景,G国的城市有n个城市,m条双向道路,这m条边中,有n-1条道路已经铺设完毕,任意两个城市之间都有一条由铺设好的道路组成的路径。
由于G国经常收到周围强大力场的影响,**G国的每个城市至多是十条道路的端点(**包括铺设好和未铺设好的道路)。
小R和小Z制订了这样一个Van耍计划:从一个城市开始,沿着G国的道路骑行,途中不经过之前已经去过的城市,也不经过之前去过的道路,最后回到起点城市。
由于他们骑得是双人自行车,前排的座位比后排的作为更累,他们决定每次到达一个城市都会换一次位置,为了保证每个人的体力消耗相同,继续进行他们下面的游戏,他们需要一条恰好有偶数条道路的路径。
为了阻止他们,小J决定破坏一些没有被铺设好的道路,由于自身能力不足,他找到了你,并将自己一周的研究数据——破坏每条未被铺设好的道路的花费告诉了你,希望你帮他算出他至少需要花费多少代价才能阻止小R和小Z的计划。
对于所有数据 n≤1000,m≤5000,每条道路的花费≤10000

Solution

题目大意:给定一棵n个节点m条边的无向图,每条边有一个删除所需要的花费,这m条边中有n-1条是不能删除的,并且它们构成一个生成树。
求最少的花费删掉一些非树边使得原图不存在边数为偶数的环。

我们在这棵生成树上考虑

假设对于一条非树边,它本身+树边构成了一个偶环,那显然它是要直接删掉的,直接累加。

如果它+树边构成了一个奇环,我们考虑它们什么时候会变成偶环。
我们不妨把这条非树边对应成两个端点在树上的路径,可以发现,如果任意两条这样的路径出现了重叠边(重叠点是可以的),那么就会出现偶环。

问题转化为,我们最多能保留多少代价和的链,使得这些链没有重叠边。

考虑将这些链都挂在它们的LCA上进行DP

F [ i ] [ S ] F[i][S] F[i][S]表示假设已对于i为根的子树,S是一个二进制状态,表示S所有的儿子边的覆盖情况。第j位为1代表i的第j个儿子被覆盖了(当然为了方便转移,可以将定义改成“至多”,相当于取子集max,即为1代表可能被覆盖也可能没有,为0则一定没有)

对于一个点,枚举每条以它为LCA的链,转移相当于对于这条链上的所有点k的 F [ k ] [ S ′ ] F[k][S'] F[k][S],S’的除了链上的儿子是0,其他都是1

对于i,我们刚刚更新的和可以与所有 F [ i ] [ T ] F[i][T] F[i][T],且T的这条链的两个儿子都为0合并成 F [ i ] [ T ′ ] F[i][T'] F[i][T]

此外,还可以直接对于每个儿子直接继承,并将这个儿子边设为已覆盖
这样对于每条链都 2 10 2^{10} 210合并一次,每个儿子也都 2 10 2^{10} 210合并一次,复杂度就是 ( n + m ) ∗ 2 10 (n+m)*2^{10} (n+m)210

Code

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 5005
using namespace std;
int n,m,f[1005][2048],fa[N][20],dep[N],a[N][5],fs[N],nt[2*N],dt[2*N],num,m1,ans,s1[N],n1,fn[N],cf[12];
vector<int> dq[N];
int lca(int x,int y)
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int j=dep[y]-dep[x],c=0;j;c++,j>>=1) if(j&1) y=fa[y][c];
	for(int j=19;x!=y;)
	{
		while(j&&fa[x][j]==fa[y][j]) j--;
		x=fa[x][j],y=fa[y][j];
	}
	return x;
}
int jump(int x,int f)
{
	if(x==f) return x;
	for(int j=dep[x]-dep[f]-1,c=0;j;c++,j>>=1) if(j&1) x=fa[x][c];
	return x;
}
void dfs(int k)
{	
	dep[k]=dep[fa[k][0]]+1;
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		if(p!=fa[k][0])
		{
			fa[p][0]=k;
			dfs(p);
		}
	}
}
void link(int x,int y)
{
	nt[++m1]=fs[x];
	dt[fs[x]=m1]=y;
}
void dp(int k)
{
	int c=0;
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		if(p!=fa[k][0]) 
		{
			dp(p),fn[p]=c++;
			fo(j,0,2047) if(!(j&cf[fn[p]]))f[k][j^cf[fn[p]]]=max(f[k][j^cf[fn[p]]],f[k][j]+f[p][2047]);
		}
	}
	int l=dq[k].size();
	fo(i,0,l-1)
	{
		int t=dq[k][i],x=a[t][0],y=a[t][1],s=0,lx=-1,ly=-1;
		while(x!=k) 
		{
			if(lx<0) s+=f[x][cf[11]-1];
			else s+=f[x][(cf[11]-1)^cf[lx]];
			lx=fn[x],x=fa[x][0];
		}
		while(y!=k)
		{
			if(ly<0) s+=f[y][cf[11]-1];
			else s+=f[y][(cf[11]-1)^cf[ly]];
			ly=fn[y],y=fa[y][0];
		}
		x=a[t][3],y=a[t][4],lx=0;
		if(x!=k) lx^=cf[fn[x]];
		if(y!=k) lx^=cf[fn[y]];
		fo(j,0,2047)
		{
			if(!(j&lx)) f[k][j^lx]=max(f[k][j^lx],s+a[t][2]+f[k][j]);		
		}
	}
}
int main()
{
	cin>>n>>m;
	fo(i,1,m)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		if(z==0) link(x,y),link(y,x);
		else a[++num][0]=x,a[num][1]=y,a[num][2]=z;
	}
	dfs(1);
	fo(i,0,11) cf[i]=1<<i;
	fo(j,1,19)
		fo(i,1,n) fa[i][j]=fa[fa[i][j-1]][j-1];
	ans=0;
	fo(i,1,num)
	{
		int x=a[i][0],y=a[i][1],p=lca(x,y);
		if((dep[x]+dep[y]-2*dep[p]+1)%2==0) ans+=a[i][2];
		else dq[p].push_back(i),a[i][3]=jump(x,p),a[i][4]=jump(y,p),ans+=a[i][2];
	}
	dp(1);
	int sv=0;
	fo(j,0,2047) sv=max(sv,f[1][j]);
	printf("%d\n",ans-sv);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值