1.分配问题
题目描述 Description
输入描述 Input Description
输出描述 Output Description
样例输入 Sample Input
样例输出 Sample Output
题解
此题比较简单直接拆点 一连 跑最小费用就好
求最大时可以写个build函数(借鉴hwer写法) 将边权值变为负数 在重跑一边最小费用 输出-min_cost
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define inf 0x7ffffff
using namespace std;
int cnt=1,d[2005],head[2005],flag[2005],from[2005],start=0,end=2001,max_cost=0,mp[1005][1005],n;
struct edge{int to,next,flow,cost;}e[1000001];
void ini(int x,int y,int flow,int cost){e[++cnt].to=y;e[cnt].flow=flow;e[cnt].cost=cost;e[cnt].next=head[x];head[x]=cnt;}
void insert(int x,int y,int flow,int cost){ini(x,y,flow,cost);ini(y,x,0,-cost);}
queue<int>q;
bool spfa(){
memset(d,127,sizeof(d));
d[start]=0;q.push(start);flag[start]=1;
while(!q.empty()){
int k=q.front();q.pop();flag[k]=0;
for(int i=head[k];i;i=e[i].next){
int kk=e[i].to;
if(e[i].flow>0&&d[kk]>d[k]+e[i].cost)
{
d[kk]=d[k]+e[i].cost;from[kk]=i;
if(!flag[kk]){
flag[kk]=1;
q.push(kk);
}
}
}
}
return d[end]<inf;
}
void mcf(){
while(spfa())
{
max_cost+=d[end];
for(int i=from[end];i;i=from[e[i^1].to])
{
e[i].flow-=1;e[i^1].flow+=1;
}
}
}
void build(int k){
cnt=1;memset(head,0,sizeof(head));max_cost=0;
for(int i=1;i<=n;i++)insert(start,i,1,0);
for(int i=1;i<=n;i++)insert(i+n,end,1,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
insert(i,j+n,1,k*mp[i][j]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&mp[i][j]);
build(1);
mcf();
printf("%d\n",max_cost);
build(-1);
mcf();
printf("%d\n",-max_cost);
return 0;
}
2.负载平衡问题
题目描述 Description
输入描述 Input Description
输出描述 Output Description
样例输入 Sample Input
样例输出 Sample Output
题解
跑的时候从左到右跑 所以多货的往少货的运以下是网上的解释
首先求出所有仓库存货量平均值,设第i个仓库的盈余量为A[i],A[i] = 第i个仓库原有存货量 - 平均存货量。建立二分图,把每个仓库抽象为两个节点Xi和Yi。增设附加源S汇T。
1、如果A[i]>0,从S向Xi连一条容量为A[i],费用为0的有向边。
2、如果A[i]<0,从Yi向T连一条容量为-A[i],费用为0的有向边。
3、每个Xi向两个相邻顶点j,从Xi到Xj连接一条容量为无穷大,费用为1的有向边,从Xi到Yj连接一条容量为无穷大,费用为1的有向边。
求最小费用最大流,最小费用流值就是最少搬运量。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7ffffff
using namespace std;
struct edge{int to,next,flow,cost;}e[1000001];
int d[2005],head[2005],flag[2005],from[2005],cnt=1;int start=0,end=2001,min_cost=0,n,a[2005];
void ini(int x,int y,int flow,int cost){e[++cnt].to=y;e[cnt].flow=flow;e[cnt].cost=cost;e[cnt].next=head[x];head[x]=cnt;}
void insert(int x,int y,int flow,int cost){ini(x,y,flow,cost);ini(y,x,0,-cost);}
queue<int>q;
bool spfa(){
memset(d,127,sizeof(d));
d[start]=0;q.push(start);flag[start]=1;
while(!q.empty()){
int k=q.front();q.pop();flag[k]=0;
for(int i=head[k];i;i=e[i].next){
int kk=e[i].to;
if(e[i].flow>0&&d[kk]>d[k]+e[i].cost)
{
d[kk]=d[k]+e[i].cost;from[kk]=i;
if(!flag[kk]){
flag[kk]=1;
q.push(kk);
}
}
}
}
return d[end]<inf;
}
void mcf(){
while(spfa()){
int mn=inf;
for(int i=from[end];i;i=from[e[i^1].to])
mn=min(mn,e[i].flow);
min_cost+=d[end]*mn;
for(int i=from[end];i;i=from[e[i^1].to])
{
e[i].flow-=mn;e[i^1].flow+=mn;
}
}
}
int main(){
scanf("%d",&n);
int tot=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
tot+=a[i];
}
tot/=n;
for(int i=1;i<=n;i++)
a[i]-=tot;
for(int i=1;i<=n;i++){
if(a[i]>0) insert(start,i,a[i],0);
else if(a[i]<0) insert(i+n,end,-a[i],0);
int j=(i==1?n:i-1);
insert(i,j,inf,1);insert(i,j+n,inf,1);
j=(i==n?1:i+1);
insert(i,j,inf,1);insert(i,j+n,inf,1);
}
mcf();
printf("%d",min_cost);
return 0;
}
3.飞行员配对问题
题目描述 Description
第二次世界大战时期,英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的2 名飞行员,其中1 名是英国飞行员,另1 名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。如何选择配对飞行的飞行员才能使一次派出最多的飞机。对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
对于给定的外籍飞行员与英国飞行员的配合情况,编程找出一个最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
输入描述 Input Description
由文件input.txt提供输入数据。文件第1 行有2个正整数m和n。n是皇家空军的飞行员总数(n<100);m是外籍飞行员数。外籍飞行员编号为1~m;英国飞行员编号为m+1~n。接下来每行有2 个正整数i和j,表示外籍飞行员i可以和英国飞行员j配合。文件最后以2个-1 结束。
输出描述 Output Description
程序运行结束时,将最佳飞行员配对方案输出到文件output.txt 中。第1 行是最佳飞行员配对方案一次能派出的最多的飞机数M。接下来M 行是最佳飞行员配对方案。每行有2个正整数i和j,表示在最佳飞行员配对方案中,飞行员i和飞行员j 配对。如果所求的最佳飞行员配对方案不存在,则输出‘No Solution!’。
样例输入 Sample Input
10 5
1 7
2 6
2 10
3 7
4 8
5 9
样例输出 Sample Output
4题解
就是一个裸的二分图匹配 可以用网络流和二分图匹配两种方法做 显然二分图匹配更好打
二分图:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m;
int flag[2005],mp[2005][2005],mat[2005];
bool km(int x){
for(int i=m;i<=n;i++)
{
if(mp[x][i]&&!flag[i]){
flag[i]=1;
if(!mat[i]||km(mat[i])){
mat[i]=x;
return 1;
}
}
}
return 0;
}
int main(){
scanf("%d%d",&m,&n);
int x,y;
while(scanf("%d%d",&x,&y)){
if(x==-1&&y==-1) break;
mp[x][y]=mp[y][x]=1;
}
int ans=0;
for(int i=1;i<=m;i++){
memset(flag,0,sizeof(flag));
if(km(i)) ans++;
}
if(ans==0)
printf("No Solution!\n");
else
{
printf("%d\n",ans);
for(int i=m+1;i<=n;i++)
{
if(mat[i])
printf("%d %d\n",mat[i],i);
}
}
return 0;
}
网络流:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7ffffff
using namespace std;
int d[2005],head[2005],ans=0,cnt=1,n,m;
int start=0,end=2001;
struct edge{int to,next,flow;}e[100001];
queue<int>q;
void ini(int x,int y,int flow){
e[++cnt].to=y;e[cnt].flow=flow;e[cnt].next=head[x];head[x]=cnt;
}
void insert(int x,int y,int flow){
ini(x,y,flow);ini(y,x,0);
}
bool bfs(){
memset(d,-1,sizeof(d));
d[start]=0;
q.push(start);
while(!q.empty()){
int k=q.front();q.pop();
for(int i=head[k];i;i=e[i].next){
int kk=e[i].to;
if(d[kk]==-1&&e[i].flow>0){
d[kk]=d[k]+1;
q.push(kk);
}
}
}
return d[end]!=-1;
}
int dfs(int x,int f){
if(x==end) return f;
for(int i=head[x];i;i=e[i].next){
int k=e[i].to;int a;
if(d[k]==d[x]+1&&e[i].flow>0&&(a=dfs(k,min(e[i].flow,f))))
{
e[i].flow-=a;
e[i^1].flow+=a;
return a;
}
}
return 0;
}
void dinic(){
while(bfs()){
int a;
while(a=dfs(start,inf))
ans+=a;
}
}
int main(){
scanf("%d%d",&m,&n);
int x,y;
for(int i=1;i<=m;i++) insert(start,i,1);
for(int i=m+1;i<=n;i++) insert(i,end,1);
while(scanf("%d%d",&x,&y)){
if(x==-1&&y==-1) break;
insert(x,y,1);
}
dinic();
if(ans==0)
printf("No Solution!\n");
else
printf("%d\n",ans);
return 0;
}
4.餐巾计划问题
题目描述 Description
输入描述 Input Description
输出描述 Output Description
样例输入 Sample Input
样例输出 Sample Output
题解
二分图的思想
把每个点分为:
今天用了要去洗的(i-day) 和 洗好了可以去用的(day+1-2*day) 然后就是连边 分四种情况(还可以留到明天再洗 费用为0)
注意细节就好了 跑最小费用最大流
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7ffffff
using namespace std;
int day,p,m,f,n,s;int start=0,end=2001,cnt=1;
int d[2005],flag[2005],head[2005],from[2005],min_cost=0;
struct edge{int to,next,flow,cost;}e[1000001];
queue<int>q;
void ini(int x,int y,int flow,int cost){e[++cnt].to=y;e[cnt].flow=flow;e[cnt].cost=cost;e[cnt].next=head[x];head[x]=cnt;}
void insert(int x,int y,int flow,int cost){ini(x,y,flow,cost);ini(y,x,0,-cost);}
bool spfa(){
memset(d,127,sizeof(d));
q.push(start);
d[start]=0;flag[start]=1;
while(!q.empty()){
int k=q.front();q.pop();flag[k]=0;
for(int i=head[k];i;i=e[i].next){
int kk=e[i].to;
if(e[i].flow>0&&d[kk]>d[k]+e[i].cost)
{
d[kk]=d[k]+e[i].cost;from[kk]=i;
if(!flag[kk]){
flag[kk]=1;
q.push(kk);
}
}
}
}
return d[end]<inf;
}
void mcf(){
while(spfa())
{
int mn=inf;
for(int i=from[end];i;i=from[e[i^1].to])
mn=min(mn,e[i].flow);
min_cost+=d[end]*mn;
for(int i=from[end];i;i=from[e[i^1].to])
{
e[i].flow-=mn;e[i^1].flow+=mn;
}
}
}
int main(){
scanf("%d%d%d%d%d%d",&day,&p,&m,&f,&n,&s);
for(int i=1;i<=day;i++){
int x;
scanf("%d",&x);
insert(start,i,x,0);
insert(i+day,end,x,0);
}
for(int i=1;i<=day;i++)
{
if(i+1<=day) insert(i,i+1,inf,0);
if(i+m<=day) insert(i,i+m+day,inf,f);
if(i+n<=day) insert(i,i+n+day,inf,s);
insert(start,day+i,inf,p);
}
mcf();
printf("%d",min_cost);
return 0;
}
5.运输问题
题目描述 Description
输入描述 Input Description
输出描述 Output Description
样例输入 Sample Input
样例输出 Sample Output
题解
比较简单就不说了 就是费用流裸题
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7ffffff
using namespace std;
int n,m;int start=0,end=2001,cnt=1;
int d[2005],c[2005][2005],a[2005],b[2005],flag[2005],head[2005],from[2005],min_cost;
struct edge{int to,next,flow,cost;}e[1000001];
queue<int>q;
void ini(int x,int y,int flow,int cost){e[++cnt].to=y;e[cnt].flow=flow;e[cnt].cost=cost;e[cnt].next=head[x];head[x]=cnt;}
void insert(int x,int y,int flow,int cost){ini(x,y,flow,cost);ini(y,x,0,-cost);}
bool spfa(){
memset(d,127,sizeof(d));
q.push(start);
d[start]=0;flag[start]=1;
while(!q.empty()){
int k=q.front();q.pop();flag[k]=0;
for(int i=head[k];i;i=e[i].next){
int kk=e[i].to;
if(e[i].flow>0&&d[kk]>d[k]+e[i].cost)
{
d[kk]=d[k]+e[i].cost;from[kk]=i;
if(!flag[kk]){
flag[kk]=1;
q.push(kk);
}
}
}
}
return d[end]<inf;
}
void mcf(){
while(spfa())
{
int mn=inf;
for(int i=from[end];i;i=from[e[i^1].to])
mn=min(mn,e[i].flow);
min_cost+=d[end]*mn;
for(int i=from[end];i;i=from[e[i^1].to])
{
e[i].flow-=mn;e[i^1].flow+=mn;
}
}
}
void build(int k){
cnt=1;
memset(head,0,sizeof(head));
for(int i=1;i<=m;i++) insert(start,i,a[i],0);
for(int i=1;i<=n;i++) insert(i+m,end,b[i],0);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
insert(i,j+m,inf,k*c[i][j]);
}
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
scanf("%d",&c[i][j]);
build(1);
mcf();printf("%d\n",min_cost);
min_cost=0;
build(-1);
mcf();printf("%d\n",-min_cost);
return 0;
}
6.太空飞行计划问题
Description
W教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合 E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的集合 I={I1,I2,…In}。实验 Ej需要用到的仪器是 I的子集。配置仪器 Ik的费用为 ck美元。实验 Ej的赞助商已同意为该实验结果支付 pj美元。W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。
Input
文件第 1行有 2个正整数 m和 n。m是实验数,n是仪器数。
接下来的 m行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。
最后一行的 n个数是配置每个仪器的费用。
Output
程序运行结束时,将最佳实验方案输出。第 1行是实验编号;第 2行是仪器编号;最后一行是净收益。
Sample Input
2 3
10 1 2
25 2 3
5 6 7
Sample Output
1 2
1 2 3
17
HINT
n,m < 50
题解
有点难。。但是建好图就简单了 转换为最小割 进而转为最大流
下面是解析:
【问题分析】
最大权闭合图问题,可以转化成最小割问题,进而用最大流解决。
【建模方法】
把每个实验看作二分图X集合中的顶点,每个设备看作二分图Y集合中的顶点,增加源S和汇T。
1、从S向每个Xi连接一条容量为该点收入的有向边。
2、从Yi向T连接一条容量为该点支出的有向边。
3、如果一个实验i需要设备j,连接一条从Xi到Yj容量为无穷大的有向边。
统计出所有实验的收入只和Total,求网络最大流Maxflow,最大收益就是Total-Maxflow。对应的解就是最小割划分出的S集合中的点,也就是最后一次增广找到阻塞流时能从S访问到的顶点。
【建模分析】
定义一个割划分出的S集合为一个解,那么割集的容量之和就是(未被选的A集合中的顶点的权值 + 被选的B集合中的顶点的权值),记为Cut。
A集合中所有顶点的权值之和记为Total,那么Total - Cut就是(被选的A集合中的顶点的权值 - 被选的B集合中的顶点的权值),即为我们的目标函数,记为A。
要想最大化目标函数A,就要尽可能使Cut小,Total是固定值,所以目标函数A取得最大值的时候,Cut最小,即为最小割。
该问题的一般模型为最大权闭合图,相关讨论见《最小割模型在信息学竞赛中的应用》作者胡伯涛。
大概就是这样了。。下面代码:
//最小割——>最大流
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7ffffff
using namespace std;
int n,m,cnt=1;
int w[2005],cost[2005],d[2005],head[2005],start=0,end=2001,ans=0;
struct edge{int to,next,flow;}e[100001];
void ini(int x,int y,int flow){
e[++cnt].to=y;e[cnt].flow=flow;e[cnt].next=head[x];head[x]=cnt;
}
void insert(int x,int y,int flow){
ini(x,y,flow);ini(y,x,0);
}
queue<int>q;
bool bfs(){
memset(d,-1,sizeof(d));
q.push(start);d[start]=0;
while(!q.empty()){
int k=q.front();q.pop();
for(int i=head[k];i;i=e[i].next){
int kk=e[i].to;
if(e[i].flow>0&&d[kk]==-1)
{
d[kk]=d[k]+1;
q.push(kk);
}
}
}
return d[end]!=-1;
}
int dfs(int x,int f){
if(x==end) return f;
for(int i=head[x];i;i=e[i].next){
int k=e[i].to;int a;
if(d[k]==d[x]+1&&e[i].flow>0&&(a=dfs(k,min(e[i].flow,f))))
{
e[i].flow-=a;
e[i^1].flow+=a;
return a;
}
}
return 0;
}
void dinic(){
while(bfs()){
int a;
while(a=dfs(start,end))
ans+=a;
}
}
int main(){
int tot=0;
scanf("%d%d",&m,&n);
int tmp;
for(int i=1;i<=m;i++){
scanf("%d",&tmp);
tot+=tmp;
insert(start,i,tmp);
char ch;
int x=0;
while(scanf("%c",&ch)){
if(ch==' '){
insert(i,x+1000,inf);
x=0;
continue;
}
else if(ch==10||ch==13){
insert(i,x+1000,inf);
x=0;break;
}
else
x=x*10+ch-'0';
}
}
int x;
for(int i=1;i<=n;i++){
scanf("%d",&x);
insert(i+1000,end,x);
}
dinic();
printf("%d",tot-ans);
return 0;
}