欢迎访问~原文出处——博客园-zhouzhendong
去博客园看该题解
题目传送门 - 洛谷2765
题意概括
假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在n根柱子上最多能放多少个球。
(哈哈,题意直接复制,原题够简略了)
数据范围 - 不详
题解
这一题是一个有点难的题。
我们先从简单的情况出发:给你m个数字,让你来做,最少需要多少个位置?
这个怎么做?
答案是二分图匹配。
我们对于每一个点(数字),拆成两个点,一个是二分图中左边一排的,一个是二分图中右边一排的,然后就是——
有向无环图最小路径覆盖问题
至于该图中要连的边,那么就是根据题目规定的要求来的:如果i<j且(i+j)为一个完全平方数,那么出点i和入点j就要连上一条边。(注意这里所的出点和入点是两个不同的点集)
然后跑二分图匹配即可求出最大匹配数,然后,
需要的位置数 = 最少路径条数
= 总点数 - 二分图匹配数
那么对于该题,我们又不知道有几个数字!
相反,叫我们求的是有几个数字!
思路1 :既然这样,那么我们可以二分答案啊,对于每一次,跑一遍匈牙利。
只要证明n增长的同时,ans也是单调递增的。
那么也可以去证明ans减少的时候n是单调不上升的。
其实很简单:设当ans = k + 1时,我们有一个方案,使得所有的ans个数字都合法放置,那么第k + 1个数字一定是最后放上去的。对于ans = k的情况,只需要把第k + 1个数字拿掉,那么就至少满足了前面的结论。
但是实际上这个方法的时间复杂度非常玄。
每次要重新构图,还要跑匈牙利算法,虚啊!
思路2 :暴力搜索,网络流SAP动态增广
那么网络流就可以解决这个问题了。
我们一个一个枚举点,每加入一个点,建立相应的边,然后用网络流跑一跑增广路,这样可以快很多。
但是细节非常多。
最后还要还原路径,具体方法就是顺着搜过去,不用寻找前驱节点(因为数字小的一定先放),只要一边找后继节点一边输出就可以了。
至于要开多大,题目没说,我也难以估计,所以,最终我采用的方法是:试数据。
在尝试不到10次(刚好9次)之后,终于得到了AC,最慢点时耗为388MS的优(ji)越(man)的时间。
接下来放代码。
代码
#include <cstring> #include <algorithm> #include <cstdio> #include <cstdlib> #include <cmath> using namespace std; const int maxR=25000+5,N=maxR*2,M=4000000,Inf=1<<25; struct Edge{ int x,y,cap,flow,nxt; }; struct Gragh{ int cnt,fst[N],dist[N],num[N],cur[N],p[N]; int s,t,n,MaxFlow; Edge e[N]; void set(int S,int T,int n){ s=S,t=T,cnt=1,(*this).n=n; memset(fst,0,sizeof fst); } void add(int a,int b,int c){ e[++cnt].x=a,e[cnt].y=b,e[cnt].cap=c,e[cnt].flow=0; e[cnt].nxt=fst[a],fst[a]=cnt; e[++cnt].x=b,e[cnt].y=a,e[cnt].cap=0,e[cnt].flow=0; e[cnt].nxt=fst[b],fst[b]=cnt; } int pre_SAP(){ MaxFlow=0; for (int i=2;i<=cnt;i++) e[i].flow=0; memset(num,0,sizeof num); memset(p,0,sizeof p); for (int i=1;i<=n;i++) num[dist[i]]++,cur[i]=fst[i]; } int Augment(int &point){ int ex_Flow=Inf; for (int i=t;i!=s;i=e[p[i]].x) if (e[p[i]].cap-e[p[i]].flow<=ex_Flow) ex_Flow=e[p[i]].cap-e[p[i]].flow,point=e[p[i]].x; for (int i=t;i!=s;i=e[p[i]].x) e[p[i]].flow+=ex_Flow,e[p[i]^1].flow-=ex_Flow; return ex_Flow; } int SAP(){ int x=s,y; memset(dist,0,sizeof dist); memset(num,0,sizeof num); for (int i=1;i<=n;i++) num[dist[i]]++,cur[i]=fst[i]; while (dist[s]<=n){ if (x==t){ MaxFlow+=Augment(x); continue; } bool found=0; for (int i=cur[x];i!=0&&!found;i=e[i].nxt) if (dist[e[i].y]+1==dist[x]&&e[i].cap>e[i].flow) p[e[i].y]=cur[x]=i,x=e[i].y,found=1; if (found) continue; int d=n+1; for (int i=fst[x];i;i=e[i].nxt) if (e[i].flow<e[i].cap) d=min(d,dist[e[i].y]+1); if (!(--num[dist[x]])) return MaxFlow; num[dist[x]=d]++,cur[x]=fst[x]; if (x!=s) x=e[p[x]].x; } return MaxFlow; } }g; int r=maxR-5,n; bool vis[N]; int main(){ scanf("%d",&n); g.set(r*2+1,r*2+2,r*2+2); int ans; g.pre_SAP(); for (int i=1;i<=r;i++){ g.add(g.s,i,1); g.add(i+r,g.t,1); for (int j=1;j<i;j++){ int s=sqrt(i+j); if (s*s==i+j) g.add(j,i+r,1); } int nowFlow=g.SAP(); if (i-nowFlow>n){ ans=i-1; break; } } printf("%d\n",ans); int de=ans+1; for (int i=2;i<=g.cnt;i++) if (g.e[i].x==de||g.e[i].y==de||g.e[i].x==de+r||g.e[i].y==de+r) g.e[i].cap=g.e[i].flow=0; g.pre_SAP(); int x=g.SAP(); memset(vis,0,sizeof vis); vis[ans+1]=1; for (int i=1;i<=ans;i++){ if (vis[i]) continue; int x=i; while (1){ printf("%d ",x); vis[x]=1; bool found=0; for (int i=g.fst[x];i;i=g.e[i].nxt){ if (i%2==1) continue; if (!vis[g.e[i].y-r]&&g.e[i].cap==g.e[i].flow){ x=g.e[i].y-r; found=1; break; } } if (!found) break; } puts(""); } return 0; }