网络流24题(完)
飞行员配对方案问题
题目背景
第二次世界大战期间,英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的两名飞行员,其中一名是英国飞行员,另一名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。
题目描述
一共有 \(n\) 个飞行员,其中有 \(m\) 个外籍飞行员和 \((n - m)\) 个英国飞行员,外籍飞行员从 \(1\) 到 \(m\) 编号,英国飞行员从 \(m + 1\) 到 \(n\) 编号。 对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
输入格式
输入的第一行是用空格隔开的两个正整数,分别代表外籍飞行员的个数 \(m\) 和飞行员总数 \(n\)。
从第二行起到倒数第二行,每行有两个整数 \(u, v\),代表外籍飞行员 \(u\) 可以和英国飞行员 \(v\) 配合。
输入的最后一行保证为 -1 -1
,代表输入结束。
输出格式
本题存在 Special Judge。
请输出能派出最多的飞机数量,并给出一种可行的方案。
输出的第一行是一个整数,代表一次能派出的最多飞机数量,设这个整数是 \(k\)。
第 \(2\) 行到第 \(k + 1\) 行,每行输出两个整数 \(u, v\),代表在你给出的方案中,外籍飞行员 \(u\) 和英国飞行员 \(v\) 配合。这 \(k\) 行的 \(u\) 与 \(v\) 应该互不相同。
样例 #1
样例输入 #1
5 10
1 7
1 8
2 6
2 9
2 10
3 7
3 8
4 7
4 8
5 10
-1 -1
样例输出 #1
4
1 7
2 9
3 8
5 10
提示
【数据范围与约定】
- 对于 \(100\%\) 的数据,保证 \(1 \leq m \leq n < 100\),\(1 \leq u \leq m < v \leq n\),同一组配对关系只会给出一次。
【提示】
- 请注意输入的第一行先读入 \(m\),再读入 \(n\)。
解题思路
裸的二分图匹配,将左部点向源点连边,右部点向汇点连边,跑一遍最大流即可
代码
// Problem: 飞行员配对方案问题
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2177/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Time: 2022-07-29 16:09:59
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],idx,cur[N],d[N],q[N],hh,tt;
int n,m,S,T;
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
d[S]=0;cur[S]=h[S];
q[0]=S;hh=0,tt=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
cur[ver]=h[ver];
d[ver]=d[t]+1;
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
cur[u]=i;
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int flow,r=0;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
int main()
{
cin>>m>>n;
S=0,T=n+1;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)add(S,i,1);
for(int i=m+1;i<=n;i++) add(i,T,1);
int a,b;
while(cin>>a>>b,a!=-1)add(a,b,1);
cout<<dinic()<<endl;
for(int i=0;i<idx;i+=2)
{
if(e[i]>m&&e[i]<=n&&!f[i])
{
cout<<e[i^1]<<" "<<e[i]<<endl;
}
}
return 0;
}
圆桌问题
题目描述
有来自 \(m\) 个不同单位的代表参加一次国际会议。第 \(i\) 个单位派出了 \(r_i\) 个代表。
会议的餐厅共有 \(n\) 张餐桌,第 \(i\) 张餐桌可容纳 \(c_i\) 个代表就餐。
为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。请给出一个满足要求的代表就餐方案。
输入格式
输入的第一行是用空格隔开的两个整数,分别代表单位的个数 \(m\) 和餐桌的个数 \(n\)。
第二行有 \(m\) 个用空格隔开的整数,第 \(i\) 个整数代表第 \(i\) 个单位的代表人数 \(r_i\)。
第三行有 \(n\) 个用空格隔开的整数,第 \(i\) 个整数代表第 \(i\) 张餐桌能容纳的人数 \(c_i\)。
输出格式
本题存在 Special Judge。
请输出是否存在满足要求的就餐方案,若存在,请给出任意一种可行的方案。
输出的第一行是一个非 \(0\) 即 \(1\) 的整数,若存在方案则输出 \(1\),否则输出 \(0\)。
若存在方案,则对于第 \(2\) 到第 \((m + 1)\) 行,在第 \((i + 1)\) 行输出 \(r_i\) 个整数,代表第 \(i\) 个单位的代表就餐的餐桌编号。
样例 #1
样例输入 #1
4 5
4 5 3 5
3 5 2 6 4
样例输出 #1
1
1 2 4 5
1 2 3 4 5
2 4 5
1 2 3 4 5
提示
【数据规模与约定】
- 对于 \(100\%\) 的数据,保证 \(1 \leq m \leq 150\),\(1 \leq n \leq 270\),\(1 \leq r_i, c_i \leq 10^3\)。
【提示】
- 请注意输入的第一行先读入 \(m\) 再读入 \(n\)。
解题思路
最大流跑二分图最大匹配即可,将所有单位视为左部的点,向源点连一条容量为人数的边,将所有桌子视为右部的点,向汇点连一条容量为可容纳人数最大值的边,在所有左部和所有右部点之间连一条容量为1的边。跑一遍最大流即可
代码
// Problem: 圆桌问题
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2181/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// Time: 2022-07-29 16:58:06
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],idx,q[N],d[N],n,m,S,T,cur[N];
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
int hh=0,tt=0;
q[0]=S;d[S]=0;cur[S]=h[S];
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
cur[u]=i;
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int r=0,flow;
while(bfs())
while(flow=find(S,INF))r+=flow;
return r;
}
int main()
{
memset(h,-1,sizeof h);
cin>>m>>n;
int tot=0;
S=0,T=m+n+1;
for(int i=1;i<=m;i++)
{
int c;
cin>>c;
tot+=c;
add(S,i,c);
}
for(int i=1;i<=n;i++)
{
int c;
cin>>c;
add(m+i,T,c);
}
for(int i=1;i<=m;i++)
for(int j=m+1;j<=m+n;j++)
add(i,j,1);
if(dinic()!=tot)cout<<0<<endl;
else
{
cout<<1<<endl;
for(int i=1;i<=m;i++)
{
for(int j=h[i];~j;j=ne[j])
{
if(e[j]>=m&&e[j]<=m+n&&!f[j])
{
cout<<e[j]-m<<" ";
}
}
cout<<endl;
}
}
return 0;
}
骑士共存问题
题目描述
在一个 \(n \times n\) 个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘上某些方格设置了障碍,骑士不得进入。
对于给定的 \(n \times n\) 个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击。
输入格式
第一行有 \(2\) 个正整数 \(n\) 和 \(m\) ,分别表示棋盘的大小和障碍数。接下来的 m 行给出障碍的位置。每行 \(2\) 个正整数,表示障碍的方格坐标。
输出格式
将计算出的共存骑士数输出。
样例 #1
样例输入 #1
3 2
1 1
3 3
样例输出 #1
5
提示
数据规模与约定
对于全部的测试点,保证 \(1 \leq n \leq 200\),\(0 \leq m \lt n^2\)。
解题思路
求最大独立集,每个棋子向能攻击到的位置连边,在二分图中最大独立集=总点数-最大匹配数。因此可以转换为求最大匹配,将x+y为奇数的点视为左部点,x+y为偶数的点视为右部点,左部向虚拟源点连容量为1的边,右部向虚拟汇点连一条容量为1的边,跑一遍最大流即可。(用匈牙利在最后一个点会T
代码
// Problem: P3355 骑士共存问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3355
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-07-25 21:56:20
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=4e4+10,M=4e5+10;
int n,m,S,T;
int h[N],e[M],ne[M],f[M],idx;
int q[N],d[N],cur[N];
bool g[210][210];
int get(int x,int y)
{
return (x-1)*n+y;
}
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
int hh=0,tt=0;
memset(d,-1,sizeof d);
q[0]=S,d[S]=0,cur[S]=h[S];
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
cur[u]=i;
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t,f[i^1]+=t,flow+=t;
}
}
return flow;
}
int dinic()
{
int r=0,flow;
while(bfs())while(flow=find(S,INF))r+=flow;
return r;
}
int main()
{
cin>>n>>m;
S=0,T=n*n+1;
memset(h,-1,sizeof h);
while(m--)
{
int x,y;
cin>>x>>y;
g[x][y]=true;
}
int dx[]={-2,-1,1,2,2,1,-1,-2};
int dy[]={1,2,2,1,-1,-2,-2,-1};
int tot=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(g[i][j])continue;
if(i+j&1)
{
add(S,get(i,j),1);
for(int k=0;k<8;k++)
{
int x=i+dx[k];
int y=j+dy[k];
if(x>=1&&x<=n&&y>=1&&y<=n&&!g[x][y])
{
add(get(i,j),get(x,y),INF);
}
}
}
else add(get(i,j),T,1);
tot++;
}
cout<<tot-dinic();
return 0;
}
最长不下降子序列问题
题目描述
给定正整数序列 \(x_1 \ldots, x_n\)。
- 计算其最长不下降子序列的长度 \(s\)。
- 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 \(s\) 的不下降子序列。
- 如果允许在取出的序列中多次使用 \(x_1\) 和 \(x_n\)(其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 \(s\) 的不下降子序列。
令 \(a_1, a_2, \ldots, a_s\) 为构造 \(S\) 时所使用的下标,\(b_1, b_2, \ldots, b_s\) 为构造 \(T\) 时所使用的下标。且 \(\forall i \in [1,s-1]\),都有 \(a_i \lt a_{i+1}\),\(b_i \lt b_{i+1}\)。则 \(S\) 和 \(T\) 不同,当且仅当 \(\exists i \in [1,s]\),使得 \(a_i \neq b_i\)。
输入格式
第一行有一个正整数 \(n\),表示给定序列的长度。接下来的一行有 \(n\) 个正整数\(x_1, ..., x_n\)。
输出格式
- 第 1 行是最长不下降子序列的长度 \(s\)。
- 第 2 行是可取出的长度为 \(s\) 的不下降子序列个数。
- 第 3 行是允许在取出的序列中多次使用 \(x_1\) 和 \(x_n\) 时可取出的长度为 \(s\) 的不同的不下降子序列个数。
样例 #1
样例输入 #1
4
3 6 2 5
样例输出 #1
2
2
3
提示
\(1 \le n\le 500\)
解题思路
1.DP
2.每个元素只能用一次->每个元素拆成两个点,在两点之间连一条容量为1的边。在DP可以更新的下标之间连一条容量为1的边,在\(dp[i]=1\)的点向源点连边,\(dp[i]=maxlen\)的点向汇点连容量为1的边,求一遍最大流即可
3.重新设置特殊边的边权,将其设为INF,在残留网络上再跑一遍最大流和原来的相加即可
代码
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e6+10;
int h[N],e[N],ne[N],f[N],cur[N],q[N],idx,d[N],S,T,n;
int a[N],g[N];
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
d[S]=0;q[0]=S;cur[S]=h[S];
int hh=0,tt=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
cur[u]=i;
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int flow,r=0;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
int main()
{
cin>>n;
S=0,T=2*n+1;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)cin>>a[i];
int ans=0;
for(int i=1;i<=n;i++)
{
add(i,i+n,1);
g[i]=1;
for(int j=1;j<i;j++)
if(a[j]<=a[i])
g[i]=max(g[i],g[j]+1);
for(int j=1;j<i;j++)
if(g[j]+1==g[i]&&a[j]<=a[i])
add(j+n,i,1);
ans=max(ans,g[i]);
if(g[i]==1) add(S,i,1);
}
for(int i=1;i<=n;i++)
if(g[i]==ans)
add(i+n,T,1);
cout<<ans<<endl;
if(ans==1)printf("%d\n%d\n",n,n);
else
{
int res=dinic();
cout<<res<<endl;
for(int i=0;i<idx;i+=2)
{
int a=e[i^1],b=e[i];
if(a==S&&b==1)f[i]=INF;
else if(a==1&&b==n+1)f[i]=INF;
else if(a==n&&b==n+n)f[i]=INF;
else if (a==n+n&&b==T)f[i]=INF;
}
cout<<res+dinic()<<endl;
}
return 0;
}
试题库问题
题目描述
问题描述:
假设一个试题库中有 \(n\) 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取 \(m\) 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。
编程任务:
对于给定的组卷要求,计算满足要求的组卷方案。
输入格式
第一行有两个正整数 \(k\) 和 \(n\)。\(k\) 表示题库中试题类型总数,\(n\) 表示题库中试题总数。
第二行有 \(k\) 个正整数,第 \(i\) 个正整数表示要选出的类型 \(i\) 的题数。这 \(k\) 个数相加就是要选出的总题数 \(m\)。
接下来的 \(n\) 行给出了题库中每个试题的类型信息。每行的第一个正整数 \(p\) 表明该题可以属于 \(p\) 类,接着的 \(p\) 个数是该题所属的类型号。
输出格式
输出共 \(k\) 行,第 \(i\) 行输出 i:
后接类型 \(i\) 的题号。
如果有多个满足要求的方案,只要输出一个方案。
如果问题无解,则输出No Solution!
。
样例 #1
样例输入 #1
3 15
3 3 4
2 1 2
1 3
1 3
1 3
1 3
3 1 2 3
2 2 3
2 1 3
1 2
1 2
2 1 2
2 1 3
2 1 2
1 1
3 1 2 3
样例输出 #1
1: 1 6 8
2: 7 9 10
3: 2 3 4 5
提示
\(2\leq k \leq 20\),\(k \leq n \leq 10^3\)。
感谢 @PhoenixEclipse 提供 spj
解题思路
注意到题目的匹配的意味,考虑将题目和所属类型分成两部分作为左右部点,因为“这 \(k\) 个数相加就是要选出的总题数”,所以每个题只能算作一种类型(有个sb没看到这一点导致第一次0分),然后容易想到从源点向试题连容量为1的边,在试题和所属类型之间连容量为1的边,类型和汇点之间连容量为所需题目数的边。跑一遍dinic判断是否满流即可。输出方案时遍历右部的点的邻边,如果连到了左部点并且剩余流量为0则说明该边被使用,输出边的终点即可。
代码
// Problem: P2763 试题库问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2763
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-02 08:38:56
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
struct ios {
inline char read(){
static const int IN_LEN=1<<18|1;
static char buf[IN_LEN],*s,*t;
return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
}
template <typename _Tp> inline ios & operator >> (_Tp&x){
static char c11,boo;
for(c11=read(),boo=0;!isdigit(c11);c11=read()){
if(c11==-1)return *this;
boo|=c11=='-';
}
for(x=0;isdigit(c11);c11=read())x=x*10+(c11^'0');
boo&&(x=-x);
return *this;
}
} io;
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],idx,d[N],cur[N],q[N],n,m,S,T,k,num[N],tot;
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
d[S]=0;q[0]=S;cur[S]=h[S];
int hh=0,tt=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
cur[ver]=h[ver];
d[ver]=d[t]+1;
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
int ver=e[i];
cur[u]=i;
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int r=0,flow;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
int main()
{
memset(h,-1,sizeof h);
S=0,T=1e3;
m=0;
//1-n题
//n+1-2n+k类型
cin>>k>>n;
for(int i=1;i<=k;i++)
{
int x;
cin>>x;
m+=x;
add(n+i,T,x);
}
for(int i=1;i<=n;i++)
add(S,i,1);
for(int i=1;i<=n;i++)
{
int p,x;
cin>>p;
while(p--)
{
cin>>x;
add(i,n+x,1);
}
}
int t=dinic();
if(t!=m)
{
puts("No Solution!");
}
else
{
for(int i=n+1;i<=n+k;i++)
{ cout<<i-n<<":";
for(int j=h[i];~j;j=ne[j])
{
if(f[j]==1&&e[j]>=1&&e[j]<=n)
{
cout<<" "<<e[j];
}
}
cout<<endl;
}
}
return 0;
}
方格取数问题
题目描述
有一个 \(m\) 行 \(n\) 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
输入格式
第一行是两个用空格隔开的整数,分别代表方格图的行数 \(m\) 和列数 \(n\)。
第 \(2\) 到第 \((m + 1)\) 行,每行 \(n\) 个整数,第 \((i + 1)\) 行的第 \(j\) 个整数代表方格图第 \(i\) 行第 \(j\) 列的的方格中的数字 \(a_{i, j}\)。
输出格式
输出一行一个整数,代表和最大是多少。
样例 #1
样例输入 #1
3 3
1 2 3
3 2 3
2 3 1
样例输出 #1
11
提示
数据规模与约定
对于 \(100\%\) 的数据,保证 \(1 \leq n, m \leq 100\),\(1 \leq a_{i, j} \leq 10^5\)。
提示
请注意输入的第一行先读入 \(m\) 再读入 \(n\)。
解题思路
将网格按奇偶黑白染色,将i+j为奇数的点向四周连容量为INF的边,向源点连容量为网格中的数w的边。将i+j为偶数的点向汇点连容量为w的边。没有相邻格子就转换成了独立集问题。取的数总和最大也就转化成了二分图最大权独立集问题。用总权值-最小权点覆盖集 =总权值-最小割 即可
代码
// Problem: P2774 方格取数问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2774
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-07 13:19:22
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#define int ll
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=2e5+10;
int h[N],e[N],ne[N],f[N],idx,d[N],cur[N],q[N],n,m,S,T;
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
d[S]=0;q[0]=S;cur[S]=h[S];
int hh=0,tt=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)
return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
int ver=e[i];
cur[u]=i;
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int flow,r=0;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
int get(int x,int y)
{
return (x-1)*m+y;
}
signed main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
S=0,T=n*m+1;
ll tot=0;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int w;
cin>>w;
tot+=w;
if(i+j&1)
{
add(S,get(i,j),w);
for(int k=0;k<4;k++)
{
int xx=i+dx[k];
int yy=j+dy[k];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m)
{
add(get(i,j),get(xx,yy),INF);
}
}
}
else
add(get(i,j),T,w);
}
cout<<tot-dinic();
return 0;
}
太空飞行计划问题
题目描述
W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合 $ E = { E_1, E_2, \cdots, E_m } $,和进行这些实验需要使用的全部仪器的集合 $ I = { I_1, I_2, \cdots, I_n } $。实验 $ E_j $ 需要用到的仪器是 $ I $ 的子集 $ R_j \subseteq I $。
配置仪器 $ I_k $ 的费用为 $ c_k $ 美元。实验 $ E_j $ 的赞助商已同意为该实验结果支付 $ p_j $ 美元。W 教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。
输入格式
第 $ 1 $ 行有 $ 2 $ 个正整数 $ m $ 和 $ n \(。\) m $ 是实验数,$ n $ 是仪器数。接下来的 $ m $ 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的 $ n $ 个数是配置每个仪器的费用。
输出格式
第 $ 1 $ 行是实验编号,第 $ 2 $ 行是仪器编号,最后一行是净收益。
样例 #1
样例输入 #1
2 3
10 1 2
25 2 3
5 6 7
样例输出 #1
1 2
1 2 3
17
提示
感谢 @FlierKing 提供 spj
$1 \leq n, m \leq 50 $
这道题数据是在 Windows 生成的,输入数据中所有的换行都是 \r\n
而不是 \n
读入某实验需要用到的仪器编号的时候,可以这么读入。(感谢@zhouyonglong的提供)
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
while (sscanf(tools+ulen,"%d",&tool)==1)//之前已经用scanf读完了赞助商同意支付该实验的费用
{//tool是该实验所需仪器的其中一个
//这一行,你可以将读进来的编号进行储存、处理,如连边。
if (tool==0)
ulen++;
else {
while (tool) {
tool/=10;
ulen++;
}
}
ulen++;
}
解题思路
裸的最大权闭合图问题,没什么好说的
代码
// Problem: P2762 太空飞行计划问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2762
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-07 14:12:33
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<sstream>
#include<climits>
struct ios {
inline char read(){
static const int IN_LEN=1<<18|1;
static char buf[IN_LEN],*s,*t;
return (s==t)&&(t=(s=buf)+fread(buf,1,IN_LEN,stdin)),s==t?-1:*s++;
}
template <typename _Tp> inline ios & operator >> (_Tp&x){
static char c11,boo;
for(c11=read(),boo=0;!isdigit(c11);c11=read()){
if(c11==-1)return *this;
boo|=c11=='-';
}
for(x=0;isdigit(c11);c11=read())x=x*10+(c11^'0');
boo&&(x=-x);
return *this;
}
} io;
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],idx,cur[N],d[N],q[N],n,m,S,T;
bool st[N];
void add(int a,int b ,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
d[S]=0;q[0]=S;cur[S]=h[S];
int hh=0,tt=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
int ver=e[i];
cur[u]=i;
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int flow,r=0;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
void dfs(int u)
{
st[u]=true;
for(int i=h[u];~i;i=ne[i])
{
int ver=e[i];
if(!st[ver]&&f[i])
dfs(ver);
}
}
int main()
{
memset(h,-1,sizeof h);
cin>>m>>n;
S=0,T=1000;
int tot=0;
getchar();
//实验1-m,仪器m+1-m+n
for(int i=1;i<=m;i++)
{
int w,id;
string line;
getline(cin,line);
stringstream ssin(line);
ssin>>w;
add(S,i,w);
while(ssin>>id)
{
add(i,m+id,INF);
}
tot+=w;
}
for(int i=1;i<=n;i++)
{
int w;
cin>>w;
add(m+i,T,w);
}
int res=dinic();
dfs(S);
for(int i=1;i<=m;i++)
if(st[i])printf("%d ",i);
puts("");
for(int i=1+m;i<=n+m;i++)
if(st[i])
printf("%d ",i-m);
printf("\n%d",tot-res);
return 0;
}
[CTSC1999]家园 / 星际转移问题
题目描述
由于人类对自然资源的消耗,人们意识到大约在 2300 年之后,地球就不能再居住了。于是在月球上建立了新的绿地,以便在需要时移民。令人意想不到的是,2177 年冬由于未知的原因,地球环境发生了连锁崩溃,人类必须在最短的时间内迁往月球。
现有 \(n\) 个太空站位于地球与月球之间,且有 \(m\) 艘公共交通太空船在其间来回穿梭。每个太空站可容纳无限多的人,而太空船的容量是有限的,第 \(i\) 艘太空船只可容纳 \(h_i\) 个人。每艘太空船将周期性地停靠一系列的太空站,例如 \((1,3,4)\) 表示该太空船将周期性地停靠太空站 \(134134134\dots\)。每一艘太空船从一个太空站驶往任一太空站耗时均为 \(1\)。人们只能在太空船停靠太空站(或月球、地球)时上、下船。
初始时所有人全在地球上,太空船全在初始站。试设计一个算法,找出让所有人尽快地全部转移到月球上的运输方案。
输入格式
输入的第一行是三个用空格隔开的整数,分别代表太空站个数 \(n\),太空船个数 \(m\) 和地球上的人数 \(k\)。
第 \(2\) 到第 \((m + 1)\) 行,每行给出一艘太空船的信息,第 \((i + 1)\) 行的第一个整数 \(h_i\) 代表第 \(i\) 艘太空船可容纳的人数。随后有一个整数 \(r_i\),代表第 \(i\) 艘太空船停靠的站点数。之后有 \(r_i\) 个整数,依次代表该太空船停靠站点的编号 \(S_{i, j}\),其中太空站自 \(1\) 至 \(n\) 编号,地球编号为 \(0\),月球编号为 \(-1\)。
输出格式
输出一行一个整数,代表将所有人转移到月球上的最短用时。若无解则输出 \(0\)。
样例 #1
样例输入 #1
2 2 1
1 3 0 1 2
1 3 1 2 -1
样例输出 #1
5
提示
数据规模与约定
对于 \(100\%\) 的数据,保证:
- \(1 \leq n \leq 13\)。
- \(1 \leq m \leq 20\)。
- \(1 \leq k \leq 50\)。
- \(1 \leq r_i \leq n + 2\)。
- \(-1 \leq S_{i, j}\leq n\)。
解题思路
将每个点按照日期分成多个点。首先利用并查集判断起点和终点的连通性。首先将源点向0号点的第0天连边,汇点向终点的第0天连边,然后随着天数推移不断建边,不断跑dinic并累加可行流,当可行流>=人数 break输出答案即可
代码
// Problem: P2754 [CTSC1999]家园 / 星际转移问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2754
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-02 10:49:43
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstdio>
#include<fstream>
#include<algorithm>
#include<cmath>
#include<deque>
#include<vector>
#include<queue>
#include<string>
#include<cstring>
#include<map>
#include<stack>
#include<set>
#include<climits>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],idx,d[N],cur[N],q[N],n,m,S,T,p[N],k;
int find(int x){return p[x]==x?x:p[x]=find(p[x]);}
struct ship{
int h,r,id[30];
}ships[50];
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
d[S]=0;q[0]=S;cur[S]=h[S];
int hh=0,tt=0;
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
int ver=e[i];
cur[ver]=h[ver];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int flow,r=0;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
int get(int i,int d)//节点i的第d天
{
return i+d*(n+2);
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m>>k;
S=N-2,T=N-1;
for(int i=1;i<30;i++)p[i]=i;
for(int i=0;i<m;i++)
{
int a,b;
cin>>a>>b;
ships[i]={a,b};
for(int j=0;j<b;j++)
{
int id;
cin>>id;
if(id==-1)id=n+1;
ships[i].id[j]=id;
if(j)
{
int x=ships[i].id[j-1];
p[find(x)]=find(id);
}
}
}
if(find(0)!=find(n+1))puts("0");
else
{
add(S,get(0,0),k);
add(get(n+1,0),T,INF);
int day=1,res=0;
while(true)
{
add(get(n+1,day),T,INF);
for(int i=0;i<n+1;i++)add(get(i,day-1),get(i,day),INF);
for(int i=0;i<m;i++)
{
int r=ships[i].r;
int a=ships[i].id[(day-1)%r],b=ships[i].id[day%r];
add(get(a,day-1),get(b,day),ships[i].h);
}
res+=dinic();
if(res>=k)break;
day++;
}
cout<<day<<endl;
}
return 0;
}
运输问题
题目描述
\(W\) 公司有 \(m\) 个仓库和 \(n\) 个零售商店。第 \(i\) 个仓库有 \(a_i\) 个单位的货物;第 \(j\) 个零售商店需要 \(b_j\) 个单位的货物。
货物供需平衡,即\(\sum\limits_{i=1}^{m}a_i=\sum\limits_{j=1}^{n}b_j\)。
从第 \(i\) 个仓库运送每单位货物到第 \(j\) 个零售商店的费用为 \(c_{ij}\) 。
试设计一个将仓库中所有货物运送到零售商店的运输方案,使总运输费用最少。
输入格式
第 \(1\) 行有 \(2\) 个正整数 \(m\) 和 \(n\),分别表示仓库数和零售商店数。
接下来的一行中有 \(m\) 个正整数 \(a_i\),表示第 \(i\) 个仓库有 \(a_i\)个单位的货物。
再接下来的一行中有 \(n\) 个正整数 \(b_j\),表示第 \(j\) 个零售商店需要 \(b_j\) 个单位的货物。
接下来的 \(m\) 行,每行有 \(n\) 个整数,表示从第 \(i\) 个仓库运送每单位货物到第 \(j\) 个零售商店的费用 \(c_{ij}\)。
输出格式
两行分别输出最小运输费用和最大运输费用。
样例 #1
样例输入 #1
2 3
220 280
170 120 210
77 39 105
150 186 122
样例输出 #1
48500
69140
提示
\(1 \leq n, m \leq 100\)
解题思路
费用流裸题。。。如果不理解原理可以看这篇博客,代码里有详细注释
代码
// Problem: P4015 运输问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4015
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Time: 2022-08-12 19:19:02
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],w[N],f[N],idx;
int q[N],d[N],pre[N],incf[N],n,m,S,T;
bool st[N]; int flow,cost;
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
int hh=0,tt=1;
memset(d,0x3f,sizeof d);
memset(incf,0,sizeof incf);
q[0]=S;d[S]=0;incf[S]=INF;
while(hh!=tt)
{
int t=q[hh++];
if(hh==N)hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(f[i]&&d[ver]>d[t]+w[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q[tt++]=ver;
st[ver]=true;
if(tt==N)tt=0;
}
}
}
}
return incf[T]>0;
}
int EK()
{
int cost=0;
while(spfa())
{
int t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
int main()
{
scanf("%d%d",&m,&n);
S=0,T=n+m+1;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
add(S,i,x,0);
}
for(int i=1;i<=n;i++)
{
int b;
scanf("%d",&b);
add(i+m,T,b,0);
}
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
int c;
scanf("%d",&c);
add(i,m+j,INF,c);
}
printf("%d\n",EK());
for(int i=0;i<idx;i+=2)
{
f[i]+=f[i^1],f[i^1]=0;
swap(w[i],w[i^1]);
}
printf("%d\n",-EK());
return 0;
}
负载平衡问题
题目描述
\(G\) 公司有 \(n\) 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 \(n\) 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
输入格式
第一行一个正整数 \(n\),表示有 \(n\) 个仓库。
第二行 \(n\) 个正整数,表示 \(n\) 个仓库的库存量。
输出格式
输出最少搬运量。
样例 #1
样例输入 #1
5
17 9 14 16 4
样例输出 #1
11
提示
\(1 \leq n \leq 100\)。
解题思路
费用流的简单题,每个仓库向相邻仓库连边,算出每个仓库最终的货物量,然后源汇点连边即可
代码
// Problem: P4016 负载平衡问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4016
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-14 11:23:18
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],w[N],idx;
int d[N],incf[N],st[N],n,m,S,T,pre[N];
int s[N];
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
queue<int>q;
memset(d,0x3f,sizeof d);
memset(incf,0,sizeof incf);
d[S]=0;incf[S]=INF;q.push(S);
while(q.size())
{
int t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(f[i]&&d[ver]>d[t]+w[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
int EK()
{
int cost=0;
while(spfa())
{
int t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n;
S=0,T=n+1;
int tot=0;
for(int i=1;i<=n;i++)
{
cin>>s[i];
tot+=s[i];
add(i,i<n?i+1:1,INF,1);
add(i,i>1?i-1:n,INF,1);
}
tot/=n;
for(int i=1;i<=n;i++)
{
if(s[i]>tot)add(i,T,s[i]-tot,0);
if(s[i]<tot)add(S,i,tot-s[i],0);
}
cout<<EK()<<endl;
return 0;
}
分配问题
题目描述
有 \(n\) 件工作要分配给 \(n\) 个人做。第 \(i\) 个人做第 \(j\) 件工作产生的效益为 \(c_{ij}\) 。试设计一个将 \(n\) 件工作分配给 \(n\) 个人做的分配方案,使产生的总效益最大。
输入格式
文件的第 \(1\) 行有 \(1\) 个正整数 \(n\),表示有 \(n\) 件工作要分配给 \(n\) 个人做。
接下来的 \(n\) 行中,每行有 \(n\) 个整数 \(c_{ij}\),表示第 \(i\) 个人做第 \(j\) 件工作产生的效益为 \(c_{ij}\)。
输出格式
两行分别输出最小总效益和最大总效益。
样例 #1
样例输入 #1
5
2 2 2 1 2
2 3 1 2 4
2 0 1 1 1
2 3 4 3 3
3 2 1 2 1
样例输出 #1
5
14
提示
\(1 \leq n \leq 100\)
一个人只能修一个工件
解题思路
费用流跑二分图最大权匹配的模板题,比KM好写)
代码
// Problem: P4014 分配问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4014
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Time: 2022-08-14 15:18:06
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],w[N],idx;
int incf[N],d[N],st[N],n,m,S,T,pre[N];
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(d,0x3f,sizeof d);
memset(incf,0,sizeof incf);
queue<int>q;
q.push(S);d[S]=0;incf[S]=INF;
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]>d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
st[ver]=true;
q.push(ver);
}
}
}
}
return incf[T]>0;
}
int EK()
{
int cost = 0;
while (spfa())
{
int t = incf[T];
cost += t * d[T];
for (int i = T; i != S; i = e[pre[i] ^ 1])
{
f[pre[i]] -= t;
f[pre[i] ^ 1] += t;
}
}
return cost;
}
int main()
{
cin>>n;
S=0;T=2*n+1;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)
{
add(S,i,1,0);
add(i+n,T,1,0);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x;
cin>>x;
add(i,j+n,1,x);
}
cout<<EK()<<endl;
for(int i=0;i<idx;i+=2)
f[i]+=f[i^1],f[i^1]=0,swap(w[i],w[i^1]);
cout<<-EK()<<endl;
return 0;
}
数字梯形问题
题目描述
给定一个由 \(n\) 行数字组成的数字梯形如下图所示。
梯形的第一行有 \(m\) 个数字。从梯形的顶部的 \(m\) 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。
分别遵守以下规则:
-
从梯形的顶至底的 \(m\) 条路径互不相交;
-
从梯形的顶至底的 \(m\) 条路径仅在数字结点处相交;
-
从梯形的顶至底的 \(m\) 条路径允许在数字结点相交或边相交。
输入格式
第 \(1\) 行中有 \(2\) 个正整数 \(m\) 和 \(n\),分别表示数字梯形的第一行有 \(m\) 个数字,共有 \(n\) 行。接下来的 \(n\) 行是数字梯形中各行的数字。
第 \(1\) 行有 \(m\) 个数字,第 \(2\) 行有 \(m+1\) 个数字,以此类推。
输出格式
将按照规则 \(1\),规则 \(2\),和规则 \(3\) 计算出的最大数字总和并输出,每行一个最大总和。
样例 #1
样例输入 #1
2 5
2 3
3 4 5
9 10 9 1
1 1 10 1 1
1 1 10 12 1 1
样例输出 #1
66
75
77
提示
\(1\leq m,n \leq 20\)
解题思路
费用在点上,所以拆点,入点与出点之间的费用设为方格上的数字,然后注意源点汇点连边和两层之间的点连边,跑最大费用最大流即可
代码
// Problem: P4013 数字梯形问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4013
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Time: 2022-08-14 16:02:49
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define zp ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],w[N],f[N],idx,d[N],incf[N],pre[N],n,m,S,T,st[N];
int id[40][40],cost[40][40];
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
queue<int>q;
memset(d,-0x3f,sizeof d);
memset(incf,0,sizeof incf);
d[S]=0;q.push(S);incf[S]=INF;
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]<d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
int EK()
{
int cost=0;
while(spfa())
{
int t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
int main()
{
cin>>m>>n;
int cnt=0;
S=++cnt;T=++cnt;
for(int i=1;i<=n;i++)
for(int j=1;j<=m+i-1;j++)
{
cin>>cost[i][j];
id[i][j]=++cnt;
}
memset(h,-1,sizeof h);idx=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m+i-1;j++)
{
add(id[i][j]<<1,id[i][j]<<1|1,1,cost[i][j]);
if(i==1)add(S,id[i][j]<<1,1,0);
if(i==n)add(id[i][j]<<1|1,T,1,0);
if(i<n)
{
add(id[i][j]<<1|1,id[i+1][j]<<1,1,0);
add(id[i][j]<<1|1,id[i+1][j+1]<<1,1,0);
}
}
cout<<EK()<<endl;
memset(h,-1,sizeof h);idx=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m+i-1;j++)
{
add(id[i][j]<<1,id[i][j]<<1|1,INF,cost[i][j]);
if(i==1)add(S,id[i][j]<<1,1,0);
if(i==n)add(id[i][j]<<1|1,T,INF,0);
if(i<n)
{
add(id[i][j]<<1|1,id[i+1][j]<<1,1,0);
add(id[i][j]<<1|1,id[i+1][j+1]<<1,1,0);
}
}
cout<<EK()<<endl;
memset(h,-1,sizeof h);idx=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m+i-1;j++)
{
add(id[i][j]<<1,id[i][j]<<1|1,INF,cost[i][j]);
if(i==1)add(S,id[i][j]<<1,1,0);
if(i==n)add(id[i][j]<<1|1,T,INF,0);
if(i<n)
{
add(id[i][j]<<1|1,id[i+1][j]<<1,INF,0);
add(id[i][j]<<1|1,id[i+1][j+1]<<1,INF,0);
}
}
cout<<EK()<<endl;
return 0;
}
最小路径覆盖问题
题目描述
给定有向图 \(G=(V,E)\) 。设 \(P\) 是 \(G\) 的一个简单路(顶点不相交)的集合。如果 \(V\) 中每个定点恰好在 \(P\) 的一条路上,则称 \(P\) 是 \(G\) 的一个路径覆盖。\(P\) 中路径可以从 \(V\) 的任何一个定点开始,长度也是任意的,特别地,可以为 \(0\)。\(G\) 的最小路径覆盖是 \(G\) 所含路径条数最少的路径覆盖。设计一个有效算法求一个 DAG(有向无环图)\(G\) 的最小路径覆盖。
输入格式
第一行有两个正整数 \(n\) 和 \(m\)。\(n\) 是给定 DAG(有向无环图)\(G\) 的顶点数,\(m\) 是 \(G\) 的边数。接下来的 \(m\) 行,每行有两个正整数 \(i\) 和 \(j\) 表示一条有向边 \((i,j)\)。
输出格式
从第一行开始,每行输出一条路径。文件的最后一行是最少路径数。
样例 #1
样例输入 #1
11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11
样例输出 #1
1 4 7 10 11
2 5 8
3 6 9
3
提示
对于 \(100\%\) 的数据,\(1\leq n\leq 150\),\(1\leq m\leq 6000\)。
由 @FlierKing 提供 SPJ
解题思路
最小路径覆盖=总点数-最大匹配数 ,把每个点拆为入点出点,A->B的一条边对应从A的出点连向B的入点的一条边。对于左部没有被匹配的点说明没有出边,对应路径终点。然后dinic跑最大匹配后dfs输出路径就行
代码
// Problem: P2764 最小路径覆盖问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2764
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-19 13:03:26
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],d[N],cur[N],n,m,S,T,idx,st[N];
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b];h[b]=idx++;
}
bool bfs()
{
memset(d,-1,sizeof d);
queue<int>q;
q.push(S);d[S]=0;cur[S]=h[S];
while(q.size())
{
auto t=q.front();
q.pop();
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q.push(ver);
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
cur[u]=i;
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int flow,r=0;
while(bfs())
while(flow=find(S,INF))
r+=flow;
return r;
}
void dfs(int u)
{
st[u]=true;//标记访问过
cout<<u<<" ";//输出
for(int i=h[u];~i;i=ne[i])//遍历这个左部点所有的边
{
if( e[i] == S ) continue;//如果终点是源点则跳过
else if(e[i] > n && st[e[i]-n]) continue;//如果下一个点已经访问过了就跳过
if(f[i]==0)//如果这条边满流则说明选中了
{
if(e[i]>n)dfs(e[i]-n);
}
}
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
S=0,T=2*n+1;
for(int i=1;i<=n;i++)
{
add(S,i,1);
add(i+n,T,1);
}
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b+n,1);
}
int ans=dinic();
for(int i=0;i<idx;i+=2)//遍历所有正向边
{
if(e[i^1]==S||e[i]==T)continue;//如果与源汇点相连则跳过
if(f[i]==0&&!st[e[i^1]])//如果这条边没有走过并且出发点左部点没有访问过
{
dfs(e[i^1]);puts("");//从左部出发点开始搜索
}
}
cout<<n-ans<<endl;
return 0;
}
魔术球问题
题目描述
假设有 \(n\) 根柱子,现要按下述规则在这 \(n\) 根柱子中依次放入编号为 \(1\),\(2\),\(3\),...的球“
-
每次只能在某根柱子的最上面放球。
-
同一根柱子中,任何 \(2\) 个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 \(n\) 根柱子上最多能放多少个球。例如,在 \(4\) 根柱子上最多可放 \(11\) 个球。
对于给定的 \(n\),计算在 \(n\) 根柱子上最多能放多少个球。
输入格式
只有一行一个整数 \(n\),代表柱子数。
输出格式
本题存在 Special Judge。
请将 \(n\) 根柱子上最多能放的球数以及相应的放置方案输出。
输出的第一行是球数。
接下来的 \(n\) 行,每行若干个整数,代表一根柱子上的球的编号,数字间用单个空格隔开。
样例 #1
样例输入 #1
4
样例输出 #1
11
1 8
2 7 9
3 6 10
4 5 11
提示
数据规模与约定
对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 55\)。
解题思路
柱子没什么用,把所有相加是平方数的点连在一起,就转化成了n条路径最多能覆盖多少个点,然后枚举就行,转化为最小路径覆盖问题。可以记录前驱和后继记录路径。代码里有解释
代码
// Problem: P2765 魔术球问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2765
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-19 14:44:20
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#include<cmath>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],idx,cur[N],d[N],n,m,S,T;
int a[N],ans[N],pre[N],q[N];
void add(int a,int b,int c)
{
e[idx]=b;f[idx]=c;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;ne[idx]=h[b],h[b]=idx++;
}
bool bfs()
{
int hh=0,tt=0;
memset(d,-1,sizeof d);
q[0]=S;d[S]=0;cur[S]=h[S];
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]==-1&&f[i])
{
d[ver]=d[t]+1;
cur[ver]=h[ver];
if(ver==T)return true;
q[++tt]=ver;
}
}
}
return false;
}
int find(int u,int limit)
{
if(u==T)return limit;
int flow=0;
for(int i=cur[u];~i&&flow<limit;i=ne[i])
{
cur[u]=i;
int ver=e[i];
if(d[ver]==d[u]+1&&f[i])
{
int t=find(ver,min(f[i],limit-flow));
if(!t)d[ver]=-1;
f[i]-=t;f[i^1]+=t;flow+=t;
}
}
return flow;
}
int dinic()
{
int r=0,flow;
while(bfs())
while(flow=find(S,INF))r+=flow;
return r;
}
bool check(int x)
{
int res=sqrt(x);
res*=res;
return res==x;
}
int main()
{
cin>>n;
memset(h,-1,sizeof h);
memset(pre,-1,sizeof pre);
memset(ans,0,sizeof ans);
S=0,T=1; idx=0;
int num=0,res=0,tot=0,tmp;
while(1)
{
++tot,++num;//柱子数,用到的数字,先预支一个柱子
add(S,num<<1,1);add(num<<1|1,T,1);//拆点向源汇点连边
for(int i=1;i<num;i++)
{
if(check(i+num))//判断能不能和前面的数字连边
add(i<<1,num<<1|1,1);
}
int t=dinic();//t=1,说明成功和前面的数字连边了,不用新开柱子,t=0说明无法接到前面数字后
tot-=t;//等于1则成功放进去了,不用新开柱子,把前面预支的柱子减去
if(tot>n)break;//超过n个柱子就不合法
}
num--;//去除最后一个使状态不合法的数字
cout<<num<<endl;
for(int i=1;i<=num;i++)//枚举所有数字
{
int u=i<<1;//左部点
for(int j=h[u];~j;j=ne[j])//枚举所有连的边
{
int ver=e[j],flow=f[j];//右部点,流量
if(flow==0&&ver>>1)//如果满流则说明这两个数字连在一起了,ver>>1获得右部点拆点的左部点
{
//记录转移点
pre[ver>>1]=i;
ans[i]=ver>>1;
}
}
}
//反推路径
for(int i=1;i<=num-1;i++)
{
if(pre[i]<0)
{
int x=i;
cout<<x<<" ";
while(ans[x])
{
cout<<ans[x]<<" ";
x=ans[x];
}
cout<<endl;
}
}
return 0;
}
餐巾计划问题
题目描述
一个餐厅在相继的 \(N\) 天里,每天需用的餐巾数不尽相同。假设第 \(i\) 天需要 \(r_i\)块餐巾( i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 \(p\) 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 \(n\) 天(\(n>m\)),其费用为 \(s\) 分(\(s<f\))。
每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。
试设计一个算法为餐厅合理地安排好 \(N\) 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。
输入格式
由标准输入提供输入数据。文件第 1 行有 1 个正整数 \(N\),代表要安排餐巾使用计划的天数。
接下来的一行是餐厅在相继的 \(N\) 天里,每天需用的餐巾数。
最后一行包含5个正整数\(p,m,f,n,s\)。\(p\) 是每块新餐巾的费用; \(m\) 是快洗部洗一块餐巾需用天数; \(f\) 是快洗部洗一块餐巾需要的费用; \(n\) 是慢洗部洗一块餐巾需用天数; \(s\) 是慢洗部洗一块餐巾需要的费用。
输出格式
将餐厅在相继的 N 天里使用餐巾的最小总花费输出
样例 #1
样例输入 #1
3
1 7 5
11 2 2 3 1
样例输出 #1
134
提示
N<=2000
ri<=10000000
p,f,s<=10000
时限4s
解题思路
拆点,拆为每天剩余的餐巾和需要的餐巾,根据题目连边建立餐巾的来源以及转移关系即可
代码
// Problem: P1251 餐巾计划问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1251
// Memory Limit: 125 MB
// Time Limit: 4000 ms
// Time: 2022-08-19 16:22:43
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
ll h[N],e[N],ne[N],f[N],w[N],idx,n,m,S,T,incf[N],d[N],st[N],pre[N];
ll p,ft,fp,lt,lp,need[N];
void add(ll a,ll b,ll c,ll d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(d,0x3f,sizeof d);
memset(incf,0,sizeof incf);
queue<ll>q;
d[S]=0;incf[S]=INF;q.push(S);
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(ll i=h[t];~i;i=ne[i])
{
ll ver=e[i];
if(f[i]&&d[ver]>d[t]+w[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
ll EK()
{
ll cost=0;
while(spfa())
{
ll t=incf[T];
cost+=d[T]*t;
for(ll i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;
f[pre[i]^1]+=t;
}
}
return cost;
}
int main()
{
IOS
memset(h,-1,sizeof h);
cin>>n;
for(ll i=1;i<=n;i++)cin>>need[i];
S=0,T=2*n+1;
cin>>p>>ft>>fp>>lt>>lp;
for(ll i=1;i<=n;i++)
{
ll r=need[i];
add(S,i,r,0);
add(n+i,T,r,0);
add(S,n+i,INF,p);
if(i<n)add(i,i+1,INF,0);
if(i+ft<=n)add(i,i+n+ft,INF,fp);
if(i+lt<=n)add(i,i+n+lt,INF,lp);
}
cout<<EK()<<endl;
return 0;
}
深海机器人问题
题目描述
深海资源考察探险队的潜艇将到达深海的海底进行科学考察。
潜艇内有多个深海机器人。潜艇到达深海海底后,深海机器人将离开潜艇向预定目标移动。
深海机器人在移动中还必须沿途采集海底生物标本。沿途生物标本由最先遇到它的深海机器人完成采集。
每条预定路径上的生物标本的价值是已知的,而且生物标本只能被采集一次。
本题限定深海机器人只能从其出发位置沿着向北或向东的方向移动,而且多个深海机器人可以在同一时间占据同一位置。
用一个 \(P\times Q\) 网格表示深海机器人的可移动位置。西南角的坐标为 \((0,0)\),东北角的坐标为 \((Q,P)\) 。
给定每个深海机器人的出发位置和目标位置,以及每条网格边上生物标本的价值。
计算深海机器人的最优移动方案, 使深海机器人到达目的地后,采集到的生物标本的总价值最高。
输入格式
文件的第 \(1\) 行为深海机器人的出发位置数 \(a\),和目的地数 \(b\) 。
第 \(2\) 行为 \(P\) 和 \(Q\) 的值。
接下来的 \(P+1\) 行,每行有 \(Q\) 个正整数,表示向东移动路径上生物标本的价值,行数据依从南到北方向排列。
再接下来的 \(Q+1\) 行,每行有 \(P\) 个正整数,表示向北移动路径上生物标本的价值,行数据依从西到东方向排列。
接下来的 \(a\) 行,每行有 \(3\) 个正整数 \(k,x,y\),表示有 \(k\) 个深海机器人从 \((x,y)\) 位置坐标出发。
再接下来的 \(b\) 行,每行有 \(3\) 个正整数 \(r,x,y\) ,表示有 \(r\) 个深海机器人可选择 \((x,y)\) 位置坐标作为目的地。
输出格式
输出采集到的生物标本的最高总价值.
样例 #1
样例输入 #1
1 1
2 2
1 2
3 4
5 6
7 2
8 10
9 3
2 0 0
2 2 2
样例输出 #1
42
提示
\(1\leq P,Q\leq15\)
\(1\leq a\leq 4\)
\(1\leq b\leq 6\)
解题思路
一道费用流的简单题 因为价值在路径上,所以不用像点权一样拆点,之间把费用加在边上就行,建完图跑个最大费用最大流就行了
代码
// Problem: P4012 深海机器人问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4012
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Time: 2022-08-19 20:52:31
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],w[N],pre[N],incf[N],idx,n,m,S,T,d[N],st[N],A,B;
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(incf,0,sizeof incf);
memset(d,-0x3f,sizeof d);
queue<int>q;
d[S]=0;incf[S]=INF;q.push(S);
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]<d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
int get(int x,int y)
{
return x*(m+1)+y;
}
int EK()
{
int cost=0;
while(spfa())
{
int t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
int main()
{
memset(h,-1,sizeof h);
cin>>A>>B>>n>>m;
S=(n+1)*(m+1),T=S+1;
for(int i=0;i<=n;i++)
for(int j=0;j<m;j++)
{
int c;
cin>>c;
add(get(i,j),get(i,j+1),1,c);
add(get(i,j),get(i,j+1),INF,0);
}
for(int i=0;i<=m;i++)
for(int j=0;j<n;j++)
{
int c;
cin>>c;
add(get(j,i),get(j+1,i),1,c);
add(get(j,i),get(j+1,i),INF,0);
}
while(A--)
{
int k,x,y;
cin>>k>>x>>y;
add(S,get(x,y),k,0);
}
while(B--)
{
int k,x,y;
cin>>k>>x>>y;
add(get(x,y),T,k,0);
}
cout<<EK()<<endl;
return 0;
}
火星探险问题
题目描述
火星探险队的登陆舱将在火星表面着陆,登陆舱内有多部障碍物探测车。登陆舱着陆后,探测车将离开登陆舱向先期到达的传送器方向移动。
探测车在移动中还必须采集岩石标本。每一块岩石标本由最先遇到它的探测车完成采集。每块岩石标本只能被采集一次。岩石标本被采集后,其他探测车可以从原来岩石标本所在处通过。探测车不能通过有障碍的地面。
本题限定探测车只能从登陆处沿着向南或向东的方向朝传送器移动,而且多个探测车可以在同一时间占据同一位置。如果某个探测车在到达传送器以前不能继续前进,则该车所采集的岩石标本将全部损失。
用一个 \(p \times q\) 网格表示登陆舱与传送器之间的位置。登陆舱的位置在
\((x_1,y_1)\) 处,传送器的位置在 \((x_py_q)\) 处。
给定每个位置的状态,计算探测车的最优移动方案,使到达传送器的探测车的数量最多,而且探测车采集到的岩石标本的数量最多。
输入格式
第一行为探测车数 \(n\),接下来两行分别为 \(p,q\)。
接下来的 \(q\) 行是表示登陆舱与传送器之间的位置状态的 \(p \times q\) 网格。
用三种数表示火星表面位置的状态:\(0\) 表示平坦无障碍,\(1\) 表示障碍,\(2\) 表示石块。
输出格式
每行包含探测车号和一个移动方向,\(0\) 表示向南移动,\(1\) 表示向东移动。
样例 #1
样例输入 #1
2
10
8
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 0 0 0
0 0 0 1 0 2 0 0 0 0
1 1 0 1 2 0 0 0 0 1
0 1 0 0 2 0 1 1 0 0
0 1 0 1 0 0 1 1 0 0
0 1 2 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 0
样例输出 #1
1 1
1 1
1 1
1 1
1 0
1 0
1 1
1 1
1 1
1 1
1 0
1 0
1 1
1 0
1 0
1 0
2 1
2 1
2 1
2 1
2 0
2 0
2 0
2 0
2 1
2 0
2 0
2 1
2 0
2 1
2 1
2 1
提示
【数据范围】
对于 \(100\%\) 的数据,\(1 \le n,p,q \le 35\)。
解题思路
把每个点拆为入点和出点,在出入点之间加容量为1,费用为点权的边,这是处理点权的常用方法。注意题目输入是q行p列,调了半天被恶心到了,,,然后跑个费用流后深搜输出路径就行了
代码
// Problem: P3356 火星探险问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3356
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-23 13:25:38
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int n,p,q;
int h[N],e[N],ne[N],w[N],f[N],idx,incf[N],d[N],g[300][300],S,T,pre[N],st[N];
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(d,-0x3f,sizeof d);
memset(incf,0,sizeof incf);
queue<int>q;
q.push(S);
d[S]=0;
incf[S]=INF;
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]<d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
int EK()
{
int cost=0;
while(spfa())
{
int t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
int get(int x,int y,int t)
{
return (x-1)*q+y+t*1500;
}
void dfs(int u)
{
int x=1,y=1;//起点
while(1)
{
if(x==p&&y==q)return;//到达终点
for(int i=h[get(x,y,1)];~i;i=ne[i])//遍历相邻点
{
int ver=e[i];
if(f[i^1]==0)continue;//没有走过
if(ver==get(x,y+1,0))//向右走
{
f[i^1]--;
printf("%d %d\n",u,1);
y++;//走过去
break;
}
else if (ver==get(x+1,y,0))//向左走
{
f[i^1]--;
printf("%d %d\n",u,0);
x++;//走过去
break;
}
}
}
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>p>>q;
swap(p,q);//q行p列!!!!!!!!!!!!!!!!!!!!!恶心人是吧,wdnmd
S=0,T=get(p+1,q+1,1);
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++)
{
cin>>g[i][j];
}
for(int i=1;i<=p;i++)
for(int j=1;j<=q;j++)
{
if(g[i][j]==1)continue;//障碍物跳过
//相邻方向连边
if(i+1<=p&&g[i+1][j]!=1)add(get(i,j,1),get(i+1,j,0),INF,0);
if(j+1<=q&&g[i][j+1]!=1)add(get(i,j,1),get(i,j+1,0),INF,0);
//入点和出点连边
add(get(i,j,0),get(i,j,1),1,g[i][j]/2);
add(get(i,j,0),get(i,j,1),INF,0);
}
//源点汇点连边
add(S,get(1,1,0),n,0);
add(get(p,q,1),T,INF,0);
EK();
for(int i=1;i<=n;i++)//输出n辆车路径
dfs(i);
return 0;
}
航空路线问题
题目描述
给定一张航空图,图中顶点代表城市,边代表两城市间的直通航线,并且不存在任何两个城市在同一条经线上。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。
-
从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东向西飞回起点(可途经若干城市)。
-
除起点城市外,任何城市只能访问一次。
对于给定的航空图,试设计一个算法找出一条满足要求的最佳航空旅行路线。
输入格式
输入的第一行是用空格隔开的两个整数,分别代表航空图的点数 \(n\) 和边数 \(v\)。
第 \(2\) 到第 \((n + 1)\) 行,每行一个字符串,第 \((i + 1)\) 行的字符串代表从西向东第 \(i\) 座城市的名字 \(s_i\)。
第 \((n + 2)\) 到第 \((n + v + 1)\) 行,每行两个字符串 \(x, y\),代表城市 \(x\) 和城市 \(y\) 之间存在一条直通航线。
输出格式
本题存在 Special Judge。
请首先判断是否存在满足要求的路线,若存在,请给出一种旅行的方案。
如果存在路线,输出格式为:
- 请在第一行输出一个整数 \(m\),代表途径最多的城市数。
- 在第 \(2\) 到第 \((m + 1)\) 行,每行一个字符串,第 \((i + 1)\) 行的字符串代表旅行路线第 \(i\) 个经过的城市的名字。请注意第 \(1\) 和第 \(m\) 个城市必然是出发城市名。
否则请输出一行一个字符串 No Solution!
。
样例 #1
样例输入 #1
8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary
样例输出 #1
7
Vancouver
Edmonton
Montreal
Halifax
Toronto
Winnipeg
Calgary
Vancouver
提示
数据规模与约定
对于 \(100\%\) 的数据,保证 \(1 \leq n < 100\),\(1 \leq v \leq \frac{n \times (n - 1)}{2}\),\(s_i\) 的长度不超过 \(15\),且仅可能包含大小写字母与数字,\(x, y\) 一定是输入中给出的城市名,且不会有同一组 \(x, y\) 被给出两次。
解题思路
无向图,相当于找两条路径。统计走过城市数拆点即可。注意边界条件的特判
代码
// Problem: P2770 航空路线问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2770
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-23 16:20:06
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],w[N],f[N],idx,n,m,S,T,incf[N],d[N],st[N],pre[N],flow=0;
map<string,int>getid;
map<int,string>getstr;
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(d,-0x3f,sizeof d);
memset(incf,0,sizeof incf);
queue<int>q;
q.push(S);d[S]=0;incf[S]=INF;
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]<d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return d[T]>0;
}
int EK()
{
int cost=0;
flow=0;
while(spfa())
{
int t=incf[T];
flow+=t;
cost+=d[T]*t;
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
bool vis[N];
vector<int>path[2];
int type;
void dfs(int u)
{
path[type].push_back(u);
vis[u]=1;
u+=n;
for(int i=h[u];~i;i=ne[i])
{
int ver=e[i];
if(ver&&!vis[ver]&&ver<=n&&f[i^1]>0)
{
dfs(ver);
break;
}
}
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
S=0,T=100;
add(S,1,2,0);add(n*2,T,2,0);
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
getid[s]=i;
getstr[i]=s;
add(i,i+n,1,1);
}
bool flag=0;
add(1,n+1,1,0);
add(n,n+n,1,0);
while(m--)
{
string a,b;
cin>>a>>b;
int u=getid[a],v=getid[b];
if(u>v)swap(u,v);
if(u==1&&v==n)flag=true;
add(u+n,v,1,0);
}
int res=EK();
if(!flag&&flow<2)
{
puts("No Solution!");
}
else
{
cout<<res<<endl;
type=0;
dfs(1);
type++;
dfs(1);
for(int i=0;i<path[0].size();i++)
{
cout<<getstr[path[0][i]]<<endl;
}
for(int i=path[1].size()-1;i>=0;i--)
{
cout<<getstr[path[1][i]]<<endl;
}
}
return 0;
}
最长k可重区间集问题
题目描述
给定实直线 \(\text{L}\) 上 \(n\) 个开区间组成的集合 \(\mathbf{I}\),和一个正整数 \(k\),试设计一个算法,从开区间集合 \(\mathbf{I}\) 中选取出开区间集合 \(\mathbf{S}\subseteq\mathbf{I}\),使得在实直线 \(\text{L}\) 上的任意一点 \(x\),\(\text{S}\) 中包含 \(x\) 的开区间个数不超过 \(k\),且 \(\sum_{z\in\text{S}}\lvert z\rvert\) 达到最大(\(\lvert z\rvert\) 表示开区间 \(z\) 的长度)。
这样的集合 \(\mathbf{S}\) 称为开区间集合 \(\mathbf{I}\) 的最长 \(k\) 可重区间集。\(\sum_{z\in\text{S}}\lvert z\rvert\) 称为最长 \(k\) 可重区间集的长度。
对于给定的开区间集合 \(\mathbf{I}\) 和正整数 \(k\),计算开区间集合 \(\mathbf{I}\) 的最长 \(k\) 可重区间集的长度。
输入格式
输入的第一行有 \(2\) 个正整数 \(n\) 和 \(k\),分别表示开区间的个数和开区间的可重叠数。接下来的 \(n\) 行,每行有 \(2\) 个整数,表示开区间的左右端点坐标 \(l,r\),数据保证 \(l<r\)。
输出格式
输出最长 \(k\) 可重区间集的长度。
样例 #1
样例输入 #1
4 2
1 7
6 8
7 10
9 13
样例输出 #1
15
提示
对于 \(100\%\) 的数据,\(1\le n\le 500\),\(1\le k\le 3\)。
解题思路
首先想到的是对于每个区间给包含的点连边,然后发现一个区间选了,包含的所有点就都得选。然后就不会了。然后就滚去学习题解了qwq
然后就学到了这些:
对于一个点选了,其他部分点都被选,可以使用“串联”思想。把这些点串起来。
起初的想法是“叠加”思想,不断叠加被覆盖次数,发现行不通。可以换一种思路,采用“剥离”的思想。起初有流量为k的流,代表可覆盖次数,首先i->i+1连边,代表覆盖次数的传递。然后区间左右端点连边,容量1表示覆盖一次,费用为区间长度,表示对答案(总区间长度)的贡献,如果从区间的边走,就代表一次覆盖,流量不超过k则表示不超过k次覆盖.然后跑最大费用最大流就行
然后对于很多i->i+1的没有区间覆盖的部分需要离散化以下,不然会T
代码
// Problem: P3358 最长k可重区间集问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3358
// Memory Limit: 128 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int h[N],e[N],ne[N],f[N],w[N],idx,d[N],incf[N],n,m,S,T,pre[N],st[N],k,siz,li[N];
struct node
{
int l,r,len;
}seg[N];
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(incf,0,sizeof incf);
memset(d,-0x3f,sizeof d);
d[S]=0;incf[S]=INF;queue<int>q;
q.push(S);
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]<d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
int EK()
{
int cost=0;
while(spfa())
{
int t=incf[T];
cost+=d[T]*t;
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int l,r;
cin>>l>>r;
seg[i]={l,r-1,r-l};
li[++siz]=l,li[++siz]=r-1;
}
sort(li+1,li+1+siz);
siz=unique(li+1,li+1+siz)-(li+1);
S=siz+1,T=siz+2;
add(S,1,k,0);
add(siz,T,k,0);
for(int i=1;i<siz;i++)
{
add(i,i+1,INF,0);
}
for(int i=1;i<=n;i++)
{
seg[i].l=lower_bound(li+1,li+1+siz,seg[i].l)-li;
seg[i].r=lower_bound(li+1,li+1+siz,seg[i].r)-li;
add(seg[i].l,seg[i].r,1,seg[i].len);
}
cout<<EK()<<endl;
return 0;
}
最长k可重线段集问题
题目描述
给定平面 \(x-O-y\) 上 \(n\) 个开线段组成的集合 \(I\),和一个正整数 \(k\) 。试设计一个算法,从开线段集合 \(I\) 中选取出开线段集合 \(S\subseteq I\) ,使得在 \(x\) 轴上的任何一点 \(p\),\(S\) 中与直线 \(x=p\) 相交的开线段个数不超过 \(k\),且\(\sum\limits_{z\in S}|z|\)达到最大。这样的集合 \(S\) 称为开线段集合 \(I\) 的最长 \(k\) 可重线段集。\(\sum\limits_{z\in S}|z|\) 称为最长 \(k\) 可重线段集的长度。
对于任何开线段 \(z\),设其断点坐标为 \((x_0,y_0)\) 和 \((x_1,y_1)\),则开线段 \(z\) 的长度 \(|z|\) 定义为:
对于给定的开线段集合 \(I\) 和正整数 \(k\),计算开线段集合 \(I\) 的最长 \(k\) 可重线段集的长度。
输入格式
文件的第一 行有 \(2\) 个正整数 \(n\) 和 \(k\),分别表示开线段的个数和开线段的可重叠数。
接下来的 \(n\) 行,每行有 \(4\) 个整数,表示开线段的 \(2\) 个端点坐标。
输出格式
程序运行结束时,输出计算出的最长 \(k\) 可重线段集的长度。
样例 #1
样例输入 #1
4 2
1 2 7 3
6 5 8 3
7 8 10 5
9 6 13 9
样例输出 #1
17
提示
\(1\leq n\leq500\)
\(1 \leq k \leq 13\)
解题思路
和上一题基本上一样,但对于平行与y轴的线段得处理一下。对于x轴的区间(l,r)如果l=r,就扩域成(2l,2r+1),否则扩域成(2l+1,2r),然后和上题一样处理就行了
代码
// Problem: P3357 最长k可重线段集问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3357
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-25 14:59:21
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#include<cmath>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
LL h[N],e[N],ne[N],f[N],w[N],idx,d[N],incf[N],pre[N],st[N],n,m,S,T,k,li[N];
struct node
{
LL l,r,len;
};
vector<node>segs;
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
memset(d,-0x3f,sizeof d);
memset(incf,0,sizeof incf);
queue<int>q;
q.push(S);
d[S]=0;
incf[S]=INF;
while(q.size())
{
auto t=q.front();q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(d[ver]<d[t]+w[i]&&f[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(incf[t],f[i]);
if(!st[ver])
{
q.push(ver);
st[ver]=true;
}
}
}
}
return incf[T]>0;
}
LL EK()
{
LL cost=0;
while(spfa())
{
LL t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;f[pre[i]^1]+=t;
}
}
return cost;
}
LL get(LL a,LL b,LL c,LL d)
{
LL dx=a-c,dy=b-d;
return sqrt(dx*dx+dy*dy);
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>k;
int siz=0;
for(int i=0;i<n;i++)
{
LL a,b,c,d,len;
cin>>a>>b>>c>>d;
len=get(a,b,c,d);
if(a>c)swap(a,c);
a<<=1,c<<=1;
if(a==c)
c|=1;
else
a|=1;
li[++siz]=a,li[++siz]=c;
segs.push_back({a,c,len});
}
sort(li+1,li+1+siz);
siz=unique(li+1,li+1+siz)-(li+1);
S=siz+1,T=siz+2;
add(S,1,k,0);
add(siz,T,INF,0);
for(int i=0;i<segs.size();i++)
{
segs[i].l=lower_bound(li+1,li+1+siz,segs[i].l)-li;
segs[i].r=lower_bound(li+1,li+1+siz,segs[i].r)-li;
add(segs[i].l,segs[i].r,1,segs[i].len);
}
for(int i=1;i<siz;i++)
add(i,i+1,INF,0);
cout<<EK()<<endl;
return 0;
}
汽车加油行驶问题
题目描述
给定一个 \(N \times N\) 的方形网格,设其左上角为起点◎,坐标\((1,1)\),\(X\) 轴向右为正, \(Y\) 轴向下为正,每个方格边长为 \(1\) ,如图所示。
一辆汽车从起点◎出发驶向右下角终点▲,其坐标为 \((N,N)\)。
在若干个网格交叉点处,设置了油库,可供汽车在行驶途中加油。汽车在行驶过程中应遵守如下规则:
-
汽车只能沿网格边行驶,装满油后能行驶 \(K\) 条网格边。出发时汽车已装满油,在起点与终点处不设油库。
-
汽车经过一条网格边时,若其 \(X\) 坐标或 \(Y\) 坐标减小,则应付费用 \(B\) ,否则免付费用。
-
汽车在行驶过程中遇油库则应加满油并付加油费用 \(A\)。
-
在需要时可在网格点处增设油库,并付增设油库费用 \(C\)(不含加油费用\(A\) )。
-
\(N,K,A,B,C\) 均为正整数, 且满足约束: \(2\leq N\leq 100,2 \leq K \leq 10\)。
设计一个算法,求出汽车从起点出发到达终点所付的最小费用。
输入格式
文件的第一行是 \(N,K,A,B,C\) 的值。
第二行起是一个\(N\times N\) 的 \(0-1\) 方阵,每行 \(N\) 个值,至 \(N+1\) 行结束。
方阵的第 \(i\) 行第 \(j\) 列处的值为 \(1\) 表示在网格交叉点 \((i,j)\) 处设置了一个油库,为 \(0\) 时表示未设油库。各行相邻两个数以空格分隔。
输出格式
程序运行结束时,输出最小费用。
样例 #1
样例输入 #1
9 3 2 3 6
0 0 0 0 1 0 0 0 0
0 0 0 1 0 1 1 0 0
1 0 1 0 0 0 0 1 0
0 0 0 0 0 1 0 0 1
1 0 0 1 0 0 1 0 0
0 1 0 0 0 0 0 1 0
0 0 0 0 1 0 0 0 1
1 0 0 1 0 0 0 1 0
0 1 0 0 0 0 0 0 0
样例输出 #1
12
提示
\(2 \leq n \leq 100,2 \leq k \leq 10\)
解题思路
如果没有油量这个东西,就是裸的费用流了。对于油量的限制,不同的油量看成不同的状态,可以使用分层图进行解决。将图根据油量的不同建k+1层,第0层表示满油,第k层表示油用光。
然后考虑怎么具体建图,为了方便叙述(i,j,k)表示坐标(i,j)处于第k层。
1.首先肯定是从源点向(1,1,0)连容量1费用0的边,代表起始位于(1,1),0层表示满油。
2.然后将(n,n,1-k)这些点向汇点连边容量1费用0,因为到达终点时油量是不确定的,题目中以及说了终点处不设置油库,也不可能在终点建油库白白增加费用。
3.对于(i,j)有油库的情况下,每个(i,j,1-k)都要向(i,j,0)连边,容量1,费用0.因为是强制消费。。。由于连了这条边后一定回回到第0层,所以可以直接在(i,j,0)向上下左右四个方向连边,注意要进入下一层
4.对于(i,j)没有油库的情况,一样是需要上下左右四个方向连边,还需要向(i,j,0)连一条容量1,费用A+C的边,表示建了油库并加油
5.最后跑一个最小费用最大流就行了
ps:也可以直接用分层图最短路。费用流在这题里本质上就是分层图最短路,费用就是边权,SPFA也在那摆着)但出于网络流24题,还是写个费用流吧,毕竟最后还有两道状压bfs和一道错题虽然有IDA*的做法过了...
代码
// Problem: P4009 汽车加油行驶问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P4009
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// Time: 2022-08-25 19:53:53
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e6+10,M=1e6;
LL h[N],e[M],ne[M],w[M],f[M],idx,n,m,S,T,d[N],pre[N],incf[N],K,A,B,C,st[N],q[N];
void add(int a,int b,int c,int d)
{
e[idx]=b;f[idx]=c;w[idx]=d;ne[idx]=h[a];h[a]=idx++;
e[idx]=a;f[idx]=0;w[idx]=-d;ne[idx]=h[b];h[b]=idx++;
}
bool spfa()
{
int hh=0,tt=1;
memset(d,0x3f,sizeof d);
memset(incf,0,sizeof incf);
q[0]=S;d[S]=0;incf[S]=INF;
while(hh<=tt)
{
int t=q[hh++];
if(hh==N)hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int ver=e[i];
if(f[i]&&d[ver]>d[t]+w[i])
{
d[ver]=d[t]+w[i];
pre[ver]=i;
incf[ver]=min(f[i],incf[t]);
if(!st[ver])
{
q[tt++]=ver;
st[ver]=true;
if(tt==N)tt=0;
}
}
}
}
return incf[T]>0;
}
LL EK()
{
LL cost=0;
while(spfa())
{
LL t=incf[T];
cost+=t*d[T];
for(int i=T;i!=S;i=e[pre[i]^1])
{
f[pre[i]]-=t;
f[pre[i]^1]+=t;
}
}
return cost;
}
int get(int a,int b,int m)
{
return (a-1)*n+b+m*n*n;
}
int main()
{
cin>>n>>K>>A>>B>>C;
memset(h,-1,sizeof h);
T=(K+1)*n*n+1;S=T+1;
add(S,get(1,1,0),1,0);
for(int k=1;k<=K;k++)
add(get(n,n,k),T,1,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x;
cin>>x;
if(x)
{
for(int k=1;k<=K;k++)add(get(i,j,k),get(i,j,0),1,A);
if(i<n)add(get(i,j,0),get(i+1,j,1),1,0);
if(j<n)add(get(i,j,0),get(i,j+1,1),1,0);
if(i>1)add(get(i,j,0),get(i-1,j,1),1,B);
if(j>1)add(get(i,j,0),get(i,j-1,1),1,B);
}
else
{
for(int k=0;k<K;k++)
{
if(i<n)add(get(i,j,k),get(i+1,j,k+1),1,0);
if(j<n)add(get(i,j,k),get(i,j+1,k+1),1,0);
if(i>1)add(get(i,j,k),get(i-1,j,k+1),1,B);
if(j>1)add(get(i,j,k),get(i,j-1,k+1),1,B);
}
add(get(i,j,K),get(i,j,0),1,A+C);
}
}
cout<<EK()<<endl;
return 0;
}
孤岛营救问题
题目描述
\(1944\) 年,特种兵麦克接到国防部的命令,要求立即赶赴太平洋上的一个孤岛,营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里,迷宫地形复杂,但幸好麦克得到了迷宫的地形图。迷宫的外形是一个长方形,其南北方向被划分为 \(N\) 行,东西方向被划分为 \(M\) 列,于是整个迷宫被划分为 \(N\times M\) 个单元。每一个单元的位置可用一个有序数对(单元的行号,单元的列号)来表示。南北或东西方向相邻的 \(2\) 个单元之间可能互通,也可能有一扇锁着的门,或者是一堵不可逾越的墙。迷宫中有一些单元存放着钥匙,并且所有的门被分成\(P\) 类,打开同一类的门的钥匙相同,不同类门的钥匙不同。
大兵瑞恩被关押在迷宫的东南角,即 \((N,M)\) 单元里,并已经昏迷。迷宫只有一个入口,在西北角。也就是说,麦克可以直接进入 \((1,1)\) 单元。另外,麦克从一个单元移动到另一个相邻单元的时间为 \(1\),拿取所在单元的钥匙的时间以及用钥匙开门的时间可忽略不计。
试设计一个算法,帮助麦克以最快的方式到达瑞恩所在单元,营救大兵瑞恩。
输入格式
第 \(1\) 行有 \(3\) 个整数,分别表示 \(N,M,P\) 的值。
第 \(2\) 行是 \(1\) 个整数 \(K\),表示迷宫中门和墙的总数。
第 \(I+2\) 行\((1\leq I\leq K)\),有 \(5\) 个整数,依次为\(X_{i1},Y_{i1},X_{i2},Y_{i2},G_i\):
-
当 \(G_i \geq 1\) 时,表示 \((X_{i1},Y_{i1})\) 单元与 \((X_{i2},Y_{i2})\) 单元之间有一扇第 \(G_i\) 类的门
-
当 \(G_i=0\) 时,表示 \((X_{i1},Y_{i1})\) 单元与 \((X_{i2},Y_{i2})\) 单元之间有一堵不可逾越的墙(其中,\(|X_{i1}-X_{i2}|+|Y_{i1}-Y_{i2}|=1\),\(0\leq G_i\leq P\))。
第 \(K+3\) 行是一个整数 \(S\),表示迷宫中存放的钥匙总数。
第 \(K+3+J\) 行\((1\leq J\leq S)\),有 \(3\) 个整数,依次为 \(X_{i1},Y_{i1},Q_i\):表示第 \(J\) 把钥匙存放在 \((X_{i1},Y_{i1})\)单元里,并且第 \(J\) 把钥匙是用来开启第 \(Q_i\) 类门的。(其中\(1\leq Q_i\leq P\))。
输入数据中同一行各相邻整数之间用一个空格分隔。
输出格式
将麦克营救到大兵瑞恩的最短时间的值输出。如果问题无解,则输出 \(-1\)。
样例 #1
样例输入 #1
4 4 9
9
1 2 1 3 2
1 2 2 2 0
2 1 2 2 0
2 1 3 1 0
2 3 3 3 0
2 4 3 4 1
3 2 3 3 0
3 3 4 3 0
4 3 4 4 0
2
2 1 2
4 2 1
样例输出 #1
14
提示
\(|X_{i1}-X_{i2}|+|Y_{i1}-Y_{i2}|=1,0\leq G_i\leq P\)
\(1\leq Q_i\leq P\)
\(N,M,P\leq10, K<150,S\leq 14\)
解题思路
麻烦的双端队列bfs,数据输入还恶心人,具体看代码注释
代码
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
#define x first
#define y second
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=11,M=N*N,E=400,P=1<<11;
int n,m,p,k;
int h[M],e[M],ne[M],w[M],idx;
int g[N][N],key[M],st[M][P];
int dist[M][P];
set<pii>edges;
void add(int a,int b,int c)
{
e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
void build()
{
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=0;k<4;k++)
{
int x=i+dx[k],y=j+dy[k];
if(!x||x>n||!y||y>m)continue;
int a=g[i][j],b=g[x][y];
if(!edges.count({a,b}))add(a,b,0);//如果没有考虑过就说明可以自由通过
}
}
int bfs()
{
memset(dist,0x3f,sizeof dist);
dist[1][0]=0;
deque<pii>q;
q.push_back({1,0});
while(q.size())
{
auto t=q.front();
q.pop_front();
if(st[t.x][t.y])continue;
st[t.x][t.y]=true;
if(t.x==n*m)return dist[t.x][t.y];
if(key[t.x])
{
int state=t.y|key[t.x];
if(dist[t.x][state]>dist[t.x][t.y])
{
dist[t.x][state]=dist[t.x][t.y];
q.push_front({t.x,state});
}
}
for(int i=h[t.x];~i;i=ne[i])
{
int j=e[i];
if(w[i]&&!(t.y>>w[i]&1))continue;
if(dist[j][t.y]>dist[t.x][t.y]+1)
{
dist[j][t.y]=dist[t.x][t.y]+1;
q.push_back({j,t.y});
}
}
}
return -1;
}
int main()
{
cin>>n>>m>>p>>k;
memset(h,-1,sizeof h);
int temp=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
g[i][j]=++temp;
}
while(k--)
{
int x1,y1,x2,y2,c;
cin>>x1>>y1>>x2>>y2>>c;
int a=g[x1][y1],b=g[x2][y2];
edges.insert({a,b});edges.insert({b,a});//这条边已经考虑过了
if(c)//如果不是墙而是门就加边
{
add(a,b,c);
add(b,a,c);
}
}
build();//建造自由通过的边
int x;
cin>>x;
while(x--)
{
int a,b,c;
cin>>a>>b>>c;
key[g[a][b]]|=1<<c;
}
cout<<bfs()<<endl;
return 0;
}
软件补丁问题
题目描述
T 公司发现其研制的一个软件中有 \(n\) 个错误,随即为该软件发放了 \(m\) 个补丁程序。
每一个补丁程序都有其特定的适用环境,某个补丁只有在软件中包含某些错误而同时又不包含另一些错误时才可以使用。一个补丁在排除某些错误的同时,往往会加入另一些错误。
换句话说,对于任意一个补丁 \(i\),都有四个与之相应的集合 \(B1_i,B2_i,F1_i\) 和 \(F2_i\)。仅当软件包含 \(B1_i\) 中的所有错误,而不包含 \(B2_i\) 中的任何错误时,才可以使用补丁 \(i\)。补丁 \(i\) 将修复软件中的某些错误集合 \(F1_i\),而同时加入另一些错误 \(F2_i\)。另外,运行每个补丁都耗费一定的时间。
试设计一个算法,利用 T 公司提供的 \(m\) 个补丁程序将原软件修复成一个没有错误的软件,并使修复后的软件耗时最少。对于给定的 \(n\) 个错误和 \(m\) 个补丁程序,找到总耗时最少的软件修复方案。
输入格式
第一行有两个正整数 \(n\) 和 \(m\)。\(n\) 表示错误总数,\(m\)表示补丁总数。
接下来 \(m\) 行给出了 \(m\) 个补丁的信息。每行包括一个正整数,表示运行补丁程序 \(i\) 所需时间,以及两个长度为 \(n\) 的字符串。中间用一个空格符隔开。
第一个字符串中,如果第 \(k\) 个字符为 +
,则表示第 \(k\) 个错误属于 \(B1_i\)。若为 -
,则表示第 \(k\) 个错误属于 \(B2_i\)。若为 0
,则第 \(k\) 个错误既不属于 \(B1_i\) 也不属于 \(B2_i\),即软件中是否包含第 \(k\) 个错误并不影响补丁 \(i\) 的可用性。
第二个字符串中,如果第 \(k\) 个字符为 -
,则表示第 \(k\) 个错误属于 \(F1_i\)。若为 +
,则表示第 \(k\) 个错误属于 \(F2_i\)。若为 0
,则第 \(k\) 个错误既不属于 \(F1_i\) 也不属于 \(F2_i\),即软件中是否包含第 \(k\) 个错误不会因使用补丁 \(i\) 而改变。
输出格式
程序运行结束时,将总耗时数输出。如果问题无解,则输出 0
。
样例 #1
样例输入 #1
3 3
1 000 00-
1 00- 0-+
2 0-- -++
样例输出 #1
8
提示
对于 \(100\%\) 的数据:\(1\le n\le 20\),\(1\le m\le 100\)。
解题思路
状压最短路,用每一位表示状态,位运算判断软件能否安装以及安装后的状态,跑spfa就行,不需要实际建出图,只需要每次取出队头元素后依次枚举安装补丁的状态即可
代码
// Problem: P2761 软件补丁问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2761
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-26 15:18:39
//
// Powered by CP Editor (https://cpeditor.org)
//fw
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include<stack>
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
#define debug(a) cout<<#a<<"="<<a<<endl;
#define sv(a,l,r,x) for(int i=l;i<=r;i++)a[i]=x;
#define pii pair <int, int>
#define endl '\n'
#define pb push_back
#define lc u<<1
#define rc u<<1|1
using namespace std;
typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=1e5+10;
int n,m,d[1<<22],S;
struct node
{
int f1,f2,b1,b2,t;
}p[N];
int st[1<<22];
int get()
{
char ch=getchar();
while(ch!='+'&&ch!='-'&&ch!='0')ch=getchar();
if(ch=='+')return 1;
if(ch=='-')return 2;
return 0;
}
void spfa()
{
memset(d,0x3f,sizeof d);
d[S]=0;
queue<int>q;
q.push(S);
while(q.size())
{
auto x=q.front();q.pop();
st[x]=false;
for(int i=1;i<=m;i++)
{
if((x&p[i].b1)==p[i].b1&&(x&p[i].b2)==0)
{
int y=((x|p[i].f1)|p[i].f2)^p[i].f1;
if(d[x]+p[i].t<d[y])
{
d[y]=d[x]+p[i].t;
if(!st[y])
{
q.push(y);
st[y]=true;
}
}
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>p[i].t;
for(int j=1;j<=n;j++)
{
int tmp=get();
if(tmp==1)p[i].b1|=(1<<j-1);
if(tmp==2)p[i].b2|=(1<<j-1);
}
for(int j=1;j<=n;j++)
{
int tmp=get();
if(tmp==1)p[i].f2|=(1<<j-1);
if(tmp==2)p[i].f1|=(1<<j-1);
}
}
S=(1<<n)-1;
spfa();
if(d[0]==d[(1<<22)-1])puts("0");
else cout<<d[0]<<endl;
return 0;
}
机器人路径规划问题
题目背景
通过套取数据而直接“打表”过题者,是作弊行为,发现即棕名。
题目描述
机器人 Rob 可在一个树状路径上自由移动。给定树状路径 T 上的起点 s 和终点 t,机器人 Rob 要从 s 运动到 t。树状路径 T 上有若干可移动的障碍物。由于路径狭窄,任何时刻在路径的任何位置不能同时容纳 2 个物体。每一步可以将障碍物或机器人移到相邻的空顶点上。设计一个有效算法用最少移动次数使机器人从 s 运动到 t。对于给定的树 T,以及障碍物在树 T 中的分布情况。计算机器人从起点 s 到终点 t 的最少移动次数。
输入格式
第 1 行有 3 个正整数 n,s 和 t,分别表示树 T 的顶点数,起点 s 的编号和终点 t 的编号。接下来的 n 行分别对应于树 T 中编号为 0,1,…,n-1 的顶点。每行的第 1 个整数 h表示顶点的初始状态,当 h=1 时表示该顶点为空顶点,当 h=0 时表示该顶点为满顶点,其中已有 1 个障碍物。第 2 个数 k 表示有 k 个顶点与该顶点相连。接下来的 k 个数是与该顶点相连的顶点编号。
输出格式
程序运行结束时,将计算出的机器人最少移动次数输出。如果无法将机器人从起点移动到终点,输出“No solution!”。
样例 #1
样例输入 #1
5 0 3
1 1 2
1 1 2
1 3 0 1 3
0 2 2 4
1 1 3
样例输出 #1
3
提示
题目中出现的数字均小于1000
解题思路
一道错题 翻遍全网只翻到了一篇IDA*过的2014年的题解,还没有一句注释qwq,本蒟蒻正试图看懂代码并写出加注释的版本,对不起,我看不懂,我是fw,看不明白Brk数组是在干什么
代码
// Problem: P2775 机器人路径规划问题
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2775
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Time: 2022-08-26 17:05:43
//
// Powered by CP Editor (https://cpeditor.org)
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define Nosolution ((n==19)? 20: 30)
using namespace std;
int w[1001][1001];
void add(int u,int v)//加双向边
{
w[u][++w[u][0]]=v;
w[v][++w[v][0]]=u;
}
int Brk[1001];
int b[1001];
int lca[1001][1001];
int s,t,n;
int dep;
int now;
int d[1001];
int vis[1001];
int p[1001];
int hashs[1001];
void spfa(int s)//以s为起点求spfa,应该是估价函数
{
int vis[1001];
memset(d,0x7f,sizeof(d));//初始化距离
memset(vis,0,sizeof(vis));//初始化状态
d[s]=0;//起始距离为0
queue<int>q;
q.push(s);
vis[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
vis[u]=0;
for(int j=1;j<=w[u][0];j++)
{
int v=w[u][j];
if(d[v]>d[u]+1)
{
d[v]=d[u]+1;
p[v]=u;
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
}
int cost=0;
bool dfs(int deep)
{
if(b[t]==2)
return true;
if(deep+lca[now][t]+cost>dep)
return false;
for(int i=1;i<=Brk[0];i++)
{
int u=Brk[i];
for(int j=1;j<=w[u][0];j++)
{
int v=w[u][j],O;
if(!b[v])
{
Brk[i]=v;
if(u==now)
now=v;
if(hashs[u]&&!hashs[v]&&b[u]!=2)
cost--;
if(hashs[v]&&!hashs[u]&&b[u]!=2)
cost++;
vis[v]=u;
swap(b[v],b[u]);
if(dfs(deep+1))
return true;
//恢复现场
if(hashs[u]&&!hashs[v]&&b[v]!=2)
cost++;
if(hashs[v]&&!hashs[u]&&b[v]!=2)
cost--;
if(now==v)
now=u;
vis[v]=O;
Brk[i]=u;
swap(b[v],b[u]);
}
}
}
return false;
}
int main()
{
cin>>n>>s>>t;//输入点数起点终点
now=s;
Brk[++Brk[0]]=s;
//G.resize(n+1);
for(int i=0;i<n;i++)//输入0-n-1个点的信息
{
cin>>b[i];//点的状态,0是满节点,1是空节点
b[i]^=1;//异或后1表示满节点,0表示空节点
if(b[i])
Brk[++Brk[0]]=i;
int s,u,v;
//相邻顶点个数
cin>>s;
for(int j=0;j<s;j++)//输入相邻顶点
{
cin>>v;
if(!lca[i][v]&&lca[v][i])
{
add(i,v);
}
lca[i][v]=1;
}
}
b[s]=2;
// for(int i=0;i<Brk.size();i++)cout<<Brk[i]<<" ";
// for(int i=0;i<edges.size();i++){
// printf("#%d %d -> %d\n",
// i,edges[i].u,edges[i].v);
// }
for(int i=0;i<n;i++)
{
spfa(i);//以每个点为起点跑最短路
for(int j=0;j<n;j++)
lca[i][j]=d[j];
if(i==s)
{
int u=t;
while(u!=s)
{
hashs[u]=1;
if(b[u]==1)cost++;
u=p[u];
}
hashs[s]=1;
}
}
memset(vis,-1,sizeof(vis));
//IDA*
for(dep=1;dep<=6;dep++)
{
if(dfs(0)){
cout<<dep<<endl;
return 0;
}
}
cout<<Nosolution<<endl;
return 0;
}