【JZOJ5391】卡常题

Description

ρ有一个二分连通无向图,X 方点、Y 方点均为n个(编号为1 ~ n)。
这个二分图比较特殊,每一个Y 方点的度为2,一条黑色边,一条白色边。
所有黑色边权值均为a ,所有白色边权值均为b 。
选择一个X 方点,代价为连接的所有边的权值之和。
激活一个Y 方点,需要选择至少一个与之相邻的X 方点。
现在,ρ想激活每个Y 方点,他想知道最小的总代价。
不过ρ很善良,他给你开了O2 优化。
这样你就不会被卡常了。
当然,除非你真的连读入优化都不想写,或者常数真的丑死。

Solution

转化一下模型,对于Y连出去的两个X,我们可以视为这两个X连一条边,那么题目代价为每条边都至少有一个端点被选择的最小代价和。

我们考虑树,显然树形dp可以很快解决这个问题:设 fv,0..1 表示 v 点是否选的最小代价和,那么转移显然。
现在是环套树,那么我们把环上随便一条边拆掉,记下它的两个端点,那么这两个端点至少有一个要选,于是从两个端点分别做一次树形dp,答案即min(fu,1,fv,1) u v为拆掉这条边的两个端点)。

Code

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
#define rep(i,x) for(int i=ls[x];i;i=nx[i])
#define N 1000010
#define M 2000010
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
int f[N][2];
int to[M],nx[M],ls[N],num=1;
int w[N];
void read(int &n)
{
    char ch=' ';int q=0,w=1;
    for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
    if(ch=='-')w=-1,ch=getchar();
    for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;
}
void link(int x,int y){
    to[++num]=y,nx[num]=ls[x],ls[x]=num;
}
int zl,zr,o;
bool vis[N];
void find(int x,int fa){
    vis[x]=true;
    rep(i,x){
        int v=to[i];
        if(v==fa) continue;
        if(vis[v]) {o=i,zl=x,zr=v;return;}
        find(v,x);
    }
}
void dp(int x,int fa)
{
    int s=0;
    f[x][0]=0,f[x][1]=w[x];
    rep(i,x)
    {
        int v=to[i];
        if(i==o || (i^1)==o || v==fa) continue;
        dp(v,x);
        s+=min(f[v][0],f[v][1]),f[x][0]+=f[v][1];
    }
    f[x][1]+=s;
}
int main()
{
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    int n,o1,o2;
    scanf("%d %d %d",&n,&o1,&o2);
    fo(i,1,n)
    {
        int x,y;
        read(x),read(y);
        w[x]+=o1,w[y]+=o2;
        link(x,y),link(y,x);
    }
    find(1,0);
    dp(zl,0);
    int ans=f[zl][1];
    dp(zr,0),ans=min(ans,f[zr][1]);
    printf("%d",ans);
}
posted @ 2017-10-06 22:14  sadstone  阅读(43)  评论(0编辑  收藏  举报