洛谷 P2765 魔术球问题(dinic算法)

题目描述

«问题描述:

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

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

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

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

«编程任务:

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

输入输出格式

输入格式:

 

第1 行有1个正整数n,表示柱子数。

 

输出格式:

 

程序运行结束时,将n 根柱子上最多能放的球数以及相应的放置方案输出。文件的第一行是球数。接下来的n行,每行是一根柱子上的球的编号。

 

输入输出样例

输入样例#1: 

4

输出样例#1: 

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

说明

感谢 @PhoenixEclipse 提供spj

4<=n<=55

解题思路

对于一个要放入的数字来说,这个数字有两种选择,一种是放在一个可以和他组成完全平方数的数字后面,另一种是放在一个新的柱子后面,这个时候用到的柱子数就+1了。自己设置一个源点s,一个汇点t。假设要放入一个数字x,那么可以把x拆分成两个点,x1和x2,让s到x1的容量为1,x2到t的容量为1。然后,找到所有比x小(已经被添加的数)并可以和x组成完全平方数的数y,让y1到x2的容量为1(x可以放在y后面),反向弧都设为0。那么,如果经过dinic()之后,弧(y1,x2)为饱和弧(选择了经过这条边),那就相当于是把x放在了y后面。如果x1的连边都没有饱和,那就说明x后面没有其他数字了,也就是说x是它所处木棍的最后一个。假设有r个像x一样的数字,那就说明使用了r个木棍。r(没有流量的点数) = 加入数字数 - 总流量。

代码如下

#include <iostream>
#include <queue>
#include <vector>
#include <cmath>
#include <cstring>
#define INF 0x3f3f3f
#define s 0
#define t 10000
#define maxn 1000
using namespace std;
struct Line{
	int r, w;
	bool dir;
	Line(){	}
	Line(int r, int w, bool d): r(r), w(w), dir(d){	} 
};
int num[maxn];
vector<Line> line;
vector<int> g[t + 5];
int deep[t + 5];
bool bfs()
{
	queue<int> que;
	memset(deep, 0, sizeof(deep));
	que.push(s);
	deep[s] = 1;
	while(!que.empty()){
		int top = que.front();
		que.pop();
		for(int i = 0; i < g[top].size(); i ++){
			int z = g[top][i];
			if(!deep[line[z].r] && line[z].w){
				deep[line[z].r] = deep[top] + 1;
				que.push(line[z].r);
				if(deep[t])
					return true;
			}
		}
	}
	return false;
}
int dfs(int x, int mix)
{
	if(x == t || !mix)
		return mix;
	int ap = 0;
	for(int i = 0; i < g[x].size(); i ++){
		int z = g[x][i];
		if(deep[x] < deep[line[z].r] && line[z].w){
			int p = dfs(line[z].r, min(mix, line[z].w));
			mix -= p;
			ap += p;
			line[z].w -= p;
			line[z^1].w += p;
			if(!mix)  
				return ap;
		}
	}
	return ap;
}
int dinic()
{
	int ans = 0;
	while(bfs())
		ans += dfs(s, INF);
	return ans;
}
void add_point(int x)   //x-2*x, x`-2*x+1
{
	line.push_back(Line(2*x, 1, 1));
	g[s].push_back(line.size() - 1);
	line.push_back(Line(s, 0, 0));
	g[2*x].push_back(line.size() - 1);
	line.push_back(Line(t, 1, 1));
	g[2*x+1].push_back(line.size() - 1);
	line.push_back(Line(2*x+1, 0, 0));
	g[t].push_back(line.size() - 1);
	for(int i = sqrt(x); i <= sqrt(x + x); i ++){
		int l = num[i] - x;
		if(l >= 1 && l < x){
			line.push_back(Line(2*x+1, 1, 1));
			g[2*l].push_back(line.size() - 1);
			line.push_back(Line(2*l, 0, 0));
			g[2*x+1].push_back(line.size() - 1);
		}		
	}
}
bool vis[t + 5];
void dfs2(int x)
{
	for(int i = 0; i < g[x].size(); i ++){
		int z = g[x][i];
		if(!vis[line[z].r / 2] && line[z].dir && !line[z].w){
			cout << " " << line[z].r / 2;
			vis[line[z].r / 2] = true;
			dfs2(line[z].r - 1);
			break;
		}
	}
}
int main()
{
	int n;
	for(int i = 0; i < maxn; i ++)
		num[i] = i * i;
	while(cin >> n){
		int k = 0;
		int w = 0;
		do{
			k++;
			add_point(k); 
			w += dinic();    //+=
		}while(k - w <= n);
		cout << k - 1 << endl;
		memset(vis, 0, sizeof(vis));
		for(int i = 1; i <= k - 1; i ++){
			if(!vis[i]){
				cout << i;
				vis[i] = true;
				dfs2(2*i);
				cout << endl;
			}
		}
		for(int i = 0; i <= t; i ++)
			g[i].clear();
		line.clear();
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值