Description
给定(n(nleq 55))个柱子,需要依次将编号为(1,2,3...)的球放上柱子,规定一个球能放上柱子当且仅当柱子为空或这个球的编号与柱子最上面球的编号的和为完全平方数。问最多能放几个球。输出方案。
Solution
先吐槽一句,网上用网络流写的题解都tm太不详细了,全是瞎jb写,写题解能不能有点责任心。气tm死了,一上午都在看写的不清楚的垃圾题解。
先说网上瞎jb讲的题解,都是说对于每个球拆点,然后源点连一个,汇点连一个,中间不连。感觉网络流最重要的就是讲清楚建图的原因和代表的意义,然而网上根本没有讲为啥要这样连,这里讲一下这样连的意义是什么。
我们先不要拿看待网络流的眼光看这题。我们把这 (m) 个球(假设有m个球)看成图上的一堆点,如果 (i<j;and;i+j) 是个完全平方数,那么我们在(i,j)连一条边。可以发现,如果用这 (n) 个柱子可以放下 (m) 个球的话,那么肯定存在 (n) 条路径完全覆盖了这(m)个点。
路径覆盖?这就跟二分图扯上关系了。回想我们求最小路径覆盖的方法,把每个点拆成入点 (i<<1) 和出点 (i<<1|1) ,如果 ((i,j)) 有边,那么就在新图中加一条 ((i<<1|1,j<<1)) 的边。求出最大匹配,用总点数减去最大匹配数就是最小路径覆盖了。给出一个小小的证明吧,因为最开始每个点都是独立的,一共(m)条路径覆盖了(m)个点。每找到一条增广路,本质上都是合并了两条路径,所以路径数减去1.
然后再回来看这题就比较显然了,枚举可以放多少个球,然后求一下这些球的最小路径覆盖是否 (leq n),如果合法那就说明当前 (n) 个柱子放这么多球是完全ojbk的,往下枚举就好了。
那这题为什么放进网络流24题里呢?我觉得本质上就是这个最小路径覆盖可以拿最大流求,其他的跟网络流建图啥的一点关系都没有。
再次鄙视网上的垃圾题解。
Code
#include<cstdio>
#include<cctype>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 5005
using std::min;
using std::max;
using std::swap;
const int inf=0x3f3f3f3f;
int d[N],s,t;
int stk[N],top;
int n,cnt,head[N];
struct Edge{
int to,nxt,flow;
}edge[N*405];
void add(int x,int y,int z){
edge[++cnt].to=y;
edge[cnt].nxt=head[x];
edge[cnt].flow=z;
head[x]=cnt;
}
int getint(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch)) f|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
#include<queue>
bool bfs(){
std::queue<int> q;q.push(s);
memset(d,0,sizeof d);d[s]=1;
while(q.size()){
int u=q.front();q.pop();
for(int i=head[u];i;i=edge[i].nxt){
int to=edge[i].to;
if(!edge[i].flow or d[to]) continue;
d[to]=d[u]+1;
q.push(to);
if(to==t) return 1;
}
} return 0;
}
int dinic(int now,int flow){
if(now==t) return flow;
int res=flow;
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(d[to]!=d[now]+1 or !edge[i].flow) continue;
int k=dinic(to,min(res,edge[i].flow));
if(!k) d[to]=0;
edge[i].flow-=k;edge[i^1].flow+=k;res-=k;
if(!res) return flow;
} return flow-res;
}
int mf(){
int ans=0,flow=0;
while(bfs()) while(flow=dinic(s,inf)) ans+=flow;
return ans;
}
void dfs(int now){
printf("%d ",now>>1);
for(int i=head[now];i;i=edge[i].nxt){
int to=edge[i].to;
if(to==t or to==s or edge[i].flow) continue;
dfs(to|1);
}
}
#include<cmath>
signed main(){
n=getint();cnt=1;
s=0;t=5000;int now=0;
for(int i=1;i;i++){
add(s,i<<1|1,1);add(i<<1|1,s,0);
add(i<<1,t,1);add(t,i<<1,0);
for(int j=sqrt(i)+1;j*j<(i<<1);j++)
add(j*j-i<<1|1,i<<1,1),add(i<<1,j*j-i<<1|1,0);
int k=mf();
if(!k){
if(now==n){
printf("%d
",i-1);
break;
} else stk[++now]=i;
}
}
for(int i=1;i<=n;i++)
dfs(stk[i]<<1|1),puts("");
return 0;
}