洛谷 - P2765 魔术球问题(最大流+残余网络上的最大流+路径打印)
題目鏈接:點擊查看
題目大意:給出n個柱子,問若按照規則擺放,最多能放多少個球,規則如下:
并輸出方案
題目分析:知道是最大流但卻不知道該怎么建圖。。太菜了,感覺這種隱式圖比正常圖更難搞,雖然是屬于網絡流的題目,好像貪心也可以搞一搞,但還是老老實實練練怎么建圖吧
對于這個題目而言,其實可以將其抽象成二分圖的模型,建好圖后就可以直接用最小路徑覆蓋的定理了:
給定一張有向無環圖,要求用盡量少的不相交的簡單路徑,覆蓋有向無環圖的所有頂點(也就是每個頂點恰好被覆蓋一次)。公式:最小路徑覆蓋=頂點數-二分圖最大匹配(無向圖需要除以2)
對于有關系的點我們可以建有向邊,然后判斷能否將所有的點分成互不相連的盡可能少的單鏈,這樣一來,就可以將每一條單鏈放在一個柱子上,就可以符合題意了
在實現上述操作的過程中,我們可以選擇不斷增加當前球的數量,從而在建邊后反復跑最大流,但如果每次都重新跑最大流的話肯定會超時,為了防止超時,我們可以每次都在殘余網絡上跑,這樣跑出來的答案就是在加上當前球后多跑的流量了,最大流跑出來的答案就是二分圖最大匹配,這樣只要滿足最小路徑覆蓋的答案小于等于柱子的個數就好了,稍微想一下這個關系
這樣顯然對于每個點我們需要進行拆點處理,每次新增加一個球,在網絡流建圖時我們就讓他的入點為2*num,出點為2*num+1,如果某個編號x與其之前的編號y可以相鄰,我們就讓y對x建邊,邊權為1就好了,跑完網絡流后相當于完成了二分圖最大匹配,就能直接得到能放多少個球了
至于另一個子問題,也就是打印路徑,在我看來反而比求最大流還要難,這里我選擇的是數組模擬鏈表的方式記錄一下其之間的關系,也就是哪些球是可以相鄰的,在跑完最大流后的殘余網絡中,兩個點(源點和匯點除外)相連的正向邊,若邊權為0說明這條邊為最大流做出了貢獻,所及記錄一下其對應關系,最后跑一邊答案就出來了
好像說的有點繞,不過確實是盡我所能去描述清楚了。。可能也是我邏輯太混亂了吧,菜是原罪
代碼:
#include<iostream> #include<cstdlib> #include<string> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<cmath> #include<cctype> #include<stack> #include<queue> #include<list> #include<vector> #include<set> #include<map> #include<sstream> #include<unordered_map> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=1e5+100;const int M=3e5+100;struct Edge {int to,w,next; }edge[M];//邊數unordered_map<int,bool>mp;int head[N],cnt,nx[N];bool vis[N];void addedge(int u,int v,int w) {edge[cnt].to=v;edge[cnt].w=w;edge[cnt].next=head[u];head[u]=cnt++;edge[cnt].to=u;edge[cnt].w=0;//反向邊邊權設置為0edge[cnt].next=head[v];head[v]=cnt++; }int d[N],now[N];//深度 當前弧優化bool bfs(int s,int t)//尋找增廣路 {memset(d,0,sizeof(d));queue<int>q;q.push(s);now[s]=head[s];d[s]=1;while(!q.empty()){int u=q.front();q.pop();for(int i=head[u];i!=-1;i=edge[i].next){int v=edge[i].to;int w=edge[i].w;if(d[v])continue;if(!w)continue;d[v]=d[u]+1;now[v]=head[v];q.push(v);if(v==t)return true;}}return false; }int dinic(int x,int t,int flow)//更新答案 {if(x==t)return flow;int rest=flow,i;for(i=now[x];i!=-1&&rest;i=edge[i].next){int v=edge[i].to;int w=edge[i].w;if(w&&d[v]==d[x]+1){int k=dinic(v,t,min(rest,w));if(!k)d[v]=0;edge[i].w-=k;edge[i^1].w+=k;rest-=k;}}now[x]=i;return flow-rest; }void init() {for(int i=1;i<=5000;i++)//預處理平方數mp[i*i]=true;memset(head,-1,sizeof(head));memset(nx,-1,sizeof(nx));//數組模擬鏈表memset(vis,false,sizeof(vis));cnt=0; }int solve(int st,int ed) {int ans=0,flow;while(bfs(st,ed))while(flow=dinic(st,ed,inf))ans+=flow;return ans; }int main() { // freopen("input.txt","r",stdin); // ios::sync_with_stdio(false);init();int n,st=N-1,ed=st-1;scanf("%d",&n);int num=0,flow=0;while(num-flow<=n){num++;addedge(st,num<<1,1);addedge(num<<1|1,ed,1);for(int i=1;i<num;i++)if(mp[i+num])addedge(i<<1,num<<1|1,1);flow+=solve(st,ed);}num--;for(int i=1;i<=num;i++){int u=i<<1;for(int j=head[u];j!=-1;j=edge[j].next){int v=edge[j].to;int w=edge[j].w;if(v==st)continue;if(w==0)nx[i]=(v-1)/2;}}printf("%d\n",num);for(int i=1;i<=num;i++){if(!vis[i]){int k=i;while(k!=-1){vis[k]=true;printf("%d ",k);k=nx[k];}printf("\n");}}return 0; }?
總結
以上是生活随笔為你收集整理的洛谷 - P2765 魔术球问题(最大流+残余网络上的最大流+路径打印)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 - P4016 负载平衡问题(最小
- 下一篇: 洛谷 - P2763 试题库问题(最大流