1. 程式人生 > >NOI2018湖北省隊集訓Day1 T3 san

NOI2018湖北省隊集訓Day1 T3 san

題面:
這裡寫圖片描述
這裡寫圖片描述

得分情況:
本來是照著30分的2n列舉全排列打的,結果多拿了五分,開心。

正解:
題目需要我們求的是一個拓撲序中的一個子串的和的最大值,看到題目並沒有什麼思路而且資料範圍又這麼小,開始考慮網路流。
我們對於每個節點考慮三種情況,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; }