题面:
得分情况:
本来是照着30分的
枚举全排列打的,结果多拿了五分,开心。
正解:
题目需要我们求的是一个拓扑序中的一个子串的和的最大值,看到题目并没有什么思路而且数据范围又这么小,开始考虑网络流。
我们对于每个节点考虑三种情况,1.不选在选的子串前,2.在选的子串中,3.不选在选的子串后。然后把每个点拆成两个点,从s到t依次连边,会有三条边,即对应这三种情况。如果一个点的权值为正,那么我们先将它加入答案,考虑最小割模型,然后1,3的flow为权值(不选的话答案减权值),2的flow为0(选的话对答案无影响);如果一个点权值为负,1,3的flow为0(不选的话对答案无影响),2的flow为权值的相反数(选的话答案减权值)。
我们再来考虑拓扑序的影响。其实两个节点之间的边就是限制了它们在拓扑序中出现的先后顺序,那么我们只要从起点拆成的两个点分别向终点拆成的两个点连flow为inf的边就行了。
最后跑最小割(最大流)即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=60,inf=1e8;
struct stu
{
int to,next,flow;
}road[1000000]; int first[maxn*3],cnt=1;
int dep[maxn*3];
int n,m,s,t,ans;
queue <int> q;
void addedge(int x,int y,int flow)
{
road[++cnt].to=y;
road[cnt].flow=flow;
road[cnt].next=first[x];
first[x]=cnt;
road[++cnt].to=x;
road[cnt].flow=0;
road[cnt].next=first[y];
first[y]=cnt;
}
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty()) q.pop();
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();q.pop();
for(int i=first[now];i;i=road[i].next)
{
int to=road[i].to;
if(!dep[to]&&road[i].flow>0)
{
dep[to]=dep[now]+1;
q.push(to);
}
}
}
if(dep[t]==0) return 0;
return 1;
}
int dfs(int now,int maxx)
{
if(now==t) return maxx;
for(int i=first[now];i;i=road[i].next)
{
int to=road[i].to;
if(road[i].flow>0&&dep[to]==dep[now]+1)
{
int k=dfs(to,min(maxx,road[i].flow));
if(k>0)
{
road[i].flow-=k;
road[i^1].flow+=k;
return k;
}
}
}
return 0;
}
int min_cut()
{
int nowans=0;
while(bfs())
{
while(int k=dfs(s,inf)) nowans+=k;
}
return nowans;
}
int main()
{
scanf("%d%d",&n,&m);
s=0,t=2*n+1;
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(x>=0)
{
ans+=x;
addedge(s,i,x);
addedge(i,i+n,0);
addedge(i+n,t,x);
}
else
{
addedge(s,i,0);
addedge(i,i+n,-x);
addedge(i+n,t,0);
}
}
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y,inf);
addedge(x+n,y+n,inf);
}
printf("%d\n",ans-min_cut());
return 0;
}