[网络流24题]魔术球问题
将所有球看做点,在每根柱子上放球就是下边的点向上边的点连边,可以连边的条件是两球编号之和为完全平方数。
再把这n跟柱子看做是n条路径,问题也就转换成了用n条路径覆盖所有的点,也就是最小路径覆盖问题。
//最小路径覆盖数随着点数的增加不会递减,满足二分的性质,但是二分时要重新构图,所以不如直接顺序枚举答案,这样可以利用上次的残量网络,降低了复杂度。
对于新枚举到的球,有两种选择:
1.放到已经有球的柱子上
2.自己单独开辟一根柱子(柱子数不超过n)
所以将这个新的球与已经放好的球连边(需满足条件),重新跑一遍最大流,这时若没有新流,则说明这个新球不能放在任何一个已经有球的柱子上。
//num数组记录每根柱子最下边的球的编号,用于输出方案。
#include<complex> #include<cstdio> using namespace std; const int INF=0x3f3f3f3f; const int N=3507; struct node{ int v,f,nxt; }e[N*N]; int n,Enum=1,s,t,tot; int front[N],cur[N],deep[N],num[N],path[N]; int q[N]; bool vis[N]; void Insert(int u,int v) { e[++Enum].v=v;e[Enum].f=1;e[Enum].nxt=front[u];front[u]=Enum; e[++Enum].v=u;e[Enum].nxt=front[v];front[v]=Enum; } bool bfs() { for(int i=0;i<=t;i++) { deep[i]=0; cur[i]=front[i]; } int head=1,tail=0,u,v; deep[s]=1;q[++tail]=s; while(head<=tail) { u=q[head++]; for(int i=front[u];i;i=e[i].nxt) { v=e[i].v; if(!deep[v] && e[i].f) { deep[v]=deep[u]+1; if(v==t)return 1; q[++tail]=v; } } } return 0; } int dfs(int x,int cur_flow) { if(x==t)return cur_flow; int rest=cur_flow,v; for(int &i=cur[x];i;i=e[i].nxt) { v=e[i].v; if(deep[v]==deep[x]+1 && e[i].f && rest) { int new_flow=dfs(v,min(e[i].f,rest)); e[i].f-=new_flow; e[i^1].f+=new_flow; rest-=new_flow; if(v!=t)path[x>>1]=v>>1; if(!rest)return cur_flow; } } deep[x]=0; return cur_flow-rest; } int Dinic() { int res=0; while(bfs()) res+=dfs(s,INF); return res; } int main() { scanf("%d",&n); s=0;t=N-1; int cnt=0; while(cnt<=n) { tot++; Insert(s,tot<<1);Insert(tot<<1|1,t); int tmp=sqrt(tot); for(int i=tmp+1;i*i<tot*2;i++) Insert((i*i-tot)<<1,tot<<1|1); if(!Dinic())num[++cnt]=tot; } printf("%d\n",tot-1); for(int i=1;i<=n;i++) { if(vis[num[i]])continue; int tmp=num[i]; vis[tmp]=1; while(tmp) { printf("%d ",tmp); tmp=path[tmp]; vis[tmp]=1; } puts(""); } return 0; }