JZOJ5669. 【GDSOI2018模拟4.19】排列

6 篇文章 0 订阅
4 篇文章 0 订阅

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,保证存在至少一种排列。

题解

先考虑没有任何限制的时候,
一定就是将所有正数放在一起,而负数就放在一边。
可惜题目给出了一些限制,
为了满足这些限制,可以采取以下这几种方式来调整顺序,
把某个正数从答案区间的前面扔出,把某个正数从答案区间的后面扔出,
或者将某个负数增加到答案序列。
也许很难想到是网络流,
但想到了,连边就对于这上面的三种情况。
将每个位置拆成两个点
从超级源S连向所有的正数,边权为整个数的数值,
这一部分边如果被割掉就表示这个正数被从前面扔出。
所有正数的另外一个点,连向超级汇T,边权为这个数的数值,
这一部分边如果被割掉就表示这个正数被从后面扔出。
对于所有的负数,
自己的两个点之间连一条边,边权为它的绝对值。
这一部分的边如果被割掉就表示某个负数加入了答案。

现在就是要看限制条件了,
对于一个限制条件:a,b
就分别从a的两个点连向b的两个点,边权为正无穷,因为这些边是不能被割掉的。
然后求一下最最大流就行了。

code

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h>
#include <time.h> 
#define ll long long
#define N 10003
#define M 103
#define db double
#define P putchar
#define G getchar
#define inf 998244353
using namespace std;
char ch;
void read(int &n)
{
    n=0;
    ch=G();
    while((ch<'0' || ch>'9') && ch!='-')ch=G();
    ll w=1;
    if(ch=='-')w=-1,ch=G();
    while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
    n*=w;
}

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
ll abs(ll x){return x<0?-x:x;}
ll sqr(ll x){return x*x;}
void write(ll x){if(x>9) write(x/10);P(x%10+'0');}

int nxt[N*2],to[N*2],v[N*2],lst[N],cur[N],tot;
int q[N],h[N],S,T,ans,n,m,x,y;

bool bfs()
{
    int head=0,tail=1;
    for(int i=0;i<=T;i++)h[i]=-1;
    q[0]=S;h[S]=0;
    while(head!=tail)
    {
        int now=q[head];head++;
        for(int i=lst[now];i;i=nxt[i])
            if(v[i] && h[to[i]]==-1)
            {
                h[to[i]]=h[now]+1;
                q[tail++]=to[i];
            }
    }
    return h[T]!=-1;
}

int dfs(int x,int f)
{
    if(x==T)return f;
    int w,used=0;
    for(int i=cur[x];i;i=nxt[i])
        if(h[to[i]]==h[x]+1)
        {
            w=f-used;
            w=dfs(to[i],min(w,v[i]));
            v[i]-=w;v[i^1]+=w;
            if(v[i])cur[x]=i;
            used+=w;
            if(used==f)return f;
        }
    if(!used)h[x]=-1;
    return used;
}

void dinic()
{
    while(bfs())
    {
        for(int i=0;i<=T;i++)
            cur[i]=lst[i];
        ans-=dfs(S,inf);
    }
}

void ins(int x,int y,int z)
{
    nxt[++tot]=lst[x];
    to[tot]=y;
    v[tot]=z;
    lst[x]=tot;
}

void ins_(int x,int y,int z)
{
    ins(x,y,z);
    ins(y,x,0);
}

int main()
{
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);

    tot=1;read(n);read(m);
    S=n*2+1;T=S+1;

    for(int i=1;i<=n;i++)
    {
        read(x);
        if(x>0)ins_(S,i,x),ins_(i+n,T,x),ans+=x;else ins_(i,i+n,-x);
    }

    for(int i=1;i<=m;i++)
        read(x),read(y),ins_(x,y,inf),ins_(x+n,y+n,inf);

    dinic();
    write(ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值