[Luogu2765]魔术球问题
sshwy
2019-03-02 15:42:44
# 题意
有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球:
- 每次只能在某根柱子的最上面放球
- 在同一根柱子中,任何2个相邻球的编号之和为完全平方数
求在n根柱子上最多能放多少个球
<!--more-->
# 分析
把球看做点,显然两个和为完全平方数的球$x,y(x<y)$可以从$x$向$y$连一条边
但是,题目中的点是动态增加的,因此我们需要动态加边实现网络流
首先有一个贪心的思路(证明先咕着),就是能放就放,实在不行再开新的柱子
题目要求每个球只能使用一次且要顺次放入,那么考虑拆点
既然是动态加点,那么我们将$u$拆后的点标为$u\times 2$和$u\times 2+1$.
将$u$拆成$u_{s}$和$u_{t}$,源点与$u_s$直接连接,$u_t$与汇点直接连接
然后对于每个$v(v<u,u+v=k^2,k\in \mathbb{Z})$,我们将$v_s$和$u_t$连一条边
如果一个点$u$不在某一个柱子的顶端,那么$f(s,u_s)=0$,也就不会重复使用$u$;如果$u$在某个柱子的顶端,那么显然$f(s,u_s)>0$,这个点就可以使用
然后在图上跑最大流即可。如果没有增加的流,就新开一个柱子(其实不需要特别操作,记录一下这个柱子的开头即可)
答案的输出,在最大流的过程中维护链表即可
# 代码
```cpp
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
using namespace std;
const int N=10000,M=2e6;
int n,m,s,t,n1,INF=0x3f3f3f3f;
struct qxx{int nex,t,v;};
qxx e[M];
int h[N],cnt=1;
void add_path(int f,int t,int v){e[++cnt]=(qxx){h[f],t,v},h[f]=cnt;}
void add_flow(int f,int t,int v){add_path(f,t,v);add_path(t,f,0);}
int rk[N];
bool bfs(){
memset(rk,0,sizeof(rk));
queue<int> q;
q.push(s),rk[s]=1;
while(q.size()){
int u=q.front();q.pop();
for(int i=h[u];i;i=e[i].nex){const int &v=e[i].t,&w=e[i].v;
if(w&&!rk[v])rk[v]=rk[u]+1,q.push(v);
}
}
return rk[t];
}
int suf[N];
int dinic(int u,int flow){
if(u==t)return flow;
for(int i=h[u];i;i=e[i].nex){const int &v=e[i].t,&w=e[i].v;
if(w<=0||rk[v]!=rk[u]+1)continue;
int x=dinic(v,min(flow,w));
if(x)return e[i].v-=x,e[i^1].v+=x,(u==t?0:suf[u>>1]=(v>>1)),x;//>>1变回原来的编号
else rk[v]=0;
}
return 0;
}
int ans[N],ant;//链表表头
int main(){
scanf("%d",&n1);
s=0,t=N-1;
while(ant<=n1){
++n;//增加一个球
add_flow(s,n<<1,1);add_flow(n<<1|1,t,1);//分点
for(int i=sqrt(n)+1;i*i<(n<<1);i++)add_flow((i*i-n)<<1,n<<1|1,1);
int flow=0;
while(bfs())for(int i=dinic(s,INF);i;i=dinic(s,INF))flow+=i;
if(!flow)ans[++ant]=n;//没有更新最大流,则新开一个结点
}
--ant,--n;
printf("%d\n",n);
for(int i=1;i<=ant;i++){
for(int j=ans[i];j!=t&&j!=t>>1&&j!=0;j=suf[j])printf("%d ",j);
puts("");
}
return 0;
}
```