网络流二十四题 ————( 四)、P2765 魔术球问题 最小路径覆盖模型 +打表找规律 或者 残留网络上跑最大流

最小路径覆盖模型,模型解法见 我上一篇BLOG:

https://blog.csdn.net/bjfu170203101/article/details/108956695

这题难点在于不知道点数,即不知道改用多少小球,枚举的话,每次重新跑dinic显然会T.

我们有两种方法解决这道题:

方法一:

打表找规律,找出每个n对应的小球的个数,只跑一遍dinic就行。

网络流的最坏复杂度是n^2*m , 但实际跑随机数据时(非完全图,边和点同一数量级),效率很高,我们可以把它看成线性的复杂度。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 40000+7;
const int N = 4000+7;
struct Dinic{
	//N个点,M条边 
	//add建双向边,然后D.gao ,最后输出maxflow  
	#define inf 0x3f3f3f3f
	ll maxflow;int s,t,n;
	int v[N],pre[N],d[N],now[N];
	ll incf[N];
	int head[N],cnt=1;
	struct EDGE{int to,nxt;ll w;}ee[M*2];
	inline void AD(int x,int y,ll w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}
	inline void add(int x,int y,ll w){AD(x,y,w);AD(y,x,0);}
	inline bool bfs()//在残量网络上构造分层图 
	{
		memset(d,0,sizeof(d));
		queue<int>q;
		q.push(s);d[s]=1;
		while(q.size())
		{
			int x=q.front();q.pop();
			for(int i=head[x];i;i=ee[i].nxt)
			{
				int y=ee[i].to;ll w=ee[i].w;
				if(w&&!d[y])
				{
					q.push(y);
					d[y]=d[x]+1;
					if(y==t)return 1;
				}
			}
		}
		return false;
	}
	inline int dinic(int x,int flow)
	{
		if(x==t)return flow;
		ll rest = flow,k;
		for(int i=now[x];i&&rest;i=ee[i].nxt)
		{
			int y=ee[i].to;ll w= ee[i].w;
			now[x]=i;
			if(w&&d[y]==d[x]+1)
			{
				k=dinic(y,min(rest,w));
				if(!k)d[y]=0;//剪枝,去掉增广完毕的点
				ee[i].w-=k;
				ee[i^1].w+=k;
				rest-=k; 
			}
		}
		return flow - rest;
	}
	inline void gao()
	{
		int flow=0;
		while(bfs())
		{
			for(int i=0;i<=n;i++)now[i]=head[i];
			while(flow=dinic(s,inf))maxflow+=flow;
		}
	}
	inline void init(int nn,int S,int T)
	{
		cnt=1;maxflow=0;
		for(int i=0;i<=n;i++)head[i]=0;
		s=S,t=T,n=nn;
	}
}D;
//求解二分图匹配问题时的复杂度是:m*sqrt(n)的
struct node{
	int x,y,id;
}p[M];//记录每条边 x -> y 反向边的编号 (cnt)
int fa[N];
int gt(int x){
	if(fa[x]==x)return x;
	return fa[x]=gt(fa[x]);
} 
vector<int>G[M];
int n,s,t;
bool gao(int nm){
	s=2*nm+1,t=2*nm+2;
	int N = 2*nm+2;
	D.init(N,s,t);
	int sz=0;
	for(int j=1;j<=nm;j++){
		for(auto y:G[j]){
			if(y>nm)break;
			D.add(j,y+nm,1);
			p[++sz]=node{j,y,D.cnt};
		//	cout<<"  =   "<<j<<"  ->  "<<y<<"  "<<endl;
		}
	}//cout<<sz<<"  -- - ----------------"<<endl;
	for(int j=1;j<=nm;j++)D.add(s,j,1),D.add(j+nm,t,1);
	D.gao();
	//if(nm-D.maxflow!=n)return false;
	
	cout<<nm<<endl;
	for(int i=1;i<=nm;i++)fa[i]=i;
	for(int i=1;i<=sz;i++){
		if(D.ee[p[i].id].w){//这条边的反向边有流量即改边被最大流选中 
			//存在一条边  : p[i].x -> p[i].y  即把与x相连的 和 与y相连的点集  合并 
			//由于求得的是不相交路径,每个点最多一个入边一个出边,所以最多往前合并一次,往后合并一次
			//cout<<" = == =  "<<p[i].x<<" "<<p[i].y<<endl;
			int gx=gt(p[i].x),gy=gt(p[i].y); 
			if(gx!=gy)fa[gx]=gy;
		}
	}
	for(int i=1;i<=nm;i++){
		bool flag=false;
		for(int j=1;j<=nm;j++){
			if(gt(j)==i)cout<<j<<" ",flag=true;
		}
		if(flag)cout<<endl;
	}
	return true;
}
int c[100];
int main()
{
	ios::sync_with_stdio(false);
  	cin.tie(0);
	
	for(int i=1;i<=2000;i++)
	for(int j=i+1;j<=2000;j++){
		int tp=i+j;
		if((int)sqrt(tp)*(int)sqrt(tp)==tp){
			G[i].push_back(j);
		}
	}
	/*
	打表发现 :
	n=1,2,3,4,……时;球的数量为: 1,3,7,11,17,23,31
	差分数组:2,4,4,6,6,8,8,………… 
	*/
	cin>>n;
	c[1]=1;c[2]=3;
	int cz=4;
	for(int i=4;i<=n+2;i+=2){
		c[i-1]=c[i-2]+cz;
		c[i]=c[i-1]+cz;
		cz+=2; 
	}
//	cout<<c[n]<<endl;
	gao(c[n]);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值