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

18 篇文章 0 订阅
7 篇文章 0 订阅

首先我们考虑如何建图,即表达相邻球之间的关系。
可以将一个球拆点为Ai和Bi,先从源点S向Ai连容量为1的边,从B_i 向汇点连容量为1的边。对于能够与它编号和为完全平方数的球j,连接A_j和Bi。
枚举球数,球数每增加1就建立新加入的球的关系,并且重复地跑最大流。柱子数对于球数存在一种单调递增的相关关系,我们这样可以求出某一柱子数下最多能放置的球数,因为当新加入的球能够加入柱子时,重复跑最大流是能得到新流(即:该球可与其他球构成新的相邻关系)的,只要一直能得到新流,就说明柱子上还可以再加,当有一次得不到新流,就说明柱子满了,新加入的球并没能加入到任何一个柱子上。此时我们就加柱子。直到柱子加到超过n,此时的球数-1就是最大球数(因为此时实际上柱子加到超过n了)。

说法2:
观察本题,对于一个进来的编号的球,他有两种情况,
1.放在某个和他组成平方数的球的后面
2.独立门户
我们要使这两种情况都合法,这样我们可以发挥网络流调整的优势
对于两种情况,为了使结果合法,我们姑且先将它和t直接相连
情况2显然要和s相连,情况1要和前面的点相连
又和s相连,又和t相连,还要和别的点相连,显然一个点是不够的
我们把一个点分开来,分开来的两个点不能相连,否则最大流就没有意义了
直接s-u-u’-t显然没有调整的作用
那么我们将u和s相连,u’和t相连,为了满足第一种条件
满足关系的两个点u、v,建立v-u’
那么在图中跑最大流算法即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;
struct edge{
    int to,cap,rev;
};
vector<edge>G[21000];
int level[20500],iter[20500];bool vis[20500];
void addedge(int from,int to,int cap)
{
    G[from].push_back(edge{to,cap,(int)G[to].size()});
    G[to].push_back(edge{from,0,(int)G[from].size()-1});//反向容量为0!!
}
void bfs(int s)
{
    memset(level,-1,sizeof(level));
    queue<int>que;
    level[s]=0;que.push(s);
    while(!que.empty()){
        int t=que.front();que.pop();
        for(int i=0;i<G[t].size();i++){
            edge e=G[t][i];
            if(e.cap>0&&level[e.to]<0){
                level[e.to]=level[t]+1;
                que.push(e.to);
            }
        }
    }
}
int dfs(int v,int t,int f)
{
    if(v==t)return f;
    for(int&i=iter[v];i<G[v].size();i++){//注意传引用!
        edge&e=G[v][i];
        if(e.cap>0&&level[v]<level[e.to]){
            int d=dfs(e.to,t,min(f,e.cap));
            if(d>0){
                e.cap-=d;
                G[e.to][e.rev].cap+=d;
                return d;
            }
        }
    }
    return 0;//不要漏了这个,很多时候可能是无法增广的
}
int maxflow(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,0x7f7f7f7f))
            flow+=f;
    }
}
int main()
{
    int n,i,j,k,cnt;
    cin>>n;
    int ball;
    for(cnt=0,ball=1;cnt<=n;cnt++){
        if(cnt){
            ball++;//maxflow(0,20000);
        }
        for(;;ball++){
            addedge(0,ball,1);addedge(ball+10000,20000,1);//addedge(ball,ball+10000,1);
            for(i=1;i<ball;i++){
                if(sqrt(ball+i)==(int)sqrt(ball+i)){
                    addedge(i,ball+10000,1);
                }
            }
            if(!maxflow(0,20000))break;
        }
    }
    cout<<ball-1;
    for(i=1;i<=ball-1;i++){//输出路径类似于线性表的玩法
        if(vis[i])continue;
        vis[i]=true;cout<<endl<<i<<' ';k=i;
        while(1) {
            bool find=false;
            for (auto j = G[k].begin(); j != G[k].end(); j++) {
                edge e = *j;
                if (e.to > 10000 && e.cap == 0) {//一定要加上第一个判断条件,否则可能混入反向边
                    cout << e.to - 10000 << ' ';
                    vis[e.to - 10000] = true;
                    k = e.to - 10000;find=true;
                    break;
                }
            }
            if(!find)break;
        }
    }

    return 0;
}

另:本题简单的贪心也能过,不过不保证完全正确;
贪心策略:用二维数组记录每个柱子上的元素,按顺序枚举,将新元素与每个柱顶元素比较判断是否合法,合法就加入该柱子,否则新开一个柱子,若当前柱子数超过题目要求,则退出,输出答案,然后将二维数组记录的答案输出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值