[网络流24题] 洛谷P2765 魔术球问题 最大流

给出 n n n个柱子,然后你依次往里面加编号从 1 1 1 n n n的球,但是要保证除了第一个加进去的球之外,其它所有的球都必须满足和前一个球的和是平方数。求问 n n n个柱子最多可以放多少个球。
当时还觉得这个题很难,想不到和网络流有什么关系。现在发现这个题是个裸的有向图最小路径覆盖,对于每个 i < j i<j i<j,若满足和为平方数就连一条有向边,每个柱子实际上就是一个路径,对于这样的有向图,每个点 u u u拆成两个点 u , u ′ u,u' u,u,源点连 u u u流量 1 1 1 u ′ u' u连汇点流量 1 1 1。其余的每条边 u u u v ′ v' v连流量 1 1 1。总点数减去匹配数就是路径覆盖数。
这个地方一开始想到二分个数,但是二分每次都要重新连边。不妨直接枚举球的个数,这样每次只要加上这个球和相应的连边。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
namespace Dinic {
	const int N=1e4+3,M=1e5+3; 
	int front[N],head[N],dep[N],tot=-1,n,S,T;
	struct edge{ int v,nxt;ll w; }e[2*M];
	void init() {
		tot=-1;
		for(int i=1;i<=n;i++) front[i]=-1;
	}
	void add(int u,int v,ll w) {
		e[++tot].v=v,e[tot].w=w,e[tot].nxt=front[u],front[u]=tot;
		e[++tot].v=u,e[tot].w=0,e[tot].nxt=front[v],front[v]=tot;
	}
	bool bfs() {
		for(int i=1;i<=n;i++) dep[i]=0;
		queue<int> q;
		q.push(S);
		dep[S]=1;
		while(!q.empty()) {
			int u=q.front();
			q.pop();
			for(int i=front[u];i!=-1;i=e[i].nxt) {
				int v=e[i].v;
				if(dep[v]==0&&e[i].w) {
					dep[v]=dep[u]+1;
					q.push(v); 
				}
			}
		}
		return dep[T];
	}
	ll dfs(int cur,ll dis=1e18) {
		if(cur==T) return dis;
		ll flow=0,sum=0;
		for(int &i=head[cur];i!=-1;i=e[i].nxt) {
			int v=e[i].v;
			if(dep[v]==dep[cur]+1&&e[i].w) {
				flow=dfs(v,min(dis,e[i].w));
				dis-=flow,sum+=flow;
				e[i].w-=flow,e[i^1].w+=flow;
				if(!dis) break;
			}
		}
		return sum;
	}
	ll solve() {
		ll ans=0;
		while(bfs()) {
			for(int i=1;i<=n;i++) head[i]=front[i];
			ans+=dfs(S);
		}
		return ans;
	}
	int to[N];
	bool used[N];
	void print(int n) { 
		for(int i=1;i<=n;i++) {
			for(int j=front[i];j!=-1;j=e[j].nxt) {
				if(e[j].v==S) continue;
				else if(e[j].w==0) {
					to[i]=e[j].v-1600;
				}
			}
		}
		for(int i=1;i<=n;i++) {
			if(used[i]) continue; 
			int cur=i;
			while(cur) {
				printf("%d ",cur);
				cur=to[cur];
				used[cur]=1;
			}
			printf("\n"); 
		} 
	}
}
vector<int> lk[1601];
bool check(int x) {
	int sqr=sqrt(x);
	return sqr*sqr==x;
}
int main() {
	int n; 
	for(int i=1;i<=1600;i++) {
		for(int j=1;j<i;j++) {
			if(check(i+j)) {
				lk[i].push_back(j);
			}
		}
	} 
	Dinic::n=3202;
	int S=3201;
	int T=3202;
	Dinic::S=S;
	Dinic::T=T;
	Dinic::init();
	scanf("%d",&n);
	int sum=0,ans=0;
	for(int ball=1;;ball++) {
		Dinic::add(S,ball,1);
		Dinic::add(ball+1600,T,1);
		for(auto &x:lk[ball]) 
			Dinic::add(x,ball+1600,1);
		sum+=Dinic::solve(); 
		if(ball-sum<=n) ans=max(ans,ball);
		else break; 
	}
	printf("%d\n",ans);
	Dinic::print(ans); 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值