Description
有 n 个数 x1 ~xn 。你需要找出它们的一个排列,满足 m 个条件,每个条件形如 x_a 必须在x_b之前。在此基础上,你要最大化这个排列的最大子段和。
Input
第一行两个整数 n,m,第二行 n 个整数 x1 ~xn ,接下来 m 行每行两个整数 a,b。
Output
输出一行一个整数表示最大子段和。
Sample Input
5 4
2 3 -2 5 -3
1 5
2 3
3 4
5 3
Sample Output
6
Data Constraint
Subtask 1 (5pts):n<=10。
Subtask 2 (20pts):n<=20。
Subtask 3 (19pts):m=n-1 且 x1 一定在排列的第一位。
Subtask 4 (56pts):无特殊限制。 对于全部数据,n<=500,m<=1000,|x i |<=1000,保证存在至少一种排列。
Solution
- 先贴题解:
看到数据范围,就应该想到网络流!!!
先把权值为正的数放到一起,但全部选就意味着同时也要选很多负的,这样不一定最优。
考虑把其中一些点扔走(往左右扔),或者干脆把中间的负点也选了算了。
于是向上面说的那样连边跑最小割,用正权值和-最小割即为答案。
Code
#include<cstdio>
#include<cctype>
using namespace std;
const int N=1005,inf=1e9;
int s,t,ans,tot=1;
int first[N],nex[N*6],en[N*6],w[N*6];
int dis[N],gap[N*6],cur[N];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) X=(X<<1)+(X<<3)+(ch^48),ch=getchar();
return w?-X:X;
}
inline int min(int x,int y)
{
return x<y?x:y;
}
inline void ins(int x,int y,int z)
{
nex[++tot]=first[x];
first[x]=tot;
en[tot]=y;
w[tot]=z;
}
inline void insert(int x,int y,int z)
{
ins(x,y,z),ins(y,x,0);
}
int sap(int x,int y)
{
if(x==t) return y;
int use=0;
for(int i=first[x];i;i=nex[i])
if(w[i] && dis[x]==dis[en[i]]+1)
{
int sum=sap(en[i],min(y-use,w[i]));
w[i]-=sum,w[i^1]+=sum,use+=sum;
if(dis[s]>t || use==y) return use;
}
cur[x]=first[x];
if(!--gap[dis[x]]) dis[s]=t+1;
gap[++dis[x]]++;
return use;
}
int main()
{
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
int n=read(),m=read();
s=n<<1|1,t=s+1;
for(int i=1;i<=n;i++)
{
int x=read();
if(x>0)
{
ans+=x;
insert(s,i,x);
insert(i+n,t,x);
}else insert(i,i+n,-x);
}
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
insert(x,y,inf);
insert(x+n,y+n,inf);
}
for(int i=1;i<=t;i++) cur[i]=first[i];
gap[0]=t;
while(dis[s]<=t) ans-=sap(s,inf);
printf("%d\n",ans);
return 0;
}