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

 1739:魔術球問題

Time Limit:1000MS  Memory Limit:65536K
Total Submit:39 Accepted:20 Page View:230 Special Judged

[Submit]   [Status]   [Discuss]

Font Size: Aa Aa Aa

Description

假設有n根柱子,現要按下述規則在這n根柱子中依次放入編號爲1,2,3,...的球。

(1)每次只能在某根柱子的最上面放球。

(2)在同一根柱子中,任何2個相鄰球的編號之和爲完全平方數。

試設計一個算法,計算出在n根柱子上最多能放多少個球。例如,在4 根柱子上最多可

放11 個球。

編程任務:

對於給定的n,計算在n根柱子上最多能放多少個球。

Input

由文件input.txt提供輸入數據。文件第1 行有1個正整數n(n<=55),表示柱子數。

Output

程序運行結束時,將n 根柱子上最多能放的球數以及相應的放置方案輸出到文件

output.txt中。文件的第一行是球數。接下來的n行,每行是一根柱子上的球的編號。

Sample Input

4

Sample Output

11

1 8

2 7 9

3 6 10

4 5 11


【問題分析】
枚舉答案轉化爲判定性問題,然後最小路徑覆蓋,可以轉化成二分圖最大匹配,從而用最大流解決。
【建模方法】
枚舉答案A,在圖中建立節點1..A。如果對於i<j有i+j爲一個完全平方數,連接一條有向邊(i,j)。該圖是有向無環圖,求最小路徑覆蓋。如果剛好滿足最小路徑覆蓋數等於N,那麼A是一個可行解,在所有可行解中找到最大的A,即爲最優解。
具體方法可以順序枚舉A的值,當最小路徑覆蓋數剛好大於N時終止,A-1就是最優解。
【建模分析】
由於是順序放球,每根柱子上的球滿足這樣的特徵,即下面的球編號小於上面球的編號。抽象成圖論,把每個球看作一個頂點,就是編號較小的頂點向編號較大的頂點連接邊,條件是兩個球可以相鄰,即編號之和爲完全平方數。每根柱子看做一條路徑,N根柱子要覆蓋掉所有點,一個解就是一個路徑覆蓋。
最小路徑覆蓋數隨球的數量遞增不遞減,滿足單調性,所以可以枚舉答案(或二分答案),對於特定的答案求出最小路徑覆蓋數,一個可行解就是最小路徑覆蓋數等於N的答案,求出最大的可行解就是最優解。本問題更適合枚舉答案而不是二分答案,因爲如果順序枚舉答案,每次只需要在殘量網絡上增加新的節點和邊,再增廣一次即可。如果二分答案,就需要每次重新建圖,大大增加了時間複雜度。


代碼:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define maxn 100000
#define eps 0.000000001
#define INF 1 << 30
using namespace std;
struct E
{
    int from, to, next, tab;
}edge[200000];
int tot, s, t, n, ans, K;
int head[maxn], d[maxn], dist[maxn], vis[maxn];
   
void add_edge(int u, int v, int f)
{
    edge[tot].from = u, edge[tot].to = v, edge[tot].tab = f, edge[tot].next = head[u], head[u] = tot++;
    edge[tot].from = v, edge[tot].to = u, edge[tot].tab = 0, edge[tot].next = head[v], head[v] = tot++;
}
   
int dfs(int x, int low)
{
    int a, b = 0;
    if (x == t) return low;
    for (int i = head[x]; i != -1 && low; i = edge[i].next)
        if (edge[i].tab > 0 && dist[edge[i].to] == dist[x] + 1 && (a = dfs(edge[i].to, min(low, edge[i].tab))))
        {
            edge[i].tab -= a, edge[i ^ 1].tab += a;
            low -= a;
            b += a;
        }
    return b;
}
   
int bfs()
{
    int l, r, k;
    memset(dist, 0xff, sizeof(dist));
    l = r = dist[s] = 0;
    d[r++] = s;
    while (l < r)
    {
        k = d[l++];
        for (int i = head[k]; i != -1; i =edge[i].next)
            if (edge[i].tab > 0 && dist[edge[i].to] < 0)
            {
                dist[edge[i].to] = dist[k] + 1;
                if (edge[i].to == t) return 1;
                d[r++] = edge[i].to;
            }
    }
    return 0;
}
   
void dinic()
{
    while (bfs()) ans += dfs(s, INF);
}
   
int check(int m, int n)
{
    int sum = m+n;
    double dsum = sqrt(sum);
    int tem = (int)dsum;
    if (dsum-tem < eps) return 1; else return 0;
}
   
void print(int u, int clr)
{
    vis[u] = clr;
    int v, flag = 1;
    for (int i = head[u*2]; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if (edge[i].tab == 0 && v!= s) 
        {
            flag = 0;
            break;
        }
    }
    if (!flag) print(v/2, clr);
}
   
int main()
{
    memset(head, 0xff, sizeof(head));
    scanf("%d", &K);
    n = ans = s = 0, t = 1;
    tot = 0;
    while (n-ans <= K)
    {
        n++;
        add_edge(s, n*2, 1);
        add_edge(n*2+1, t, 1);
        for (int i = 1; i < n; i++) if (check(i, n)) add_edge(i * 2, n * 2 + 1, 1);
        dinic();
    }
    n--;
    printf("%d\n", n);
    memset(vis, 0xff, sizeof(vis));
    int clr = 0;
    for (int j = 1; j <= n; j++) if (vis[j] == -1) print(j, clr++);
    for (int k = 0; k < clr; k++)
    {
        int first = 1;
        for (int j = 1; j<= n; j++)
            if (vis[j] == k)
            {
                if (first) printf("%d", j), first = 0; else printf(" %d", j);
            }   
        printf("\n");
    }
    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章