網絡流與線性規劃24題04魔術球問題

問題描述:
假設有n根柱子,現要按下述規則在這n根柱子中依次放入編號爲1,2,3,...的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何2個相鄰球的編號之和爲完全平方數。
試設計一個算法,計算出在n根柱子上最多能放多少個球。例如,在4 根柱子上最多可
放11 個球。
編程任務:
對於給定的n,計算在n根柱子上最多能放多少個球。
數據輸入:

由文件input.txt提供輸入數據。文件第1 行有1個正整數n,表示柱子數。
結果輸出:
程序運行結束時,將n 根柱子上最多能放的球數以及相應的放置方案輸出到文件
output.txt中。文件的第一行是球數。接下來的n行,每行是一根柱子上的球的編號。

輸入示例 :

4

輸出示例 :

11
1 8
2 7 9
3 6 10
4 5 11

分析:

這裏有一個明顯的二元關係,就是i和j能否組成完全平方數。

每個柱子的球滿足這樣的特徵,相鄰的球能組成完全平方數,如果用弧表示,就是一個柱子代表一條連續的路徑,那麼這個問題就轉化爲最小路徑覆蓋了。

最小路徑覆蓋見我03最小路徑覆蓋的博文。

該題解法是枚舉答案x,然後求出最小路徑覆蓋看是否滿足小於等於n,如果滿足就增大x,同時增加相應的弧,再求。如果不滿足,則x-1爲答案。

下面建立二分圖,對於一個頂點X,分成拆兩個頂點出發點X‘和目標點X’‘。我設定了一個常量MAX,點X的出發點爲X,目標點爲MAX-X。

對於j>i,如果i和j能組成完全平方數,則添加弧<i,j>到圖中。還有要注意的是,因爲是順序放球,j是不能在i的下面的,所以不能加弧<j,i>。

另外這一題也可以二分答案,但是二分需要重新建圖,而枚舉,則只需要在之前的殘量網絡增加對應的弧,然後再增廣一次即可,所以還是用枚舉比較快。

代碼寫的很粗陋,這一次求二分圖用的是剛學的ISAP算法,另外多寫了一個逆向的BFS求路徑的過程,不寫也無所謂,時間相差不大。

學網絡流到現在,還是感覺最簡單的鄰接矩陣+BFS找增廣路徑的算法最好寫,可惜就是慢了點。啊啊啊啊啊,該死的鄰接表,DINIC和ISAP,還有沒學的那個什麼預流推進算法。坑爹 呢這是。

ISAP求最大流:

#include<cstdio>
#include<queue>
#include<vector>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN=4003;
const int MAX=4000;
struct edge{
    int from,to,cap,flow;
    edge(int a,int b,int c,int d):from(a),to(b),cap(c),flow(d)
    {}
};

vector<edge> edges;
vector<int> g[MAXN];
int d[MAXN];
int p[MAXN];
int num[MAXN];
int visit[MAXN];
int cur[MAXN];
bool issquare[MAXN*MAXN>>2];
void add(int from,int to,int cap)
{
    edges.push_back(edge(from,to,cap,0));
    edges.push_back(edge(to,from,0,0));
    int m=edges.size();
    g[from].push_back(m-2);
    g[to].push_back(m-1);
}
inline void build(int n)//對於球的數目從n-1到n增加相應的弧
{
    for(int j=1;j<n;j++){
        if(issquare[n+j])
            add(j,MAX-n,1);
    }
}
void BFS(int t,int s)//逆向BFS
{

    queue<int> q;
    d[t]=0;
    visit[t]=true;
    q.push(t);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=0;i<g[x].size();i++){
            edge & e=edges[g[x][i]^1];
            if(d[e.from]==-1&&e.cap>e.flow){
                d[e.from]=d[x]+1;
                q.push(e.from);
            }
        }
    }
}
int agument(int s,int t)//找到增廣路徑後,從匯點到源點找到最小可改進量然後逐個改進
{
    int x=t,a=1<<30;
    while(x!=s){
        edge &e=edges[p[x]];
        a=min(a,e.cap-e.flow);
        x=e.from;
    }
    x=t;
    while(x!=s){
        edges[p[x]].flow+=a;
        edges[p[x]^1].flow-=a;
        x=edges[p[x]].from;
    }
    return a;
}
int maxflow(int &flow,int n,int s,int t)//isap算法,坑爹的一塌糊塗。
{
    memset(d,-1,sizeof(d));
    memset(cur,0,sizeof(cur));
    build(n);
    n<<=1;
    BFS(t,s);
    int x=s;
    while(d[s]<n){
        if(x==t){
            flow+=agument(s,t);
            x=s;
        }
        bool flag=false;
        for(int i=cur[x];i<g[x].size();i++){
            edge &e=edges[g[x][i]];
            if(e.cap>e.flow&&d[x]==d[e.to]+1){
                flag=true;
                p[e.to]=g[x][i];
                cur[x]=i;
                x=e.to;
                break;
            }
        }
        if(!flag){
            int m=n-1;
            for(int i=0;i<g[x].size();i++){
                edge &e=edges[g[x][i]];
                if(e.cap>e.flow) m=min(m,d[e.to]+1);
            }
            if(--num[d[x]]==0) break;
            d[x]=m+1;
            num[d[x]]++;
            cur[x]=0;
            if(x!=s) x=edges[p[x]].from;
        }
    }
    return flow;
}
void print(int i,int s,int t)//輸出路徑
{
    visit[i]=true;
    for(int j=0;j<g[i].size();j++)
    {
        edge&e=edges[g[i][j]];
        if(!visit[e.to]&&e.to!=t&&e.flow==1){
            printf(" %d",MAX-e.to);
            print(MAX-e.to,s,t);
        }
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<MAXN;i++)
        g[i].clear();
    edges.clear();
    int s=0,t=MAXN-1;
    for(int i=1;i<MAXN/2;i++)
        issquare[i*i]=true;
    for(int i=1;i<=MAX/2-1;i++)//從源點到所有出發點加弧,從所有目標點到匯點加弧
        add(s,i,1),add(MAX-i,t,1);
    int flow=0;
    int x=1;
    while(x-maxflow(flow,x,s,t)<=n)//求x個球的最小路徑覆蓋
        x++;
    printf("%d\n",x-1);
    for(int i=1;i<x;i++)
        if(!visit[i])
            printf("%d",i),print(i,s,t),printf("\n");
    return 0;
}


發佈了34 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章