[bzoj1808]/[Ioi2007]training 训练路径

题目大意

给定n个点m条边的无向图,你需要删掉一些边,使得此图没有长度为偶数的简单环。
删掉第i条边有Ci的花费,有些边又是不能删的。不能删的边形成图的一棵生成树。
n≤1000
m≤5000
点的度数不超过10

分析

首先一条非树边的两个端点在树上的距离为奇数就必须删掉。
考虑剩下的边,如果两条边覆盖的树的路径有公共边,那么也是不能同时存在的。
那么问题变成:保留一些非树边,使得图是一个仙人掌。求最少花费。
任找一点为根。设f[i]表示以i为根的子树的答案(不考虑子树内的点向子树外连的边),g[i][j]表示以i为根的子树不考虑i的儿子j为根的子树的答案。
考虑一条两端点lca为i的非树边,它必然经过i,而且会经过两条与i之间相连的树边。
由于一个点的度数不超过10,可以考虑状压DP。设h[S]表示集合S的与i相连的树边(不考虑连向父亲的边)已经被覆盖。然后一条lca为i的路径(u,v)对答案的贡献为f[u]+f[v]+C+对于路径上除去u,v,i的所有点,除去路径经过的子树后的答案(这个用g数组求)。
注意转移和答案的合并。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

#define max(a,b) ((a)>(b)?(a):(b))

using namespace std;

const int N=1e3+5,M=5e3+5,T=1024,INF=1e9;

typedef long long LL;

int n,m,sum,f[N],G[N][N],g[T],h[N],tot,e[M],nxt[M],E[M][3],s[N],Id[N],fa[N],a[N],dep[N],st[N];

vector <int> H[N];

bool vis[N];

char c;

int read()
{
    int x=0,sig=1;
    for (c=getchar();c<'0' || c>'9';c=getchar()) if (c=='-') sig=-1;
    for (;c>='0' && c<='9';c=getchar()) x=x*10+c-48;
    return x*sig;
}

void add(int x,int y)
{
    e[++tot]=y; nxt[tot]=h[x]; h[x]=tot;
}

void Init(int x)
{
    for (int i=h[x];i;i=nxt[i]) if (e[i]!=fa[x])
    {
        fa[e[i]]=x; dep[e[i]]=dep[x]+1; Init(e[i]);
    }   
}

void dfs(int x)
{
    for (int i=h[x];i;i=nxt[i]) if (e[i]!=fa[x])
    {
        s[e[i]]=s[x]-f[x]+G[x][Id[e[i]]]+f[e[i]]; st[e[i]]=st[x];
        dfs(e[i]);
    }
}

void dp(int x)
{
    int cnt=0,i,j,k,t,d1,d2;
    for (i=h[x];i;i=nxt[i]) if (e[i]!=fa[x])
    {
        Id[e[i]]=cnt++;
        dp(e[i]);
        s[e[i]]=f[e[i]]; st[e[i]]=cnt-1; dfs(e[i]);
    }
    for (i=h[x];i;i=nxt[i]) if (e[i]!=fa[x]) a[Id[e[i]]]=e[i];
    if (!cnt) return;
    t=1<<cnt;
    for (i=1;i<t;i++) g[i]=-INF; g[0]=0;
    vector <int> ::iterator it;
    for (it=H[x].begin();it!=H[x].end();it++)
    {
        j=*it;
        if (x!=E[j][0]) d1=1<<st[E[j][0]];else d1=0;
        if (x!=E[j][1]) d2=1<<st[E[j][1]];else d2=0;
        for (i=k=(t-1)^(d1|d2);i;i=(i-1)&k)
            g[i|d1|d2]=max(g[i|d1|d2],g[i]+s[E[j][0]]+s[E[j][1]]+E[j][2]);
        g[d1|d2]=max(g[d1|d2],s[E[j][0]]+s[E[j][1]]+E[j][2]);
    }
    for (i=0;i<t;i++)
    {
        for (j=0;j<cnt;j++) g[i]+=(((1<<j)&i)==0)*f[a[j]];
        for (j=0;j<cnt;j++) if (((1<<j)&i)==0) G[x][j]=max(G[x][j],g[i]-f[a[j]]);
        f[x]=max(f[x],g[i]);
    }
}

int main()
{
    n=read();
    for (int i=read();i--;)
    {
        int x=read(),y=read(),w=read();
        if (!w)
        {
            add(x,y); add(y,x); continue;
        }
        if (x==y) continue;
        sum+=w;
        E[m][0]=x; E[m][1]=y; E[m++][2]=w;
    }
    Init(1);
    for (int i=0,j;i<m;i++)
    {
        for (j=1;j<=n;j++) vis[j]=0;
        for (j=E[i][0];j;j=fa[j]) vis[j]=1;
        for (j=E[i][1];!vis[j];j=fa[j]);
        if (!((dep[E[i][0]]+dep[E[i][1]]-2*dep[j])&1)) H[j].push_back(i);
    }
    dp(1);
    printf("%d\n",sum-f[1]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值