小M的作物

link

我的网络流建模第一题。当然那个什么奶牛的电信太弱了忽略不计。

我目前做到的网络流题目都用到了一个很重要的结论,那就是在一个网络流中,最小割等于最大流。严谨的证明我不会也懒得去想,感性的证明还是懂的。先不管这玩意为什么正确,知道它正确就行了。它有许多重要的应用,因为可以把网络流中的边看成是一种决策,假如这条边在我们的最小割里那么就可以看成是我们舍弃了这个决策,并可以把问题转化成求解最大流。放在这道题的背景下分析一下。

我们会发现一棵植物要么种在A田里,要么种在B里,很明显这是两个互斥的决策,有你没我有我没你的那种(“小孩子才做选择题呢”),不得不说此处有那么一点2-sat的味道。于是可以考虑把这颗植物向源点与汇点连边,那么两个不能同时选就转化成了源点与汇点不相连,那不就是割的定义了吗。

至于下面那个奇怪的同种植物相亲相爱的设定,很明显可以想到建立代表点的处理方法,图论那边此类用法多的是。于是考虑建立两个代表点,连上A和B,分别对应集合内植物全部选A或全部选B的情况。可以看出要使得AB天涯永隔,要么断代表点到源汇点的边,要么断掉集合内所有点到另一侧源汇点的边,对应的决策分别是要么损失掉相亲相爱带来的收益,要么付出相亲相爱的代价。至此,题目中所有的可能决策都已经在网络流里得到对应,接下来跑最大流就可以了。

这道题边数多得有些不太正常,百万级别的边实在令人有些汗颜。而且我的老版dinic还直接被卡出了7个Tle,太残暴了,逼得我上-1优化。不过数据很水,把反边对应写成&而非^也能得88分。太良心了。

#include<cstdio>
#include<cstring>
#define zczc
#define int long long
using namespace std;
const int N=1010<<2;
const int M=1000010;
const int inf=1e15;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}
inline int min(int s1,int s2){
	return s1<s2?s1:s2;
}

int m,n;
struct edge{
	int t,v,next;
}e[M<<1];
int esum=1,head[N];
inline void add(int fr,int to,int val){
	e[++esum]=(edge){to,val,head[fr]};head[fr]=esum;
}
inline void addedge(int fr,int to,int val){
	add(fr,to,val);add(to,fr,0);return;
}

int q[N],l,r,d[N];
bool vis[N];
bool check(){
	memset(d,0,sizeof(d));
	memset(vis,false,sizeof(vis));
	q[l=r=1]=1,d[1]=1;int wh;
	while(l<=r)
		for(int i=head[wh=q[l++]],th;i;i=e[i].next)
			if(e[i].v>0&&d[th=e[i].t]==0)d[th]=d[wh]+1,q[++r]=th;
	return d[2]>0;
}
int dinic(int wh,int val){
	if(wh==2)return val;int used=0;vis[wh]=true;
	for(int i=head[wh],th;i;i=e[i].next){
		if(e[i].v==0||d[th=e[i].t]!=d[wh]+1||vis[th])continue;
		int now=dinic(th,min(val-used,e[i].v));
		if(now)used+=now,e[i].v-=now,e[i&1].v+=now;
		if(used==val)break;
	}
	return vis[wh]=used!=val,used;
}

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	int s1,s2,s3,cnt=2,all=0;read(m);
	for(int i=1;i<=m;i++){
		read(s1);all+=s1;addedge(1,++cnt,s1);
	}
	for(int i=1;i<=m;i++){
		read(s1);all+=s1;addedge(i+2,2,s1);
	}
	read(n);
	while(n--){
		read(s1);int p1=++cnt,p2=++cnt;
		read(s2);addedge(1,p1,s2);all+=s2;read(s2);addedge(p2,2,s2);all+=s2;
		while(s1--){
			read(s2);addedge(p1,s2+2,inf);addedge(s2+2,p2,inf);
		}
	}
	
	int ans=0;
	while(check())ans+=dinic(1,inf);
	printf("%lld\n",all-ans);
	
	return 0;
}
posted @ 2022-02-08 18:06  Feyn618  阅读(31)  评论(0编辑  收藏  举报