【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,答案即
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);
}