NOI2018湖北省隊集訓Day1 T3 san
阿新 • • 發佈:2019-01-11
題面:
得分情況:
本來是照著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;
}