[IOI2007]Training , [JZOJ5320]偶环

题目描述

给定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 是保留的边的费用。 
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值