魔術球問題

魔術球問題

題目鏈接

題目描述

«問題描述:

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

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

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

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

«編程任務:

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

輸入輸出格式

輸入格式

\(1\)行有\(1\)個正整數\(n\),表示柱子數。

輸出格式

程序運行結束時,將\(n\)根柱子上最多能放的球數以及相應的放置方案輸出。文件的第一行是球數。接下來的\(n\)行,每行是一根柱子上的球的編號。

輸入輸出樣例

輸入樣例

4

輸出樣例

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

\(Solution\)

第一眼沒思路,第二眼還是沒有。

沒思路想耍流氓二分。似乎可以,顯然柱子越多,放的球越多(至少不會更少),滿足單調性。那就二分答案判斷唄。

怎麼判斷呢?

鑑於這是一道網絡流的題。。。

圖怎麼建?什麼情況下,兩個點之間能有連邊?滿足兩點標號之和爲完全平方數。數範圍不大,可以\(n^2\)枚舉。但僅僅這樣似乎不行。因爲操作對象是點,不是邊,因此要將點拆開。拆開之後,一邊鏈接起點,拆出來的點鏈接終點。至於點與點之間,則是小的鏈接大的。邊權都是\(1\)

然後就是最大流板子。找到答案之後,再處理每條邊上的點就好了。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <queue>
#define INF 10000000
using namespace std;
long long read(){
    long long x = 0; int f = 0; char c = getchar();
    while(c < '0' || c > '9') f |= c == '-', c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return f? -x:x;
}

int n, s, t;

struct szh{
    int to, next, w;
    inline void clear(){to = next = w = 0;}
    //因爲要建很多遍圖,所以要清空
}a[400000];
int cnt = 1, hd[10000];
inline void add(int u, int v, int w){//連邊
    a[++cnt].to = v, a[cnt].w = w, a[cnt].next = hd[u], hd[u] = cnt;
    a[++cnt].to = u, a[cnt].w = 0, a[cnt].next = hd[v], hd[v] = cnt;
}

int Q[10000], dis[10000];
queue<int> q;
bool bfs(){//求增廣路
    memset(dis, 0, sizeof dis);
    dis[s] = 1, q.push(s);
    while(!q.empty()){
        int u = q.front(); q.pop(); 
        for(int i = hd[u], v; v = a[i].to, i; i = a[i].next)
            if(a[i].w > 0 && !dis[v]) dis[v] = dis[u] + 1, q.push(v);
    }
    return dis[t];
}
int dfs(int u, int f){//增廣
    if(u == t || !f) return f;
    int ans = 0;
    for(int i = hd[u], v;v = a[i].to, i; i = a[i].next)
        if(a[i].w > 0 && dis[v] == dis[u] + 1){
            int x = dfs(v, min(f, a[i].w));
            a[i].w -= x; a[i ^ 1].w += x, ans += x;
            if(!(f -= x)) break;
        }
    return ans;
}

int mf;
void dinic(){//板子
    mf = 0;
    while(bfs()) mf += dfs(s, INF);
}

bool check(int x){
    for(int i = 0; i < 100000; ++i) a[i].clear();//初始化
    memset(hd, 0, sizeof hd);
    cnt = 0, s = 0, t = 2 * x + 1;
    for(int i = 1; i <= x; ++i){//建圖
        add(s, i, 1); add(i + x, t, 1);
        for(int j = i + 1; j <= x; ++j)//連邊
            if(i + j == (int) sqrt(i + j) * sqrt(i + j)) add(i, j + x, 1);
    }
    dinic();
    return x - mf <= n;
}

int nxt[10000];
bool use[10000];
int main(){
    n = read();
    int l = 1, r = 2000, mid, ans = 0;
    while(l <= r){//二分
        mid = (l + r) >> 1;
        if(check(mid)) ans = mid, l = mid + 1;
        else r = mid - 1;
    }
    printf("%d\n", ans);
    check(ans);
    for(int i = 1; i <= ans; ++i){
        for(int j = hd[i], v; v = a[j].to, j; j = a[j].next){
            if(v == s) continue;
            if(!a[j].w) nxt[i] = v - ans;//記錄路徑
        }
        for(int j = hd[i + ans], v; v = a[j].to, j; j = a[j].next){
            if(v != t) continue;
            if(!a[j].w) use[i] = 1;//排除非起點
        }
    }
    for(int i = 1; i <= ans; ++i){
        if(use[i]) continue;
        int u = i;
        while(u){//從編號最小的點開始輸出
            printf("%d ", u); u = nxt[u];
        }
        printf("\n");
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章