最小路径覆盖模型,模型解法见 我上一篇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;
}