【洛谷】P2765 魔术球问题(网络流)

题目描述

«问题描述:

假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,…的球。

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

(2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在n根柱子上最多能放多少个球。例如,在4 根柱子上最多可放11 个球。

«编程任务:

对于给定的n,计算在n根柱子上最多能放多少个球。

输入输出格式

输入格式:
第1 行有1个正整数n,表示柱子数。

输出格式:
程序运行结束时,将n 根柱子上最多能放的球数以及相应的放置方案输出。文件的第一行是球数。接下来的n行,每行是一根柱子上的球的编号。

输入输出样例

输入样例#1: 复制
4
输出样例#1: 复制
11
1 8
2 7 9
3 6 10
4 5 11

【think】
刚开始的时候,我不知道该怎么处理那个球数量不一定的情况。
后来发现可以每次都跑一遍dinic,假如有超过柱子数量的小球不能够找到之前的点,就可以跳出了。
建图:
之前选择一个较大的数,对于不超过55的柱子数量,小球个数的最大。。maxn
源点:S(0)
汇点:T(2*maxn+1)
将每一个小球都要拆分成两个,也就是拆点。
然后分别与源点和汇点,建立容量为1的边。
每次增加完一个小球之后,都要跑一遍dinic,判断是否符合条件。

#include<bits/stdc++.h>
#define MAX 600000
#define INF 0x3f3f3f3f
using namespace std;
int level[MAX];
int iter[MAX];
int maxn = 72312;
int t = maxn*2+1;
struct edge
{
    int to, cap, rev;
};
int next[MAX];
int vis[MAX];
vector<edge>G[MAX];
void add_edge(int from, int to, int cap)
{
    struct edge e;
    e.to = to;
    e.cap = cap;
    e.rev = G[to].size();
    G[from].push_back(e);
    e.to = from;
    e.cap = 0;
    e.rev = G[from].size()-1;
    G[to].push_back(e);
}
void bfs(int s)
{
    memset(level, -1, sizeof(level));
    level[s] = 0;
    queue<int>que;
    que.push(s);
    while(!que.empty())
    {
        int v = que.front();
        que.pop();
        for(int i=0; i<G[v].size(); i++)
        {
            edge &e = G[v][i];
            if(e.cap>0&&level[e.to]<0)
            {
                level[e.to] = level[v]+1;
                que.push(e.to);
            }
        }
    }
}
int dfs(int v, int t, int flow)
{
    if(v==t)
        return flow;
    for(int &i=iter[v]; i<G[v].size(); i++)
    {
        edge & e = G[v][i];
        if(e.cap>0&&level[e.to]>level[v])
        {
            int d = dfs(e.to, t, min(flow, e.cap));
            if(d>0)
            {
                e.cap -= d;
                G[e.to][e.rev].cap += d;
                return d;
            }
        }
    }
    return 0;
}
int max_flow(int s, int t)
{
    int flow = 0;
    for(;;)
    {
        bfs(s);
        if(level[t]<0)
            return flow;
        memset(iter, 0, sizeof(iter));
        int f;
        while((f=dfs(s, t, INF))>0)
        {
            flow += f;
        }
    }
}
int main()
{
    int n;
    scanf("%d", &n);//n个柱子
    int num = 0, ans=0;
    for(;;)
    {
        ans++, num++;//num代表小球的个数
        add_edge(0, num, 1);
        for(int i=1;i<num;i++)
        {
            if(sqrt(i+num)==(int)sqrt(i+num))
            {
                add_edge(i, num+maxn, 1);
            }
        }
        add_edge(num+maxn, t, 1);
        int f = max_flow(0, t);//每次都要跑dinic,找到边的数量
        ans -= f;
        if(ans>n)//如果ans现在的个数大于柱子数量,说明会有小球没有地方放。
            break;
    }
    printf("%d\n", num-1);
    memset(next, 0, sizeof(next));
    for(int i=maxn;i<maxn+num;i++)
    {
        for(int j=0;j<G[i].size();j++)
        {
            edge & e= G[i][j];
            if(e.cap>0)
            {
                next[e.to] = i-maxn;
            }
        }
    }
    memset(vis, 0, sizeof(vis));
    for(int i=1;i<num;i++)
    {
        if(vis[i]==0)
        {
            vis[i] = 1;
            printf("%d", i);
            int a  = i;
            while(next[a])
            {
                printf(" %d", next[a]);
                vis[next[a]] = 1;
                a = next[a];
            }
            printf("\n");
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值