网络流24题4. 魔术球问题

魔术球问题

Description

假设有 n 根柱子,现要按下述规则在这 n 根柱子中依次放入编号为 1,2,3,…的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何 2 个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4 根柱子上最多可放 11 个球。
对于给定的 n,计算在 n 根柱子上最多能放多少个球。

Input

文件第 1 行有 1 个正整数 n,表示柱子数。

Output

将 n 根柱子上最多能放的球数以及相应的放置方案输出。第一行是球数。接下来的 n 行,每行是一根柱子上的球的编号。

题解

直接求解较为困难,考虑转化为判定性问题,即在n根柱子上能不能放a个球。
a个球在柱子上从下到上必然是从小到大的,那么两个球如果能放在一起(和伟完全平方数)那么就将他们之间连一条从小编号指向大编号的有向边,如此一来,每根柱子可以看做是这个途中的一条路径,而用最小路径覆盖就可以求出最少需要的柱子数量。那么我们从小到大枚举a,一旦最小路径覆盖数大于n,那么a-1就是答案。方案也只需找到有向图对应的二分图中的匹配即可。
在实际实现的过程中,从小到大枚举a,每次只需要在上次计算过的残量网络中添加一些边再继续增广即可,比二分答案然后每次重新建图的时间复杂度小许多。

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 5000 + 10, M = 500000 + 10, inf = 0x3f3f3f3f;

struct Edge{
    int fr, to, cap, flow;
}edg[M];
int hd[N], nxt[M];
int d[N], vis[N], q[N], dfn;
int s, t;
int n, ans, tot;
bool is_sq[N];
void insert(int u, int v, int w){
    edg[tot].fr = u, edg[tot].to = v, edg[tot].cap = w;
    nxt[tot] = hd[u], hd[u] = tot;
    tot++;
    edg[tot].fr = v, edg[tot].to = u;
    nxt[tot] = hd[v], hd[v] = tot;
    tot++;
}
bool bfs(){
    int head = 1, tail = 1;
    q[1] = s; vis[s] = ++dfn; d[s] = 0;
    while(head <= tail){
        int u = q[head++];
        for(int i = hd[u]; i >= 0; i = nxt[i]){
            Edge &e = edg[i];
            if(vis[e.to] == dfn || e.cap <= e.flow) continue;
            vis[e.to] = dfn;
            d[e.to] = d[u] + 1;
            q[++tail] = e.to;
        }
    }
    return vis[t] == dfn;
}
int dfs(int x, int a){
    if(x == t || a == 0) return a;
    int flow = 0, f;
    for(int i = hd[x]; i >= 0; i = nxt[i]){
        Edge &e = edg[i];
        if(d[e.to] == d[x] + 1 && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0){
            flow += f;
            e.flow += f;
            edg[i^1].flow -= f;
            a -= f;
            if(a == 0) break;
        }
    }
    return flow;
}
void work(){
    scanf("%d", &n);
    memset(hd, -1, sizeof(hd));
    s = 0; t = 1;
    for(int i = 1; i * i <= 5000; i++) is_sq[i*i] = 1;
    for(int a = 1, tmp = 1; ; a++, tmp++){
        insert(s, a<<1, 1);
        insert(a<<1|1, t, 1);
        for(int i = 1; i < a; i++)
            if(is_sq[a+i]) insert(i<<1, a<<1|1, 1);
        while(bfs())
            tmp -= dfs(s, inf);
        if(tmp > n){
            printf("%d\n", a - 1);
            break;
        }
    }
}

int main(){
    freopen("prog84.in", "r", stdin);
    freopen("prog84.out", "w", stdout);
    work();
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值