problem
假设有 n n n 根柱子,现要按下述规则在这 n n n 根柱子中依次放入编号为 1 , 2 , 3 , . . . 1,2,3,... 1,2,3,... 的球。
- 每次只能在某根柱子的最上面放球。
- 在同一根柱子中,任何两个相邻球的编号之和为完全平方数。
对于给定的 n n n,请计算在 n n n 根柱子上最多能放多少个球,并输出方案。
数据范围: 4 ≤ n ≤ 55 4\le n\le55 4≤n≤55。
solution
这道题其实有一种比较暴力的解法。
我们可以枚举球数,每新加一个球的时候,就把能和它凑成平方数的球向当前的球连边(比如加入 5 5 5 的时候,会有 4 → 5 4\rightarrow5 4→5 的边),然后跑一个最小路径覆盖,如果 > n >n >n 说明至少需要 > n >n >n 个柱子,就 break 掉,此时的球数 − 1 -1 −1 就是答案。
每次跑的时候不用重新跑,在残图的基础上加上新边跑就可以了。
输出方案也按照最小路径覆盖那样输出就可以了。
注:当一个柱子上只有一个球时,这个球不要求是完全平方数。
code
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define inf 0x3f3f3f3f
using namespace std;
int n,S=0,T=4001,tot=1,num,Flow,cnt=2000;
int d[N],f[N],first[N],v[N],w[N],nxt[N];
void add(int x,int y){
nxt[++tot]=first[x],first[x]=tot,v[tot]=y,w[tot]=1;
nxt[++tot]=first[y],first[y]=tot,v[tot]=x,w[tot]=0;
}
bool bfs(){
memset(d,-1,sizeof(d));
memcpy(f,first,sizeof(f));
queue<int>Q;Q.push(S);d[S]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int i=first[x];i;i=nxt[i]){
int to=v[i];
if(w[i]&&d[to]==-1){
d[to]=d[x]+1,Q.push(to);
if(to==T) return true;
}
}
}
return false;
}
int dinic(int x,int flow){
if(x==T) return flow;
int delta,ans=0;
for(int &i=f[x];i;i=nxt[i]){
int to=v[i];
if(w[i]&&d[to]==d[x]+1){
delta=dinic(to,min(flow,w[i]));
w[i]-=delta,w[i^1]+=delta,flow-=delta,ans+=delta;
if(!flow) return ans;
}
}
return ans;
}
int solve(int num){
for(int i=1;i<num;++i){
if(i*i-num>=num) break;
if(i*i>num) add(i*i-num,cnt+num);
}
add(S,num),add(cnt+num,T);
while(bfs()) Flow+=dinic(S,inf);
return num-Flow;
}
int vis[N];
void find(int x){
vis[x]=1,printf("%d ",x);
for(int i=first[x];i;i=nxt[i]){
int to=v[i];
if(to>cnt&&to<=2*cnt&&!w[i]) find(to-cnt);
}
}
int main(){
scanf("%d",&n);
for(num=1;;++num)
if(solve(num)>n) break;
printf("%d\n",--num);
for(int i=1;i<=num;++i)
if(!vis[i]) find(i),puts("");
return 0;
}