【洛谷 2765】魔术球问题

传送门


problem

假设有 n n n 根柱子,现要按下述规则在这 n n n 根柱子中依次放入编号为 1 , 2 , 3 , . . . 1,2,3,... 1,2,3,... 的球。

  1. 每次只能在某根柱子的最上面放球。
  2. 在同一根柱子中,任何两个相邻球的编号之和为完全平方数。

对于给定的 n n n,请计算在 n n n 根柱子上最多能放多少个球,并输出方案。

数据范围: 4 ≤ n ≤ 55 4\le n\le55 4n55


solution

这道题其实有一种比较暴力的解法。

我们可以枚举球数,每新加一个球的时候,就把能和它凑成平方数的球当前的球连边(比如加入 5 5 5 的时候,会有 4 → 5 4\rightarrow5 45 的边),然后跑一个最小路径覆盖,如果 > 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值