1. 程式人生 > >「網絡流24題」魔術球問題

「網絡流24題」魔術球問題

pro oid 什麽 拆點 最大流 eve 因此 std 奇偶性

傳送門:>Here<

題意:有N根柱子,並且有連續編號的小球依次放入。要求後來的小球只能放在某根柱子最上面的小球上面,並且必須滿足這兩個小球的編號之和為完全平方數。求最多能放幾個小球?

思路分析

真是好題~

由於N的範圍不到60,所以我們可以采用非常暴力的做法。

把問題反過來考慮,如果有N個球,最少用幾個柱子?考慮DAG的最小路徑覆蓋——將能夠放在一起的(即和為完全平方數)的小球之間連有向邊(小的連到大的),這樣的一張圖的最小路徑覆蓋也就是需要的最少的柱子。因此問題就轉化為了最小路徑覆蓋問題——拆點+最大流。

因此我們只需要枚舉小球的數量,每一次把新來的小球拆成兩個,分別連源點和匯點,並且掃一遍連完全平方數。然後每一次做一遍Dinic(千萬註意flow要清零),判斷是否夠N根柱子。如果大於N,則跳出。去除這一次多余的邊重新建圖跑Dinic,輸出方案即可

現在有一個問題,為什麽柱子的需求量與小球數量成正比?其實很簡單,新加進去一個球並沒有可能性讓柱子的數量減少。

Code

典型的細節題:註意在統計的時候由於編號是偶數,每次+2,而不是*2!

另外,去除最後多余的邊是個問題。我們可以考慮重新弄一張鄰接表,每一次如果成功就把所有邊賦值一遍。註意first隨時都會修改,所以所有點的first都需要更新。最後在記錄sec的時候要記得-1,分清奇偶性。

/*By DennyQi*/
#include <cstdio>
#include <queue>
#include <cstring>
#include 
<algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; const int MAXN = 100010; const int MAXM = 300010; const int INF = 1061109567; inline int read(){ int x = 0; int w = 1; register int
c = getchar(); while(c ^ - && (c < 0 || c > 9)) c = getchar(); if(c == -) w = -1, c = getchar(); while(c >= 0 && c <= 9) x = (x << 3) +(x << 1) + c - 0, c = getchar(); return x * w; } int P,N,S,T,x,y,match; int first[MAXM*2],nxt[MAXM*2],to[MAXM*2],cap[MAXM*2],flow[MAXM*2],num_edge=-1; int _first[MAXM*2],_nxt[MAXM*2],_to[MAXM*2],_cap[MAXM*2],_flow[MAXM*2],last=-1; int level[MAXN],cur[MAXN],pre[MAXN],sec[MAXN]; queue <int> q; inline void add(int u, int v, int c, int f){ to[++num_edge] = v; cap[num_edge] = c; flow[num_edge] = f; nxt[num_edge] = first[u]; first[u] = num_edge; } inline void AddEdge(int u, int v){ add(u*2, v*2+1, 1, 0); add(v*2+1, u*2, 0, 0); } inline bool BFS(){ memset(level, 0, sizeof(level)); while(!q.empty()) q.pop(); q.push(S); level[S] = 1; int u,v; while(!q.empty()){ u = q.front(); q.pop(); for(int i = first[u]; i != -1; i = nxt[i]){ v = to[i]; if(!level[v] && cap[i]-flow[i] > 0){ level[v] = level[u] + 1; q.push(v); } } } return level[T] != 0; } int DFS(int u, int a){ if(u == T || a == 0) return a; int ans = 0, v, _f; for(int& i = cur[u]; i != -1; i = nxt[i]){ v = to[i]; if(level[u]+1 == level[v] && cap[i]-flow[i] > 0){ _f = DFS(v, Min(a, cap[i]-flow[i])); ans += _f, a -= _f; flow[i] += _f, flow[i^1] -= _f; if(a == 0) break; } } return ans; } inline void Dinic(){ match = 0; while(BFS()){ for(int i = S; i <= T; ++i) cur[i] = first[i]; match += DFS(S, INF); } match = N - match; } int main(){ P=r; S = 0, T = 12345; memset(first,-1,sizeof(first)); for(N = 1; ; ++N){ last = num_edge; memset(flow, 0, sizeof(flow)); for(int i = 1; i < N; ++i){ if(i*i - N >= N) break; if(i*i > N){ AddEdge(i*i-N, N); } } add(S, N*2, 1, 0), add(N*2, S, 0, 0); add(N*2+1, T, 1, 0), add(T, N*2+1, 0, 0); Dinic(); if(match > P){ --N; break; } for(int i = 0; i <= num_edge; ++i){ _nxt[i] = nxt[i]; _cap[i] = cap[i]; _flow[i] = flow[i]; _to[i] = to[i]; } for(int i = 0; i <= N*2+4; ++i){ _first[i] = first[i]; } _first[T] = first[T]; } printf("%d\n", N); for(int i = 2; i <= 2*N; i += 2){ for(int j = _first[i]; j != -1; j = _nxt[j]){ if(_cap[j]-_flow[j]==0 && _cap[j]==1){ pre[_to[j]-1] = i; sec[i] = _to[j]-1; } } } for(int i = 2; i <= 2*N; i += 2){ if(pre[i] == 0){ int u = i; while(sec[u] != 0){ printf("%d ", u/2); u = sec[u]; } printf("%d\n", u/2); } } return 0; }

「網絡流24題」魔術球問題