魔术球问题【题解】

6 篇文章 0 订阅
4 篇文章 0 订阅

前言

具体的数学关系还是不会证,除了能打表发现球数规律外,内在原理也弄不清楚。

题面

sol

很难想象是一道网络流。第一感是数学。
但是看到数据范围比较小,估计可以暴力dp。
又发现,状态转移比较难,状态调整比较多,dp估计难设,又看到数据范围又小于dp通常可做范围,所以用同样以规划和状态设计为关键字的,状态调整能力更强的,复杂度可能略高的网络流来做。
实际上,这种题考点不在网络流的精深图论,而在于网络流的规划,计算,调整能力,也就是通常说的的建模能力。只有最后输出方案需要一点图论知识。
多啰嗦一句的是,网络流,建状态同样也有优化,尽量少的边与点,边权尽量更加集中(少建INF边)会优化速度.
然后这道题首先,每个点只能选一次,这就可以用一个套路,抱一个点拆成一条边权为1的边,那么该边就只能经过一次了。
然后算贡献的话,就通过原始dp来考虑。比如,写dp就会直接转移到一个与自己加起来为平方和的点,那么网络流也是可以流向一条与自己加起来为平方和的点,然后流入成功就算一次贡献。网络流算贡献就是向汇点T流入1的流量。
显然dp还是可以自己创一组,就是流向一个柱子,注意到柱子也只能被流一次,所以就用上述的套路,跟流向一个点是一样的。
然后源点肯定是相当于去激活每一个点,那么每一个点都可以被S激活,所以搞一个S流向每一个点的流量是1的边。
有人说跟最小点覆盖很像,其实就是用到了锁定一个点只能流一次这个套路。
然后注意到,这个题说了只能选满1-m个球,根据dp的套路就是一个一个考虑,然后大的只能放向小的,所以就从小的往大枚举的放就可以了,如果某一个小的放不进当前的状态,那么就break。
网络流还是胜在自己的调整能力。单用点与边来规划处所有的情况,模拟水流,就会有着较强的调整能力,这就使得他只需用当前这张图来表示当前的状态,而不用像dp那样存储下每一种状态。dp的调整能力也不强,当一种情况的加入会大量改变已有状态时,dp就会显的很麻烦,而网络流则会自动的调整了当前的状态。目前的网络流只能通过构建边与点的关系来构造,所有的套路很少,也只限于这一个点与边的圈子,而dp却显得更加丰富与综合。

code

#include<bits/stdc++.h>
using namespace std;
inline char gc(){
    static char buf[1<<7],*p1=buf,*p2=buf;
    return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<7,stdin),p1==p2)?EOF:*p1++;
}
template <class T>
inline void read(T&data){
    data=0;
    register char ch=0;
    while(ch<'0'||ch>'9')ch=gc();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch^48);
        ch=gc();
    }
    return;
}
template <class R>
inline void write(R data){
    if(data>9)write(data/10);
    putchar(data%10+'0');
}
const int _ = 200001,__  = 4000;
int n,to[_<<1],head[_],cur[_],S,T,nxt[_<<1],w[_<<1],cnt=-1,vnt,ball1[_],ball2[_],reball[_];
bool vis[_];
inline void add(register int a,register int b,register int c ){
    to[++cnt]=b,nxt[cnt]=head[a],head[a]=cnt,w[cnt]=c;
    to[++cnt]=a,nxt[cnt]=head[b],head[b]=cnt,w[cnt]=0;
}
int dfs(register int now,register int flow){
    if(now==T)return flow;
    vis[now]=1;
    for(register int i=head[now];~i;i=nxt[i]){
        if(w[i]==0)continue;
        if(vis[to[i]])continue;
        register int di=dfs(to[i],min(flow,w[i]));
        if(di>0){
            w[i]-=di,w[i^1]+=di;return di;
        }
    }
    return 0;
}
int main(){
    memset(head,-1,sizeof(head));
    read(n);//柱子的标号就是1到n了
    S=n+1,T=S+1;//这两个就这样定了吧注意球从什么开始标号
    vnt=T+1;
    for(register int i=1;i<=n;++i)
        add(i,T,1);
    register int ret=0,ans=0;
    for(register int i=1;1;++i){

        ball1[i]=++vnt,ball2[i]=++vnt;
        reball[vnt-1]=i;
        add(ball2[i],T,1);
        add(S,ball1[i],1);
        for(register int j=1;j<=n;++j)add(ball1[i],j,1);
        if(i!=1){
            for(register int j=1;j*j<(i<<1);++j){
                if(j*j<=i)continue;
                add(ball1[i],ball2[j*j-i],1);
            }
        }
        ret=dfs(S,1);
        memset(vis,0,sizeof(vis));      
        if(ret==0){
            ans=i-1;
            break;
        }
    }
    write(ans);puts("");
    for(register int i=1;i<=ans;++i){
        if(vis[i])continue;
        register int now = i;
        do{
            write(now);putchar(' ');vis[now]=1;
            for(register int j=head[ball2[now]];~j;j=nxt[j]){
                if(to[j]==T)continue;
                if(w[j^1]==0){
                    now=reball[to[j]];break;
                }
            }
        }while(!vis[now]);
        puts("");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值