洛谷OJ:P2765 魔术球问题(网络流24题)

题目描述

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

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 2 个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4 根柱子上最多可放 11 个球。

对于给定的 n,计算在 n 根柱子上最多能放多少个球。

输入格式

只有一行一个整数 n,代表柱子数。

输出格式

本题存在 Special Judge

请将 n 根柱子上最多能放的球数以及相应的放置方案输出。

输出的第一行是球数。

接下来的 n 行,每行若干个整数,代表一根柱子上的球的编号,数字间用单个空格隔开。

输入输出样例

输入 #1复制

4

输出 #1复制

11
1 8
2 7 9
3 6 10
4 5 11

说明/提示

数据规模与约定

对于 100% 的数据,保证 1≤n≤55。

思路:网络流最小覆盖问题,我们将n根柱子所能放的最大点数m这个问题转化为最多只能用n条路径来覆盖m个点的问题。和普通最少路径覆盖不同,我们需要每添加一个点就在图里加上相应的边,并更新当前最小路径数,直到大于n的时候,上一个点数m即为答案。

#include<set>
#include<map>
#include<queue>
#include<vector>
#include<string>
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
#define maxn 350500
#define ll long long
#define inf 1e9
int n,m,s,t,cnt=2,head[maxn];
int offset=3000,lv[maxn/10],nxt[maxn/10],cur[maxn];
struct edges{
	int u;
	int v;
	int w;
	int next;
}edges[maxn];
void add(int u,int v,int w){
	edges[cnt].u=u;
	edges[cnt].v=v;
	edges[cnt].w=w;
	edges[cnt].next=head[u];
	head[u]=cnt++;
}
//lv是每个点的层数
bool bfs(){
	memset(lv,-1,sizeof(lv));
	memcpy(cur,head,sizeof(head)); //当前弧优化
	lv[s]=0;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int now=q.front();
		q.pop();
		for(int eg=head[now];eg;eg=edges[eg].next){
			int to=edges[eg].v,val=edges[eg].w;
			if(val>0 && lv[to]==-1)
				lv[to]=lv[now]+1,q.push(to);
		}
	}
	return lv[t]!=-1;
}
int dfs(int p=s,int flow=inf){
	if(p==t)
		return flow;
	int mn=flow;
	for(int eg=cur[p];eg;eg=edges[eg].next){
		cur[p]=eg; //更新当前弧
		int to=edges[eg].v,val=edges[eg].w;
		if(val>0 && lv[to]==lv[p]+1){
			ll c=dfs(to,min(val,mn));
			mn-=c;
			edges[eg].w-=c;
			edges[eg^1].w+=c;
			if(c) nxt[p]=to-offset;
		}
	}
	return flow-mn;
}
ll dinic(){
	ll maxFlow=0;
	while(bfs())
		maxFlow+=dfs();
	return maxFlow;
}
bool jud(int x){
	int y=sqrt(x);
	return y*y==x;
}
int main(void){
	int num=0;
	scanf("%d",&n);
	t=2*offset+1;
	for(m=1;m<=2000;m++){
		add(s,m,1);
		add(m,s,0);
		add(m+offset,t,1);
		add(t,m+offset,0);
		for(int i=1;i<m;i++)
			if(jud(m+i))
				add(i,m+offset,1),add(m+offset,i,0);
		num+=dinic();
		if(m-num>n) break;
	}
	m--;
	cnt=2;
	memset(nxt,0,sizeof(nxt));
	memset(cur,0,sizeof(cur));
	memset(head,0,sizeof(head));
	for(int i=1;i<=m;i++){
		add(s,i,1);
		add(i,s,0);
		add(i+offset,t,1);
		add(t,i+offset,0);
		for(int j=i+1;j<=m;j++)
			if(jud(i+j))
				add(i,j+offset,1),add(j+offset,i,0);
	}
	dinic();
	printf("%d\n",m);
	for(int eg=head[t];eg;eg=edges[eg].next){
		if(edges[eg^1].w){
			int now=edges[eg].v-offset;
			printf("%d",now);
			while(nxt[now]){
				now=nxt[now];
				printf(" %d",now);
			}
			printf("\n");
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值