1. 程式人生 > >[網路流24題] 魔術球問題

[網路流24題] 魔術球問題

Description

給定\(n(n\leq 55)\)個柱子,需要依次將編號為\(1,2,3...\)的球放上柱子,規定一個球能放上柱子當且僅當柱子為空或這個球的編號與柱子最上面球的編號的和為完全平方數。問最多能放幾個球。輸出方案。

Solution

先吐槽一句,網上用網路流寫的題解都tm太不詳細了,全是瞎jb寫,寫題解能不能有點責任心。氣tm死了,一上午都在看寫的不清楚的垃圾題解。 先說網上瞎jb講的題解,都是說對於每個球拆點,然後源點連一個,匯點連一個,中間不連。感覺網路流最重要的就是講清楚建圖的原因和代表的意義,然而網上根本沒有講為啥要這樣連,這裡講一下這樣連的意義是什麼。 我們先不要拿看待網路流的眼光看這題。我們把這\(m\)

個球(假設有m個球)看成圖上的一堆點,如果\(i<j\;and\;i+j\)是個完全平方數,那麼我們在(i,j)連一條邊。可以發現,如果用這\(n\)個柱子可以放下\(m\)個球的話,那麼肯定存在\(n\)條路徑完全覆蓋了這\(m\)個點。 路徑覆蓋?這就跟二分圖扯上關係了。回想我們求最小路徑覆蓋的方法,把每個點拆成入點\(i<<1\)和出點\(i<<1|1\),如果\((i,j)\)有邊,那麼就在新圖中加一條\((i<<1|1,j<<1)\)的邊。求出最大匹配,用總點數減去最大匹配數就是最小路徑覆蓋了。給出一個小小的證明吧,因為最開始每個點都是獨立的,一共\(m\)
條路徑覆蓋了\(m\)個點。每找到一條增廣路,本質上都是合併了兩條路徑,所以路徑數減去1. 然後再回來看這題就比較顯然了,列舉可以放多少個球,然後求一下這些球的最小路徑覆蓋是否\(\leq n\),如果合法那就說明當前\(n\)個柱子放這麼多球是完全ojbk的,往下列舉就好了。 那這題為什麼放進網路流24題裡呢?我覺得本質上就是這個最小路徑覆蓋可以拿最大流求,其他的跟網路流建圖啥的一點關係都沒有。 再次鄙視網上的垃圾題解。

Code

#include<cstdio>
#include<cctype>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 5005
using std::min;
using std::max;
using std::swap;
const int inf=0x3f3f3f3f;

int d[N],s,t;
int stk[N],top;
int n,cnt,head[N];

struct Edge{
    int to,nxt,flow;
}edge[N*405];

void add(int x,int y,int z){
    edge[++cnt].to=y;
    edge[cnt].nxt=head[x];
    edge[cnt].flow=z;
    head[x]=cnt;
}

int getint(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch)) f|=ch=='-',ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return f?-x:x;
}
#include<queue>
bool bfs(){
    std::queue<int> q;q.push(s);
    memset(d,0,sizeof d);d[s]=1;
    while(q.size()){
        int u=q.front();q.pop();
        for(int i=head[u];i;i=edge[i].nxt){
            int to=edge[i].to;
            if(!edge[i].flow or d[to]) continue;
            d[to]=d[u]+1;
            q.push(to);
            if(to==t) return 1;
        }
    } return 0;
}

int dinic(int now,int flow){
    if(now==t) return flow;
    int res=flow;
    for(int i=head[now];i;i=edge[i].nxt){
        int to=edge[i].to;
        if(d[to]!=d[now]+1 or !edge[i].flow) continue;
        int k=dinic(to,min(res,edge[i].flow));
        if(!k) d[to]=0;
        edge[i].flow-=k;edge[i^1].flow+=k;res-=k;
        if(!res) return flow;
    } return flow-res;
}

int mf(){
    int ans=0,flow=0;
    while(bfs()) while(flow=dinic(s,inf)) ans+=flow;
    return ans;
}

void dfs(int now){
    printf("%d ",now>>1);
    for(int i=head[now];i;i=edge[i].nxt){
        int to=edge[i].to;
        if(to==t or to==s or edge[i].flow) continue;
        dfs(to|1);
    }
}
#include<cmath>
signed main(){
    n=getint();cnt=1;
    s=0;t=5000;int now=0;
    for(int i=1;i;i++){
        add(s,i<<1|1,1);add(i<<1|1,s,0);
        add(i<<1,t,1);add(t,i<<1,0);
        for(int j=sqrt(i)+1;j*j<(i<<1);j++)
            add(j*j-i<<1|1,i<<1,1),add(i<<1,j*j-i<<1|1,0);
        int k=mf();
        if(!k){
            if(now==n){
                printf("%d\n",i-1);
                break;
            } else stk[++now]=i;
        }
    }
    for(int i=1;i<=n;i++)
        dfs(stk[i]<<1|1),puts("");
    return 0;
}