[JZOJ5391]【NOIP2017提高A组模拟9.26】卡常题

二分图最小代价激活问题
探讨一个特殊的二分图问题,目标是最小化激活所有Y方点的总代价。通过将问题转化为树形DP问题,并利用读入优化等技巧进行高效求解。

Description

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

20%的数据:n <= 20。
40%的数据:n <=10^3。
另外 10%的数据:a = b =1。
另外20%的数据:保证每个X 方点连接的边颜色相同。
另外 10%的数据:保证原图是一个大小为2*n的环。
100%的数据:n <=10^6,a,b <=100,保证无重边。

Solution

转化一下题意,把每个Y方点看成一条边连接两个点。
那么原图就变成X方点构成的一个环套外向树了,每条边两个端点至少选一个。

随便删掉环上的一条边,以其中一个端点为根树形DP,记录一下另一个端点的状态即可。

注意读入优化。

Code

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <algorithm>
#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 1000005
using namespace std;
int nt[2*N],fs[N],dt[2*N],pr[N],n,m,a,b,rt,rq,fx[2*N],f[N][2][2],rw,s1[N];
bool bz[N];
int 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;return n;
}
void dfs(int k,int fw)
{
    for(int i=fs[k];i;i=nt[i])
    {
        int p=dt[i];
        if(i!=fx[fw])
        {
            if(bz[p]) rt=p,rq=k,rw=i;
            else bz[p]=1,dfs(p,i);
        }
    }
}
void link(int x,int y)
{
    nt[++m]=fs[x];
    dt[fs[x]=m]=y;
}
void dp(int k,int fw)
{
    f[k][1][0]=f[k][1][1]=s1[k];
    for(int i=fs[k];i;i=nt[i])
    {
        int p=dt[i];
        if(i!=fx[fw]&&i!=rw&&i!=fx[rw])
        {
            dp(p,i);
            f[k][0][0]+=f[p][1][0];
            f[k][0][1]+=f[p][1][1];
            f[k][1][0]+=f[p][0][0];
            f[k][1][1]+=f[p][0][1];
        }
    }
    if(k==rq) f[k][0][1]=f[k][1][1];
    f[k][0][0]=min(f[k][0][0],f[k][1][0]);
    f[k][0][1]=min(f[k][0][1],f[k][1][1]);
}
int main()
{
    cin>>n>>a>>b;
    memset(s1,0,sizeof(s1));
    fo(i,1,n) 
    {
        int x,y;
        read(x),read(y);
        link(x,y);
        s1[x]+=a,s1[y]+=b;
        fx[m]=m+1,fx[m+1]=m;
        link(y,x);
    }
    memset(f,0,sizeof(f));
    bz[1]=1;
    dfs(1,0);
    dp(rt,0);
    printf("%d\n",min(f[rt][1][0],f[rt][0][1]));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值