网络流24题 题解


网络流24题

\(\large by~ctldragon\)

到目前为止,还有数字梯形问题和机器人路径规划问题未完成。

网络流24题主要考建模,算法都不难(

餐巾计划问题

显然要拆点,把一天拆成早上和晚上。

源点 \(s\) 向每天晚上连一条容量为所需餐巾数,费用为 \(0\) 的边,表示获得脏餐巾。

每天早上向汇点 \(t\) 连一条容量为所需餐巾数,费用为 \(0\) 的边,表示提供干净餐巾。

每天晚上可以向第二天晚上连一条容量为 \(inf\) ,费用为 \(0\) 的边,表示将脏餐巾留在第二天。

\(i-m\) 天向 \(i\) 天连一条容量为 \(inf\) ,费用为 \(f\) 的边,表示快洗。

\(i-n\) 天向 \(i\) 天连一条容量为 \(inf\) ,费用为 \(s\) 的边,表示快洗。

然后就愉快的跑最小费用最大流了。

远古代码:

#include
#define re register
#define int long long
using namespace std;
const int inf=0x3f3f3f3f;
int n;
int p,ft,fcost,st,scost;
int s,t;
struct node
{
	int v,nxt,flow,dis;
}a[1000000];
int head[20000],cnt=1;
void add(int u,int v,int flow,int dis)
{
	a[++cnt].v=v;
	a[cnt].flow=flow;
	a[cnt].dis=dis;
	a[cnt].nxt=head[u];
	head[u]=cnt;
}
int dis[20000],pre[20000];
bool vis[20000];
bool SPFA()
{
	queueq;
	memset(vis,false,sizeof vis);
	memset(pre,0,sizeof pre);
	while(!q.empty())q.pop();
	for(re int i=0;i<=2*n+1;i++)dis[i]=inf;
	dis[s]=0;vis[s]=true;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(re int i=head[u];i;i=a[i].nxt)
		{
			int v=a[i].v;
			if(!a[i].flow)continue;
			if(dis[v]>dis[u]+a[i].dis)
			{
				dis[v]=dis[u]+a[i].dis,pre[v]=i;
				if(!vis[v])vis[v]=true,q.push(v);
			}
		}
	}
	if(dis[t]==inf)return 0;
	return 1;
}
int MCMF()
{
	int mincost=0;
	while(SPFA())
	{
		int d=inf,v=t;
		while(v!=s)
		{
			d=min(d,a[pre[v]].flow);
			v=a[pre[v]^1].v;
		}
		v=t;
		while(v!=s)
		{
			a[pre[v]].flow-=d;
			a[pre[v]^1].flow+=d;
			v=a[pre[v]^1].v;
		}
		mincost+=d*dis[t];
	}
	return mincost;
}
signed main()
{
	scanf("%lld",&n);
	t=2*n+1;
	for(re int i=1;i<=n;++i)
	{
		int cj;scanf("%lld",&cj);
		add(s,i+n,cj,0),add(i+n,s,0,0);
		add(i,t,cj,0),add(t,i,0,0);
	}
	scanf("%lld%lld%lld%lld%lld",&p,&ft,&fcost,&st,&scost);
	for(re int i=1;i<=n;++i)add(s,i,inf,p),add(i,s,0,-p);	
	for(re int i=1;i

\(n\) 个月之前的代码了,码风极其奇怪/kk

家园 / 星际转移问题

首先并查集判断一下地球和月球是否连起来,没连起来就是无解。

考虑类似分层图,每个点表示第 \(i\) 个太空站在第 \(t\) 秒的状态。

源点向每个时间点的地球连一条边 \(((u,t),(u,t+1),inf)\)

每个空间站向下一个时间点的自己连一条边 \(((u,t),(u,t+1),inf)\)

若第 \(i\) 个太空船可以从第 \(u\) 个太空站到第 \(v\) 个太空站,就连一条边 \(((u,t),(v,t+1),h_i)\)

然后我们就可以从小到大枚举时间来得到答案了。当有一个 \(t\) 使得在 \(t\) 秒内有 \(maxflow\ge k\) ,则 \(t\) 就是答案。

代码不给了。

飞行员配对方案问题

简单二分图最大匹配问题,跑 匈牙利 或 DINIC 都行。

太板了,快水!

同远古代码:

#include
#define re register
using namespace std;
int n,m,ans;
struct node
{
	int v,nxt;
}a[20005];
int head[105],cnt;
void add(int u,int v)
{
	a[++cnt].v=v;
	a[cnt].nxt=head[u];
	head[u]=cnt;
}
int match[105];
bool vis[105];
bool maxmatch(int x)
{
	for(re int i=head[x];i;i=a[i].nxt)
	{
		int y=a[i].v;
		if(vis[y])continue;
		vis[y]=true;
		if(!match[y]||maxmatch(match[y]))
		{
			match[y]=x;
			return true;
		}
	}
	return false;
}
int main()
{
	scanf("%d%d",&m,&n);
	int u,v;
	while(1)
	{
		scanf("%d%d",&u,&v);
		if(u==-1&&v==-1)break;
		if(u>=v)swap(u,v);
		add(u,v);
	}
	for(re int i=1;i<=m;++i){memset(vis,false,sizeof vis);if(maxmatch(i))ans++;}
	printf("%d\n",ans);
	for(re int i=m+1;i<=n;++i)if(match[i])printf("%d %d\n",match[i],i);
	return 0;
}

软件补丁问题

一句话:状压+最短路。

没什么难度,代码不给了。

太空飞行计划问题

好题!!!

最大权闭合子图经典问题。

可以来口胡(白嫖)一波:

如果一个点被选择了则后继必须被选择,那么称该图是 闭合的,因此该问题叫做最大权闭合子图问题

具体的建图方法为:

源点向所有正权点连结一条容量为权值的边

保留原图中所有的边,容量为正无穷

所有负权点向汇点连结一条容量为权值绝对值的边

则原图的最大权闭合子图的点权和即为所有正权点权值之和减去建出的网络流图的最小割。

在图中,若割掉 \(s\)\(u\) 之间的边,则代表不选择 \(u\) 进入子图;若割掉 \(v\)\(t\) 之间的边,则代表选择 \(v\) 进入子图。

所以最小割割掉的权值和是 不被选择的正权点权值和 + 被选择的负权点的权值的绝对值和。

所以

\(最大权闭合子图的点权和=max \{被选择的点权和\}\)

\(=正点权和-min\{没被选择的正点权和+被选择的负点权和的绝对值\}=正点权和-最小割\)

那这道题就做完了(

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=1e9;
int n,m,s,t,ans;
struct edge
{
	int v,flow,nxt;
}e[1005];
int ecnt=1,head[105];
void add(int u,int v,int w)
{
	e[++ecnt].v=v;e[ecnt].flow=w;
	e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int w){add(u,v,w);add(v,u,0);}
int dep[105];
bool bfs()
{
	queueq;
	memset(dep,0,sizeof dep);
	dep[s]=1;q.push(s);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;
			if(dep[v]||!e[i].flow)continue;
			dep[v]=dep[u]+1;q.push(v);
		}
	}return dep[t];
}
int dfs(int x,int flow)
{
	if(x==t)return flow; int sum=0;
	for(int i=head[x];i&&sum

这个才是近期代码(

试题库问题

这个也很板,建图没啥难度,唯一搞人心态的就是输出方案了。

二分图匹配,跑 匈牙利 或 DINIC 都行。

但我这份代码是 EK 。/jk

远古代码:

#include
#define re register
using namespace std;
const int inf=0x3f3f3f3f;
vectorvec[100];
int k,n,sum;
int s,t;
struct node
{
	int v,nxt,flow;
}a[1000000];
int head[2005],cnt=1;
void add(int u,int v,int w)
{
	a[++cnt].v=v;
	a[cnt].flow=w;
	a[cnt].nxt=head[u];
	head[u]=cnt;
}
int pre[2005];
bool vis[2005];
bool bfs()
{
	queueq;
	while(!q.empty())q.pop();
	memset(vis,false,sizeof vis);
	memset(pre,0,sizeof pre);
	q.push(s);vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(re int i=head[u];i;i=a[i].nxt)
		{
			int v=a[i].v;
			if(vis[v]||a[i].flow==0)continue;
			vis[v]=true;pre[v]=i;
			q.push(v);
			if(v==t)return 1;
		}
	}
	return 0;
}
void EK()
{
	int maxflow=0;
	while(bfs())
	{
		int d=inf,v=t;
		while(v!=s)
		{
			d=min(d,a[pre[v]].flow);
			v=a[pre[v]^1].v;
		}
		v=t;maxflow+=d;
		while(v!=s)
		{
			a[pre[v]].flow-=d;
			a[pre[v]^1].flow+=d;
			v=a[pre[v]^1].v;
		}
	}
	if(maxflown)/*cout<

最小路径覆盖问题

结论题。最小路径覆盖(最小边覆盖)=点的总数-网络最大流(二分图最大匹配)。

证明略。

远古代码:

#include
using namespace std;
const int maxn=350;
struct node
{
    int v,next;
}e[maxn*maxn*2];
int cnt,head[maxn],tim=1,vis[maxn],mat[maxn],n,m,ans;
void add(int u,int v)
{
    e[++cnt].next=head[u];
    e[cnt].v=v;
    head[u]=cnt;
}
int getmat(int u)
{
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].v;
        if(vis[v]==tim) continue;
        vis[v]=tim;
        if(!mat[v] || getmat(mat[v]))
        {
            mat[v]=u;mat[u]=v;
            return 1;
        }
    }
    return 0;
}
void getans()
{
    for(int i=1;i<=n;i++,tim++) ans+=getmat(i);
}
void print(int x)
{
    x+=n;
    do
        printf("%d ",x=x-n);
    while(vis[x]=1,x=mat[x]);
    printf("\n");
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,u,v;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        add(u,v+n);
    }
    getans();
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        if(!vis[i]) print(i);
    printf("%d\n",n-ans);
    return 0;
}

魔术球问题

做法1:网络流

只要编号相加为平方数就可以互相连边了。

对于题目问题就可以转换为:对于给定的n,计算不超过n条路径最多可以覆盖多少满足条件的节点

有一个性质:最小边覆盖=点总数-最大匹配。

所以我们可以进行拆点,转换为二分图的形式,每加一个点就计算一下最大匹配(最大流),只要 点总数-最大匹配>最小边覆盖 就退出并输出答案。

没写这种做法,所以无代码。

做法2:打表+贪心

通项公式:(n*(n+2)+(n&1)-2)/2

远古代码:

#include
#define re register
using namespace std;
int a[100][100];
int n,m;
int main()
{
    scanf("%d",&n);
    m=(n*(n+2)+(n&1)-2)/2;
    printf("%d\n",m);
    for(int i=1;i<=m;++i)
    	for(re int j=1;j<=n;++j)
    	{
	    	int x=a[j][a[j][0]];
	    	if(!x||pow((int)sqrt(x+i),2)==x+i){a[j][++a[j][0]]=i;break;}
		}
    for(re int i=1;i<=n;++i)
    {
    	for(re int j=1;j<=a[i][0];++j)
		    printf("%d ",a[i][j]);
		printf("\n");
	}
    return 0;
}

最长不下降子序列问题

这道题有3个问题,我们可以逐步解决。

问题1:计算其最长不下降子序列的长度 \(s\)

这个可以直接 \(O(n^2)\) DP求出来。

问题2:如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 \(s\) 的不下降子序列。

通过问题1我们可以求出数组 \(f\)\(f[i]\) 代表以 \(i\) 开头的最长不下降子序列的长度。

因为每个元素最多用一次,所以我们需要拆点,连边 \((i,i+n,1)\)

对于每个 \(f[i]=1\)\(i\) ,我们连边 \((s,i,1)\)

对于每个 \(f[i]=s\)\(i\) ,我们连边 \((i+n,t,1)\)

对于每个 \(i\) 位置,枚举 \(j(i ,若 \(f[j]=f[i]+1\) ,则连边 \((i+n,j,1)\)

最后就可以快乐的跑最大流了。

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=1e9;
int n,m,s,t,ans,len;
int a[505],f[505];
struct edge
{
	int v,flow,nxt;
}e[50005];
int ecnt=1,head[1005];
void add(int u,int v,int w)
{
	e[++ecnt].v=v;e[ecnt].flow=w;
	e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int w){add(u,v,w);add(v,u,0);}
int dep[1005];
bool bfs()
{
	queueq;
	memset(dep,0,sizeof dep);
	dep[s]=1;q.push(s);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;
			if(dep[v]||!e[i].flow)continue;
			dep[v]=dep[u]+1;q.push(v);
		}
	}return dep[t];
}
int dfs(int x,int flow)
{
	if(x==t)return flow; int sum=0;
	for(int i=head[x];i&&sum

航空路线问题

因为是无向图,可以理解为从 \(1\) 号点去 \(n\) 号点跑2次,并且途径的点不重复( \(1,n\) 两点可以经过2次)。

因为途径点只能经过1次,所以还是要套路拆点的,l连边 \((u,u+n,1)\)。( \(1,n\) 两点容量为2)。

连边 \((s,1,2),(n+n,t,2)\) 。若 \(u\) 能到 \(v\) ,连边 \((u+n,v,1)\)

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=0x3f3f3f3f;
mapmp;
int n,m,s,t;bool flag;
string str[105];
struct edge
{
	int v,flow,dis,nxt;
}e[20005];
int ecnt=1,head[205];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[205],dis[205];bool vis[205];
bool spfa()
{
	queueq;
	memset(dis,-1,sizeof dis);
	memset(vis,false,sizeof vis);
	q.push(s);dis[s]=0;vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;if(!e[i].flow)continue;
			if(dis[v]0;
}
int MCMF()
{
	int mncost=0,mxflow=0;
	while(spfa())
	{
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mncost+=dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}if(mxflow==1&&flag){puts("2");cout<>str[i],mp[str[i]]=i;
	for(int i=1;i<=n;++i)ADD(i,n+i,(i==1||i==n)?2:1,1);
	for(int i=1;i<=m;++i)
	{
		string s1,s2;cin>>s1>>s2;
		int u=mp[s1],v=mp[s2];
		if(u>v)swap(u,v);
		if(u==1||v==n)flag=true;
		ADD(u+n,v,1,0);
	}ADD(s,1,2,0);ADD(n*2,t,2,0);
	int mncost=MCMF();write(mncost-2),pc('\n');
	vis[t]=true;dfs(1,1);dfs(1,0);
	return 0;
}

方格取数问题

相邻的数不能取,可以理解为相邻的奇偶块不能同时取,这样就可以建出一个二分图了。

\(s\) 和奇块相连 \((s,(i-1)*n+j,a[i][j])\)

\(t\) 和偶块相连 \((s,(i-1)*n+j,a[i][j])\)

相邻的块也要连边。最后跑一下最大流,答案就是总点权-最小割。

远古代码:

#include
#define re register
#define int long long
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,sum;
int s,t;
int dir[4][2]={{0,-1},{0,1},{-1,0},{1,0}};
struct node
{
	int v,nxt,flow;
}a[500000];
int head[50005],cnt=1;
void add(int u,int v,int w)
{
	a[++cnt].v=v;
	a[cnt].flow=w;
	a[cnt].nxt=head[u];
	head[u]=cnt;
}
int pre[50005];
bool vis[50005];
bool bfs()
{
	queueq;
	while(!q.empty())q.pop();
	memset(vis,false,sizeof vis);
	memset(pre,0,sizeof pre);
	q.push(s);vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(re int i=head[u];i;i=a[i].nxt)
		{
			int v=a[i].v;
			if(vis[v]||a[i].flow==0)continue;
			vis[v]=true;pre[v]=i;
			q.push(v);
			if(v==t)return 1;
		}
	}
	return 0;
}
int EK()
{
	int maxflow=0;
	while(bfs())
	{
		int d=inf,v=t;
		while(v!=s)
		{
			d=min(d,a[pre[v]].flow);
			v=a[pre[v]^1].v;
		}
		v=t;
		while(v!=s)
		{
			a[pre[v]].flow-=d;
			a[pre[v]^1].flow+=d;
			v=a[pre[v]^1].v;
		}
		maxflow+=d;
	}
	return maxflow;
}
signed main()
{
	scanf("%lld%lld",&m,&n);
	s=0,t=m*n+1;
	for(re int i=1;i<=m;++i)
	{
		for(re int j=1;j<=n;++j)
		{
			int shu;
			scanf("%lld",&shu);sum+=shu;
			if((i+j)&1)add(s,(i-1)*n+j,shu),add((i-1)*n+j,s,0);
			else add((i-1)*n+j,t,shu),add(t,(i-1)*n+j,0);
		}
	}
	for(re int i=1;i<=m;++i)
	{
		for(re int j=1;j<=n;++j)
		{
			if(!((i+j)&1))continue;
			int u=(i-1)*n+j;
			for(re int k=0;k<=3;++k)
			{
				if(i+dir[k][0]<1||i+dir[k][0]>m||j+dir[k][1]<1||j+dir[k][1]>n)continue;
				int pos=(i+dir[k][0]-1)*n+j+dir[k][1];
				add(u,pos,inf),add(pos,u,0);
			}
		}
	}
	printf("%lld\n",sum-EK());
	return 0;
}

圆桌问题

裸的二分图匹配问题,直接上板子即可。

远古代码:

#include
#define re register
#define int long long
using namespace std;
const int inf=5e17;
int n,m,sum=0;
int table,men;
int maxflow=0;
int s,t;
struct node
{
	int v,nxt,flow;
}a[1000000];
int head[500000],cnt=1;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')f=ch=='-'?-1:f,ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x*f;
}
void add(int u,int v,int w)
{
	a[++cnt].v=v;
	a[cnt].flow=w;
	a[cnt].nxt=head[u];
	head[u]=cnt;
}
int dep[600],cur[600];
bool bfs()
{
	queueq;
	while(!q.empty())q.pop();
	memset(dep,0,sizeof dep);
	q.push(s);dep[s]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(re int i=head[u];i;i=a[i].nxt)
		{
			int v=a[i].v;
			if(!a[i].flow||dep[v])continue;
			dep[v]=dep[u]+1;
			q.push(v);
		}
	}
	return dep[t];
}
int dfs(int x,int flow)
{
	if(x==t)return flow;
	int sum=0;
	for(re int i=cur[x];i&&sum

骑士共存问题

这道题和那个方格取数问题差不多,直接用奇偶性来建图。

#include
#define re register 
using namespace std;
int n,m,sum,ans,bh;
struct node
{
	int v,nxt;
} a[400010];
int head[40005],cnt;
int num[205][205],match[40050];
int dir1[5]= {0,1,2,2,1},dir2[5]= {0,2,1,-1,-2};
bool vis[40050],mp[205][205];
void add(int x,int y)
{
	a[++cnt].nxt=head[x];
	a[cnt].v=y;
	head[x]=cnt;
}
bool maxmatch(int x)
{
	for(re int i=head[x]; i; i=a[i].nxt)
	{
		int y=a[i].v;
		if(vis[y])continue; else vis[y]=1;
		if(!match[y]||maxmatch(match[y])){match[y]=x;return 1;}
	}
	return 0;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(re int i=1;i<=m;++i)
	{
		int qx,qy;
		scanf("%d%d",&qx,&qy);mp[qx][qy]=1;
	}
	for(re int i=1; i<=n; ++i)
		for(re int j=1; j<=n; ++j)
		{
			num[i][j]=++bh;
			if(!mp[i][j])sum++; else continue;
			for(re int k=1; k<=4; ++k)
				if((i>dir1[k])&&(j>dir2[k])&&(j-dir2[k]<=n)&&(!mp[i-dir1[k]][j-dir2[k]]))
					add(num[i][j],num[i-dir1[k]][j-dir2[k]]),
            		add(num[i-dir1[k]][j-dir2[k]],num[i][j]);
		}
	for(re int i=1; i<=n; i++)
		for(re int j=1; j<=n; j++)
			if((i+j&1)&&!mp[i][j]){memset(vis,0,sizeof vis);if(maxmatch(num[i][j]))ans++;}
	printf("%d\n",sum-ans);
	return 0;
}

火星探险问题

因为岩石标本是在点上的,所以套路拆点必不可少。

那建图就很简单了。(往下和往右连边即可)

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=0x3f3f3f3f;
int n,m,k,s,t;
int a[40][40];
int dir[2][2]={{0,1},{1,0}};
struct edge
{
	int v,flow,dis,nxt;
}e[20005];
int ecnt=1,head[5005];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[5005],dis[5005];bool vis[5005];
bool spfa()
{
	queueq;
	memset(dis,-0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	q.push(s);dis[s]=0;vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;if(!e[i].flow)continue;
			if(dis[v]0;
}
int MCMF()
{
	int mncost=0,mxflow=0;
	while(spfa())
	{
		
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mncost+=dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}return mxflow;
}
int id(int x,int y){return (x-1)*m+y;}
int stk[500],top;
bool dfs(int x,int car)
{
	if(x==n*m)return true;
	for(int i=head[x+n*m];i;i=e[i].nxt)
	{
		int y=e[i].v;if(e[i].flow==inf)continue;
		if(e[i].flow<=0||y==x)continue;
		e[i].flow++;
		if(dfs(y,car))
		{
			if(y-x==m)stk[++top]=0;
			else stk[++top]=1;
			return true;
		}
	}return true;
}
int main()
{
	k=read();m=read(),n=read();s=0,t=n*m*2+1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			a[i][j]=read();
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			for(int k=0;k<2&&a[i][j]!=1;++k)
			{
				int x=i+dir[k][0],y=j+dir[k][1];
				if(x<1||y<1||x>n||y>m||a[x][y]==1)continue;
				ADD(id(i,j)+n*m,id(x,y),inf,0);
			}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			ADD(id(i,j),id(i,j)+n*m,inf,0);
			if(a[i][j]==2)ADD(id(i,j),id(i,j)+n*m,1,1);
		}
	ADD(s,id(1,1),k,0);ADD(id(n,m)+n*m,t,k,0);
	int num=MCMF();//cout<

最长k可重区间集问题

题面恶心人的就是没讲清是左闭右开区间。

将数轴上的每个点抽象为一个节点,连边 \((i,i+1,k,0)\)

那么上面那些边就代表了一个单位区间。

然后建一个超级源点 \(S\) ,让 \(S\) 向第一个节点连一条 \(0\) 费容量 \(k\) 的边,汇点 \(T\) 就默认为最后一个点。

那么这样是直接由 \(S\)\(k\) 的流量到 \(T\) 的。

考虑选取一个区间 \([l,r)\) ,他将覆盖第 \(l\) 到第 \(r-1\) 个单位线段,我们连边 \((l,r,1,r-l)\)

然后在这张图上跑最大费用最大流,费用即为答案。

为什么呢?在 \(S\) 顺着 \(0\) 费的边流向 \(T\) 的时候,如果选择了一段区间,则流量会减少 \(1\) ,然后在区间右端点的时候这个流量又会回来,这样最多连续减少 \(k\) 的流量,这就限制了 \(k\) 这个条件。(脑补一下画面吧...

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=0x3f3f3f3f;
int n,m,k,s,t,b[1005];
struct node
{
	int l,r,len;
}a[505];
struct edge
{
	int v,flow,dis,nxt;
}e[5005];
int ecnt=1,head[1005];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[1005],dis[1005];bool vis[1005];
bool spfa()
{
	queueq;
	memset(dis,-0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	q.push(s);dis[s]=0;vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;if(!e[i].flow)continue;
			if(dis[v]0;
}
int MCMF()
{
	int mxcost=0,mxflow=0;
	while(spfa())
	{
		
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mxcost+=dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}return mxcost;
}
int main()
{
	n=read(),k=read();
	for(int i=1;i<=n;++i)
	{
		int l=read(),r=read();
		b[i]=l,b[i+n]=r;a[i]=(node){l,r,r-l};
	}sort(b+1,b+n*2+1);m=unique(b+1,b+2*n+1)-(b+1);
	for(int i=1;i<=n;++i)
	{
		a[i].l=lower_bound(b+1,b+m+1,a[i].l)-b;
		a[i].r=lower_bound(b+1,b+m+1,a[i].r)-b;
		ADD(a[i].l,a[i].r,1,a[i].len);
	}s=0,t=m+1;
	for(int i=0;i<=m;++i)ADD(i,i+1,k,0);
	write(MCMF()),pc('\n');
	return 0;
}

最长k可重线段集问题

如果直接将线段映射成区间的话,能得 \(82\) 分的好成绩。

为什么会有问题呢?

例如: \((x,y_1),(x,y_2)\) ,这两个点连成的线段在映射成区间就是 \([x,x)\) ,明显有问题。

所以我们需要放缩法,把坐标都放大 \(2\) 倍, \([x,x)\) 变为 \([x,x+1)\)

但是还有一个问题,\([l,l),[l,r)\) 是没有交集的,但放缩使他们有了交集。所以对于 \([l,r)\) 我们要把它变成

\([l+1,r)\)

最后跑一跑最大费用最大流就好。

代码:

#include
#define pc(x) putchar(x)
#define int long long
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=5e17;
int n,m,k,s,t,b[1005];
struct node
{
	int lx,ly,rx,ry,len;
}a[505];
struct edge
{
	int v,flow,dis,nxt;
}e[50005];
int ecnt=1,head[10005];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[10005],dis[10005];bool vis[10005];
bool spfa()
{
	queueq;
	memset(dis,-0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	q.push(s);dis[s]=0;vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;if(!e[i].flow)continue;
			if(dis[v]0;
}
int MCMF()
{
	int mxcost=0,mxflow=0;
	while(spfa())
	{
		
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mxcost+=dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}return mxcost;
}
signed main()
{
	n=read(),k=read();
	for(int i=1;i<=n;++i)
	{
		int lx=read(),ly=read(),rx=read(),ry=read();b[i]=lx,b[i+n]=rx;
		a[i].lx=lx,a[i].ly=ly;a[i].rx=rx,a[i].ry=ry;
		a[i].len=(int)sqrt(pow(rx-lx,2)+pow(ry-ly,2));
	}sort(b+1,b+n*2+1);m=unique(b+1,b+2*n+1)-(b+1);
	for(int i=1;i<=n;++i)
	{
		a[i].lx=lower_bound(b+1,b+m+1,a[i].lx)-b;
		a[i].rx=lower_bound(b+1,b+m+1,a[i].rx)-b;
		a[i].lx*=2;a[i].rx*=2;
		if(a[i].lx==a[i].rx)a[i].rx++;else a[i].lx++;
		ADD(a[i].lx,a[i].rx,1,a[i].len);t=max(t,a[i].rx+1);
	}for(int i=0;i

汽车加油行驶问题

当前油量不能用流量和费用来表示,就只好用分层图来表示了。

关于建图有几个细节:

往回走要支付费用 \(B\)

没油库时加油的实际费用是 \(A+C\)

加油连边是自己的第 \(K\) 层走到自己的第 \(0\) 层。

有油库的话时必须加油的,所以自己的所有层都向第 \(0\) 层连边,第 \(0\) 层再向其他点连边。

建完图之后跑最小费用最大流捏!

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=0x3f3f3f3f;
int n,k,s,t,ai,bi,ci;
int a[105][105];
struct edge
{
	int v,flow,dis,nxt;
}e[1000005];
int ecnt=1,head[200005];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[200005],dis[200005];bool vis[200005];
bool spfa()
{
	queueq;
	memset(dis,0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	q.push(s);dis[s]=0;vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;if(!e[i].flow)continue;
			if(dis[v]>dis[u]+e[i].dis)
			{
				dis[v]=dis[u]+e[i].dis;pre[v]=i;
				if(!vis[v])vis[v]=true,q.push(v);
			}
		}
	}return dis[t]!=0x3f3f3f3f;
}
int MCMF()
{
	int mncost=0,mxflow=0;
	while(spfa())
	{
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mncost+=dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}return mncost;
}
int id(int x,int y,int z){return n*n*z+(x-1)*n+y;}
int main()
{
	n=read(),k=read(),ai=read(),bi=read(),ci=read();t=n*n*k+n*n+1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			a[i][j]=read();
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			for(int l=0;l=1)ADD(id(i,j,l),id(i-1,j,l+1),1,bi);
				if(j-1>=1)ADD(id(i,j,l),id(i,j-1,l+1),1,bi);
			}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
		{
			if(!a[i][j])ADD(id(i,j,k),id(i,j,0),1,ai+ci);
			for(int l=1;l<=k&&a[i][j];++l)
				ADD(id(i,j,l),id(i,j,0),1,ai);
		}
	ADD(s,id(1,1,0),1,0);
	for(int i=0;i<=k;++i)ADD(id(n,n,i),t,1,0);
	write(MCMF()),pc('\n');
	return 0;
}

孤岛营救问题

24题中最水之一。

状压加广搜就行了。

远古代码:

#include
#define re register 
using namespace std;
int n,m,p,k;
int t[12][12][12][12];
int cun[12][12];
bool vis[12][12][1100],f=false;
int dir[4][3]={{0,1},{1,0},{0,-1},{-1,0}};
struct node
{
	int x,y,ys,cnt;
}a[5000];
int head=1,tail=1;
int main()
{
	scanf("%d%d%d%d",&n,&m,&p,&k);
	for(re int i=1,xi1,xi2,yi1,yi2,gi;i<=k;++i)
	{
		scanf("%d%d%d%d%d",&xi1,&yi1,&xi2,&yi2,&gi);
		if(gi==0)	t[xi1][yi1][xi2][yi2]=999,t[xi2][yi2][xi1][yi1]=999;
		else 		t[xi1][yi1][xi2][yi2]=gi,t[xi2][yi2][xi1][yi1]=gi;
	}
	int qqq;
	scanf("%d",&qqq);
	for(re int i=1,xi1,yi1,qi;i<=qqq;++i)
	{
		scanf("%d%d%d",&xi1,&yi1,&qi);
		cun[xi1][yi1]+=(1<<(qi-1));
	}
	a[tail].x=a[tail].y=1;
	a[tail].ys=0,a[tail].cnt=0;
	tail++;
	while(headn||ty>m)continue;
			if(t[a[head].x][a[head].y][tx][ty]==999)continue;
			if(t[a[head].x][a[head].y][tx][ty])
			{
				int men=t[a[head].x][a[head].y][tx][ty];
				bool flag=a[head].ys&(1<<(men-1));
				if(!flag)continue;
			}
			if(cun[tx][ty])	yao=a[head].ys|cun[tx][ty];
			else yao=a[head].ys;
			if(vis[tx][ty][yao])continue;
			vis[tx][ty][yao]=1;
			a[tail].x=tx,a[tail].y=ty;
			a[tail].cnt=a[head].cnt+1;
			a[tail].ys=yao;
			if(tx==n&&ty==m){f=1;break;}
			tail++;
		}
		if(f==1)break;
		head++;
	}
	if(f==1)printf("%d\n",a[tail].cnt);
	else printf("-1\n");
	return 0;
}

深海机器人问题

这题和火星探险问题一样,一丁点变动。

所有出发点和源点连边,所有目标点和汇点连边。

点和点之间要建两条边,一条是 \((u,v,1,value)\) ,另一条是 \((u,v,inf,0)\)

跑最大费用最大流即可。(有个坑点,跑玩 SPFA 后费用可能为负数)

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=0x3f3f3f3f;
int a,b,n,m,s,t;
struct edge
{
	int v,flow,dis,nxt;
}e[500005];
int ecnt=1,head[100005];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[100005],dis[100005];bool vis[100005];
bool spfa()
{
	queueq;
	memset(dis,-0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	q.push(s);dis[s]=0;vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();q.pop();vis[u]=false;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].v;if(!e[i].flow)continue;
			if(dis[v]-10000;
}
int MCMF()
{
	int mxcost=0,mxflow=0;
	while(spfa())
	{
		
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mxcost+=dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}return mxcost;
}
int id(int x,int y){return (x-1)*m+y;}
int main()
{
	a=read(),b=read(),n=read()+1,m=read()+1;t=1000;
	for(int i=1;i<=n;++i)
		for(int j=1;j

分配问题

很显然二分图吧(这要看不出来建议重学

最小收益和最大收益分别跑最小费用最大流和最大费用最大流就行了。

远古代码:

#include
#define re register
using namespace std;
const int maxn=300;
const int maxm=50000;
const int inf=0x3f3f3f3f;
int n,m,s,t,cnt=1;
int dis[maxn],head[maxn],flow[maxn],pre[maxn];
int ccc[105][105];
bool vis[maxn];
struct node
{
	int v,nxt,dis,flow;
}a[maxm<<1];
void add(int u,int v,int w,int cost)
{
	a[++cnt].v=v;
	a[cnt].flow=w;
	a[cnt].dis=cost;
	a[cnt].nxt=head[u];
	head[u]=cnt;
}
bool minspfa()
{
	queueq;
	memset(dis,0x3f,sizeof dis);
	memset(vis,false,sizeof vis);
	while(!q.empty())q.pop();
	q.push(s);
	dis[s]=0,vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(re int i=head[u];i;i=a[i].nxt)
		{
			int v=a[i].v;
			if(!a[i].flow)continue;
			if(dis[v]>dis[u]+a[i].dis)
			{
				dis[v]=dis[u]+a[i].dis,pre[v]=i;
				if(!vis[v])vis[v]=true,q.push(v);
			}
		}
	}
	if(dis[t]==0x3f3f3f3f)return 0;
	return 1;
}
bool maxspfa()
{
	queueq;
	for(re int i=0;i<=t;++i)dis[i]=-inf;
	memset(vis,false,sizeof vis);
	while(!q.empty())q.pop();
	q.push(s);
	dis[s]=0,vis[s]=true;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=false;
		for(re int i=head[u];i;i=a[i].nxt)
		{
			int v=a[i].v;
			if(!a[i].flow)continue;
			if(dis[v]

运输问题

和分配问题一样的,分别跑一下就行。

代码:

#include
#define pc(x) putchar(x)
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:f;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0){x=-x;putchar('-');}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
const int inf=0x3f3f3f3f;
int n,m,s,t;
int a[105],b[105],c[105][105];
struct edge
{
	int v,flow,dis,nxt;
}e[1000005];
int ecnt=1,head[200005];
void add(int u,int v,int flow,int dis)
{
	e[++ecnt].v=v;e[ecnt].flow=flow;
	e[ecnt].dis=dis;e[ecnt].nxt=head[u];
	head[u]=ecnt;
}
void ADD(int u,int v,int flow,int dis)
{add(u,v,flow,dis);add(v,u,0,-dis);}
int pre[200005],dis[200005];bool vis[200005];
bool spfa(int op)
{
	if(op)
	{
		queueq;
		memset(dis,0x3f,sizeof dis);
		memset(vis,false,sizeof vis);
		q.push(s);dis[s]=0;vis[s]=true;
		while(!q.empty())
		{
			int u=q.front();q.pop();vis[u]=false;
			for(int i=head[u];i;i=e[i].nxt)
			{
				int v=e[i].v;if(!e[i].flow)continue;
				if(dis[v]>dis[u]+e[i].dis)
				{
					dis[v]=dis[u]+e[i].dis;pre[v]=i;
					if(!vis[v])vis[v]=true,q.push(v);
				}
			}
		}return dis[t]!=0x3f3f3f3f;
	}
	else
	{
		queueq;
		memset(dis,-0x3f,sizeof dis);
		memset(vis,false,sizeof vis);
		q.push(s);dis[s]=0;vis[s]=true;
		while(!q.empty())
		{
			int u=q.front();q.pop();vis[u]=false;
			for(int i=head[u];i;i=e[i].nxt)
			{
				int v=e[i].v;if(!e[i].flow)continue;
				if(dis[v]0;
	}
}
int MCMF(int op)
{
	int mncost=0,mxflow=0;
	while(spfa(op))
	{
		int v=t,d=inf;
		while(v!=s)
		{
			d=min(d,e[pre[v]].flow);
			v=e[pre[v]^1].v;
		}v=t,mxflow+=d;mncost+=d*dis[t];
		while(v!=s)
		{
			e[pre[v]].flow-=d;
			e[pre[v]^1].flow+=d;
			v=e[pre[v]^1].v;
		}
	}return mncost;
}
int main()
{
	m=read(),n=read();t=m+n+1;
	for(int i=1;i<=m;++i)a[i]=read();
	for(int i=1;i<=n;++i)b[i]=read();
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
			c[i][j]=read();
	for(int i=1;i<=m;++i)ADD(s,i,a[i],0);
	for(int i=1;i<=n;++i)ADD(i+m,t,b[i],0);
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
			ADD(i,j+m,inf,c[i][j]);
	write(MCMF(1)),pc('\n');
	memset(head,0,sizeof head);ecnt=1;
	for(int i=1;i<=m;++i)ADD(s,i,a[i],0);
	for(int i=1;i<=n;++i)ADD(i+m,t,b[i],0);
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
			ADD(i,j+m,inf,c[i][j]);
	write(MCMF(0)),pc('\n');
	return 0;
}

负载平衡问题

网络流做法很显然,这题建图一点都不难,就不展开说了。

主要是另一种做法。(题解嫖的/qiao)

贪心+数学

先来讲下普通均分纸牌问题:

普通均分纸牌问题就是 \(n\) 个小朋友排成一列,各自有 \(a[i]\) 张牌,每个人只能给相邻的人传递纸牌,问至少需要传递多少张纸牌才能使每个小朋友牌的个数相等。

设总牌数为 \(sum\)(即 \(sum=\sum{a[i]}\) ),则每个人最后会各自有 \(T=\frac{sum}{n}\) 张牌,设 \(g[i]=T-a[i]\) ,则让前 \(k\) 个人牌数相同需要的交换牌数为 \(\sum\limits_{i=1}^{i\leq k}{|s[i]|}\) ,其中 \(s[i]=\sum\limits_{j=1}^{j\leq i}{g[i]}\) ,可以这样理解,要让前kk个人牌数相同,要依次让前 \(1,2,3…k-1\) 个人牌数相同,多退少补,会与后边的人发生二者之差绝对值的牌数交换。所以移动总牌数 \(ans=\sum{|s[i]|}\)

再来讲下本题的环形均分纸牌问题:

环形均分纸牌问题就是 \(n\) 个小朋友围成了一圈(等同于第一人和最后一人相邻),这样的话其实可以同样的处理。

仔细思考环形均分纸牌问题可以发现一个性质:必定至少有两个相邻的人不需要从别人那里获得纸牌(这是显然的,不妨设这两个人的位置为 \(i\)\(i+1\) ,则环形序列中必定有满足条件 \(a[i]\leq T~;~a[i+1]\geq T\) 的两个相邻位置,这样 \(a[i],a[i+1]\) 之间没有交换,\(a[i]\leq T\) 可以从 \(a[i-1]\) 获得纸牌,\(a[i+1]\geq T\) 可以把多的纸牌给 \(a[i+2]\) )。

于是由上面的性质,我们直接破环成链,枚举相邻的不需要交换纸牌的两人(将其分别放在第一和最后一个位置)。

按开始的序列顺序,像普通均分纸牌一样处理出 \(s\) 数组,那么假设枚举的位置为 \(k\) ,则类比普通均分纸牌求法,新的 \(s[i]=s[i]-s[k]\) (注意ss为前缀和),于是 \(ans=\sum{|s[i]-s[k]|}\) ,我们套用中学数学知识可知当 \(s[k]\)\(s\) 中位数时,\(ans\) 最小。于是本题就解决了。

远古代码:

#include
#define re register
using namespace std;
long long n,a[105],sum,s[105];
int main()
{
    scanf("%lld",&n);
    for(re int i=1;i<=n;i++)
    scanf("%lld",&a[i]),sum+=a[i];sum/=n;
    for(re int i=1;i<=n;i++)
    a[i]-=sum,s[i]=s[i-1]+a[i];
    sort(s+1,s+n+1);sum=0;
    for(re int i=1;i<=n;i++)
    sum+=abs(s[n/2+1]-s[i]);
    printf("%lld\n",sum);
    return 0;
}

数字梯形问题

嘤嘤嘤,还没做。(/kk

机器人路径规划问题

目前无较优复杂度的正解。(还未被解决)

但可以用 IDA* 去A这道题。

嘤嘤嘤,还没做。

相关