题目描述
给定n个点m个边的带权无向图,你要删除若干条边,使得图没有长度为偶数的简单环,求最小费用。
权值为0的边不能删去,他们组成了这个图的一颗生成树。
30分:生成树为一条链。
100分:n<=1000,m<=5000,权值<=10000,无重边,每个点的度数不超过10。
分析
很明显,给定的生成树是我们思考的基础。
考虑怎么样的边要删掉。如果一条非树边,和生成树构成了偶环,肯定要删掉。
另外,两个在生成树上边有交集的奇环,其中有一个的非树边一定要删掉。原因很明显,不走他们的交集构成的环一定是偶环。注意到交集是一个点是没关系的。
那么先处理第一种情况。考虑第二种情况,我们可以算哪些边是可以保留下来的,因为你选了一条非树边,那么有一些就必须要删掉。那么选择保留一条非树边,就相当于把树上一条路径标记了,之后不能再被标记。
在链上很好考虑嘛,设f[i]表示考虑完链上起点和终点都在点i及以下的非树边时,保留的最大费用。对于一条非树边(x,y),我们在深度较浅的点,设为x处理他,f[x]=max(f[x],f[y]+value),value即为这条边的权值,这样,穿过中间的非树边绝对不会被选。什么都不干的转移,就是f[x]=max(f[son],f[x]),可以看成是非树边的转移,那么我们不分树边或者非树边,一起处理即可。
100分,我们考虑在lca搞一条边,一个问题是我们要把沿途的点的其他贡献给加上。一个问题是怎么这些贡献不会和当前选的边冲突。如果我们能记录下一个点的某条非树边不被标记时,它整棵子树产生的贡献就好了,这样我们标记的时候就不会重复。注意到度数不超过10,我们再设f[i][s],意义差不多,s表示点i的树边标记的二进制情况,就可以处理了。具体处理自己推一推,比较简单。
代码
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<set>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int N=1005,NS=1024+5;
int n,m,x,y,z,tot,ans,ax,ay,S,inc,s,i;
int f[N][NS],g[N][15],d[N][15],edge[N][N*5],h[N];
int dis[N],first[N],a[N*10],b[N*10],next[N*10],c[N*10],tt,pd[N*10],fa[N],bel[N];
void cr(int x,int y,int z)
{
tt++;
a[tt]=x;
b[tt]=y;
c[tt]=z;
pd[tt]=1;
next[tt]=first[x];
first[x]=tt;
}
void dfs(int x,int y)
{
dis[x]=dis[y]+1;
fa[x]=y;
for(int p=first[x];p;p=next[p])
if (b[p]!=y&&!c[p])
{
d[x][++d[x][0]]=b[p];
edge[x][++edge[x][0]]=p;
bel[b[p]]=d[x][0];
dfs(b[p],x);
}
}
void predo()
{
fo(x,1,n)
{
for(int p=first[x];p;p=next[p])
if (c[p])
{
if (dis[x]%2!=dis[b[p]]%2) pd[p]=0;
else
{
ax=x;ay=b[p];
if (dis[ax]<dis[ay]) swap(ax,ay);
while (dis[ax]>dis[ay]) ax=fa[ax];
while (ax!=ay) ax=fa[ax],ay=fa[ay];
edge[ax][++edge[ax][0]]=p;
}
}
}
}
void calc(int p)
{
s=inc=0;
x=a[p];y=b[p];ax=ay=0;
if (dis[x]<dis[y]) swap(x,y);
while (dis[x]!=dis[y])
{
ax=x;x=fa[x];
inc+=g[x][bel[ax]];
}
while (x!=y)
{
ax=x;x=fa[x];
inc+=g[x][bel[ax]];
ay=y;y=fa[y];
inc+=g[y][bel[ay]];
}
s=((1<<bel[ax])+(1<<bel[ay]))>>1;
inc+=h[a[p]];
inc+=h[b[p]];
}
void dp(int x)
{
int i,j;
fo(i,1,d[x][0])
dp(d[x][i]);
S=(1<<d[x][0])-1;
fo(i,0,S) f[x][i]=-1;
f[x][0]=0;
fo(i,1,edge[x][0])
{
calc(edge[x][i]);// s,inc
fo(j,0,S)
if (f[x][j]!=-1&&((j&s)==0))
f[x][j|s]=max(f[x][j|s],f[x][j]+inc+c[edge[x][i]]);
}
fo(j,0,S)
{
fo(i,1,d[x][0]) if ((j&(1<<(i-1)))==0)
g[x][i]=max(g[x][i],f[x][j]);
h[x]=max(h[x],f[x][j]);
}
}
int main()
{
freopen("t2.in","r",stdin);
// freopen("t2.out","w",stdout);
scanf("%d %d\n",&n,&m);
fo(i,1,m)
{
scanf("%d %d %d\n",&x,&y,&z);
if (x==y) continue;
cr(x,y,z);
if (!z) cr(y,x,z);
tot+=z;
}
dfs(1,0);
predo();
dp(1);
printf("%d\n",tot-h[1]);
//f 是保留的边的费用。
}