Jzoj5669 排列

有 n 个数 x1 ~xn 。你需要找出它们的一个排列,满足 m 个条件,每个条件形如 x_a 必须在x_b之前。在此基础上,你要最大化这个排列的最大子段和。

神题,这里先orz一下当场切掉的神犇wjw

看下数据范围,n<=500感觉是网络流,结果就是

构图方式,每个点拆开成a[i],b[i]

如果x[i]>0,那么S->a[i],b[i]->T连权值为x[i]的边

否则,a[i]->b[i]权值为-x[i]的边

对于每个限制(x,y) 连a[x]->a[y] 和 b[x]->b[y],容量+inf

跑一次最小割,用所有正数的和减掉最小割就是答案

其实割掉三种边的意义是很明显的:

割掉S->a[i]相当于将i放到最大子段的前面

割掉a[i]->b[i]相当于将i放入最大子段中

割掉b[i]->T相当于将i放到最大子段的后面

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1010
using namespace std;
struct edge{ int v,c,nt; } G[N<<3];
int n,m,cnt=1,ans=0,h[N],cur[N],lvl[N],gap[N],p[N];
inline void adj(int x,int y,int c){
	G[++cnt]=(edge){y,c,h[x]}; h[x]=cnt;
	G[++cnt]=(edge){x,0,h[y]}; h[y]=cnt;
}
inline int ISAP(int S,int T){
	memset(p,-1,sizeof p);
	memcpy(cur,h,sizeof cur);
	int x=p[S]=S,fl=0,df=(1<<29); *gap=n+1<<1;
	for(;lvl[S]<(n+1)<<1;){
		loop:
		for(int &i=cur[x],v;i;i=G[i].nt){
			v=G[i].v;
			if(G[i].c && lvl[v]+1==lvl[x]){
				df=min(df,G[i].c); p[v]=x; x=v;
				if(v==T){
					fl+=df;
					for(x=p[v];v!=S;v=x,x=p[x]){
						G[cur[x]].c-=df;
						G[cur[x]^1].c+=df;
					}
					df=1<<30;
				}
				goto loop;
			}
		}
		int dl=n+1<<1;
		for(int i=h[x];i;i=G[i].nt)
			if(G[i].c && lvl[G[i].v]<dl){
				dl=lvl[G[i].v];
				cur[x]=i;
			}
		if(--gap[lvl[x]]==0) break;
		lvl[x]=dl+1;
		++gap[lvl[x]]; x=p[x];
	}
	return fl;
}
int main(){
	freopen("permutation.in","r",stdin);
	freopen("permutation.out","w",stdout);
	scanf("%d%d",&n,&m); 
	int S=n*2+1,T=n*2+2;
	for(int x,i=1;i<=n;++i){
		scanf("%d",&x);
		if(x>=0){
			ans+=x;
			adj(S,i,x);
			adj(i+n,T,x);
		} else adj(i,i+n,-x);
	}
	for(int x,y;m--;){
		scanf("%d%d",&x,&y);
		adj(x,y,1<<20);
		adj(x+n,y+n,1<<20);
	}
	printf("%d\n",ans-ISAP(S,T));
}


posted @ 2018-04-20 21:29  扩展的灰(Extended_Ash)  阅读(246)  评论(0编辑  收藏  举报