洛谷 P2765 魔术球问题

190 篇文章 2 订阅
25 篇文章 0 订阅

题面

题意

有n个柱子,编号为1,2…….的小环,要将它们依次套在环上,要求直接接触的两小球的和为完全平方数,那么最多可以套几个小球。

做法

首先可以贪心,如果可以套在其他小球上,则套在其他小球上,反之,套在柱子上,直到没有多余柱子,可以证明这是对的,但我觉得还是网络流的做法比较重要。
因为要依次取小球,那么当答案为ans时,编号为1-ans的小球都被取走,我们可以用类似于洛谷 P1251 餐巾计划问题的做法来拆点建图。
将每个点拆成a,b两点,因为放环有两种可能,从s连一条边到a,流量为1,表示直接将小球放在柱子底部,a再向t连一条流量为1的边,保证该小球已经用过了,S向b连一条流量为1的边,因为该小球并不会随着它流到汇点而消失,b向编号大于它的且满足和为完全平方数的点的a连边,表示在这个小球上放小球。最后S向s连一条流量为柱子数的边,用于限制柱子数。
在实际操作时,因为并不知道答案为多少,为了保证它能依次被取走,故要枚举小球个数,不断的在残余网络上加入有关第i个小球的边,并在参与网络的基础上求最大流,若最大流不等于i,说明i-1为最优解。因为除了S->s的边外,边权均为一,因而可以枚举边来计算流量。
另外此题不可以二分答案来做,因为从小到大枚举小球个数时可以利用残余网络加速,而二分则每次都要重新建图,反而更劣。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF 0x3f3f3f3f
#define N 4100
using namespace std;

int n,m,bb,first[N],deep[N],tmp,s,t,sum,ans,cur[N];
bool out[N];
struct Bn
{
    int next,to,quan;
} bn[200100];
queue<int>que;

inline void add(int u,int v,int w)
{
    bn[bb].to=v;
    bn[bb].quan=w;
    bn[bb].next=first[u];
    first[u]=bb;
    bb++;
}

inline void ad(int u,int v,int w)
{
    add(u,v,w);
    add(v,u,0);
}

inline bool bfs()
{
    int p,q;
    for(; !que.empty(); que.pop());
    memset(deep,0,sizeof(deep));
    deep[s]=1;
    que.push(s);
    for(; !que.empty();)
    {
        q=que.front();
        que.pop();
        for(p=first[q]; p!=-1&&!deep[t]; p=bn[p].next)
        {
            if(deep[bn[p].to]||!bn[p].quan) continue;
            deep[bn[p].to]=deep[q]+1;
            que.push(bn[p].to);
        }
    }
    return deep[t];
}

int dfs(int now,int mn)
{
    if(now==t)
    {
        return mn;
    }
    int res;
    for(int &p=cur[now]; p!=-1; p=bn[p].next)
    {
        if(deep[bn[p].to]!=deep[now]+1||!bn[p].quan) continue;
        res=dfs(bn[p].to,min(bn[p].quan,mn));
        if(res)
        {
            bn[p].quan-=res;
            bn[p^1].quan+=res;
            return res;
        }
    }
    return 0;
}

int main()
{
    memset(first,-1,sizeof(first));
    int i,j,p,q,o,z;
    cin>>n;
    s=4001,t=4002;
    for(i=1; i*i<=4000; i++)
    {
        for(j=1; j<=i*i/2; j++)
        {
            if(i*i-j<=2000&&j<i*i-j)
                ad(j+2000,i*i-j,1);
        }
    }
    ad(s,0,n);
    for(i=1; i<=2000; i++)
    {
        ad(0,i,1);
        ad(s,i+2000,1);
        ad(i,t,1);
        for(; bfs();)
        {
            for(j=0; j<=t; j++) cur[j]=first[j];
            for(p=dfs(s,INF); p; ans+=p,p=dfs(s,INF));
        }
        if(ans!=i) break;
    }
    cout<<i-1<<endl;
    for(j=1;j<i;j++)
    {
        if(out[j]) continue;
        printf("%d ",j);
        for(q=j;;)
        {
            for(p=first[q+2000];p!=-1;p=bn[p].next)
            {
                if(!bn[p].quan&&bn[p].to>=1&&bn[p].to<=2000)
                {
                    q=bn[p].to;
                    out[q]=1;
                    printf("%d ",q);
                    break;
                }
            }
            if(p==-1) break;
        }
        puts("");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值