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));
}