6478. 【GDOI2020模拟02.19】C

题目

给你一棵大小为 n n n的树,还有 m m m条路径。
每条路径都可以染成红色或蓝色,各有一定的贡献。
对于每一条边,每条经过它的红色路径会带来一些贡献,每条经过它的蓝色路径也会带来一些贡献。
而且经过这条边的红色路径和蓝色路径的条数都有上限。
问满足所有限制条件之后的最小贡献是多少。


思考历程

打了前两档暴力,并且由于没有输出-1,所以第一档暴力还挂了。
第三档暴力想出了暴力dp的做法,但没有时间打。


正解

显然是个线性规划嘛,只是想不想得到的问题。

先说pty的做法:
先假设所有的路径都选红色,接下来要将一些路径改选成蓝色。
那么每条边上,经过它的路径中改选成蓝色的路径条数在一定区间内(记为 [ L u , R u ] [L_u,R_u] [Lu,Ru]
先计算出每条路径改选之后贡献。
x i x_i xi为第 i i i条路径选或不选的状态。
考虑对一条边 ( u , f a ) (u,fa) (u,fa)的限制: ∑ i 为 经 过 ( u , f a ) 的 路 径 x i ∈ [ L u , R u ] \sum_{i为经过(u,fa)的路径}{x_i}\in [L_u,R_u] i(u,fa)xi[Lu,Ru]
可以转化成这样的限制 ∑ x i + a u = R u \sum{x_i}+a_u=R_u xi+au=Ru ∑ x i − b u = L u \sum{x_i}-b_u=L_u xibu=Lu
两式相减,得 a u + b u = R u − L u a_u+b_u=R_u-L_u au+bu=RuLu,相当于 a u ≤ R u − L u a_u\leq R_u-L_u auRuLu
于是这就成了个下界为 0 0 0的差分约束模型: x i ≤ 1 x_i\leq 1 xi1并且 a u ≤ R u − L u a_u\leq R_u-L_u auRuLu,要满足 ∑ x i + a u = R u \sum x_i+a_u=R_u xi+au=Ru,求最小贡献。
考虑对于一棵树上,每个点都有个方程,将这个方程跟所有儿子的方程作差,会得到什么。
∑ i 为 u 出 发 的 路 径 x i − ∑ i 为 u 结 束 的 路 径 x i + a u − ∑ a s o n = R u − ∑ R s o n \sum_{i为u出发的路径}x_i-\sum_{i为u结束的路径}x_i+a_u-\sum a_{son}=R_u-\sum R_{son} iuxiiuxi+auason=RuRson
考虑费用流。假如式子左边是 F 出 − F 入 F_出-F_入 FF
对于每个路径 ( x , y ) (x,y) (x,y) y y y x x x连边 ( 1 , V ) (1,V) (1,V) V V V表示贡献)。 y y y的流出加一, x x x的流入加一。
对于每条边 ( u , f a ) (u,fa) (u,fa) u u u f a fa fa连边 ( R u − L u , 0 ) (R_u-L_u,0) (RuLu,0)。分析类似。
这样连边之后, F 出 − F 入 F_出-F_入 FF不一定会为 0 0 0。为了保证流量平衡,对于 R u − ∑ R s o n R_u-\sum R_{son} RuRson为v正数的点 u u u,连 ( S , u ) (S,u) (S,u)补够流入;反之同理。
接下来跑最小费用最大流即可。有解的条件是:和源点汇点连的边都满流。
(我终于发现了原来zkw费用流跑带负权的边不靠谱,于是打了dinic。)

然后说题解的做法:
同样也要进行一开始的处理。
对于每条边 ( u , f a ) (u,fa) (u,fa),连一条上下界为 [ L u , R u ] [L_u,R_u] [Lu,Ru]的费用为 0 0 0的边。
对于每条路径 ( x , y ) (x,y) (x,y),从 S S S y y y连上下界为 [ 1 , 1 ] [1,1] [1,1]的费用为 0 0 0的边。
x x x T T T连一条上下界为 [ 1 , 1 ] [1,1] [1,1]的费用为 0 0 0的边。
y y y x x x连一条上下界为 [ 0 , 1 ] [0,1] [0,1]的费用为 V V V的边。( V V V为红改蓝的贡献)
跑上下界费用流。
这样给 y y y f 出 − f 入 f_出-f_入 ff减一,给 x x x f 出 − f 入 f_出-f_入 ff加一。
要使流量平衡,要么走 ( y , x ) (y,x) (y,x),意味着红变蓝;要么走树上的路径,保留原样。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cassert>
#define N 160
int n,m;
struct EDGE{
	int to;
	EDGE *las;
} e[N];
int ne;
EDGE *last[N];
int fa[N],A[N],B[N],eA[N],eB[N];
int sumA[N],sumB[N];
int bz[N],tmp[N];
struct Path{
	int u,v,pA,pB;
} p[N];

struct EDGE2{
	int to,c,w;
	EDGE2 *las;
} e2[N*3*2];
int ne2;
EDGE2 *last2[N];
int S,T;
#define rev(ei) (e2+(int((ei)-e2)^1))
inline void link(int u,int v,int c,int w){
	e2[ne2]={v,c,w,last2[u]};
	last2[u]=e2+ne2++;
	e2[ne2]={u,0,-w,last2[v]};
	last2[v]=e2+ne2++;
}
bool vis[N];
int dis[N];
EDGE2 *cur[N];
int maxflow,mincost;
int dfs(int x,int s){
	if (x==T){
		maxflow+=s;
		mincost+=dis[T]*s;
		return s;
	}
	vis[x]=1;
	int have=s;
	for (EDGE2 *ei=last2[x];ei;ei=ei->las)
		if (ei->c && !vis[ei->to] && dis[x]+ei->w==dis[ei->to]){
			int t=dfs(ei->to,min(have,ei->c));
			ei->c-=t,rev(ei)->c+=t,have-=t;
			if (!have)
				return s;
		}
	return s-have;
}
int cop[N];
int q[N*N];
bool inq[N];
inline bool change(){
	memcpy(cop,dis,sizeof dis);
	memset(dis,127,sizeof dis);
	dis[S]=0;
	inq[S]=1;
	int head=0,tail=0;
	q[0]=S;
	while (head<=tail){
		int x=q[head++];
		for (EDGE2 *ei=last2[x];ei;ei=ei->las)
			if (ei->c && dis[x]+ei->w<dis[ei->to]){
				dis[ei->to]=dis[x]+ei->w;
				if (!inq[ei->to]){
					q[++tail]=ei->to;
					inq[ei->to]=1;
				}
			}
		inq[x]=0;
	}
	for (int i=1;i<=T;++i)
		if (dis[i]!=cop[i])
			return 1;
	return 0;
}
inline void flow(){
	mincost=maxflow=0;
	while (change())
		do
			memset(vis,0,sizeof vis);
		while (dfs(S,INT_MAX));
}
void init(int x){
	for (EDGE *ei=last[x];ei;ei=ei->las){
		sumA[ei->to]=sumA[x]+eA[ei->to];
		sumB[ei->to]=sumB[x]+eB[ei->to];
		init(ei->to);
	}
}
void init2(int x){
	int s=B[x];
	for (EDGE *ei=last[x];ei;ei=ei->las){
		init2(ei->to);
		A[ei->to]=bz[ei->to]-A[ei->to];
		link(ei->to,x,B[ei->to]-A[ei->to],0);
		bz[x]+=bz[ei->to];
		s-=B[ei->to];
	}
	if (s>0)
		link(S,x,s,0);
	else if (s<0)
		link(x,T,-s,0);
}
int main(){
	freopen("C.in","r",stdin);
	freopen("C.out","w",stdout);
	scanf("%d%d",&n,&m);
	bool _2=1;
	for (int i=2;i<=n;++i){
		scanf("%d%d%d%d%d",&fa[i],&A[i],&B[i],&eA[i],&eB[i]);
		A[i]=min(A[i],m);
		B[i]=min(B[i],m);
		e[ne]={i,last[fa[i]]};
		last[fa[i]]=e+ne++;
	}
	S=n+1,T=n+2;
	init(1);
	for (int i=2;i<=n;++i)
		eB[i]-=eA[i];
	int sum=0;
	for (int i=1;i<=m;++i){
		scanf("%d%d%d%d",&p[i].u,&p[i].v,&p[i].pA,&p[i].pB);
		int u=p[i].u,v=p[i].v;
		bz[v]++,bz[u]--;
		p[i].pA+=sumA[v]-sumA[u];
		p[i].pB+=sumB[v]-sumB[u];
		p[i].pB-=p[i].pA;
		sum+=p[i].pA;
		link(p[i].v,p[i].u,1,p[i].pB);
	}
	init2(1);
	for (int i=1;i<=n;++i)
		if (A[i]>B[i]){
			printf("-1\n");
			return 0;
		}
	flow();
	int tmp=0;
	for (EDGE2 *ei=last2[S];ei;ei=ei->las)
		if (ei->c){
			printf("-1\n");
			return 0;
		}
	for (EDGE2 *ei=last2[T];ei;ei=ei->las)
		if (rev(ei)->c){
			printf("-1\n");
			return 0;
		}
	printf("%d\n",mincost+sum);
	return 0;
}

总结

线性规划真是个大坑……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值