写这篇博客主要是给自己看的督促自己写题
搭配飞行员
题意&&解法:
二分图匹配模板题
匈牙利算法模板get
ACODE:
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int maxn = 200 + 7;
int n, m;
int mp[maxn][maxn];
bool vis[maxn];
int match[maxn];
bool find(int x) {
for (int i = 1; i <= n; ++i) {
if (mp[x][i] && !vis[i]) {
vis[i] = true;
if (!match[i] || find(match[i])) {
match[i] = x;
return true;
}
}
}
return false;
}
int work() {
int res = 0;
for (int i = 1; i <= m; ++i) {
memset(vis, false, sizeof vis);
if (find(i))
res++;
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
n -= m;
int u, v;
while (~scanf("%d%d", &u, &v)) {
mp[u][v - m] = true;
}
printf("%d\n", work());
return 0;
}
太空飞行计划
题意:
有若干个实验以及器材,完成某个实验需要同时拥有其所需全部器材。完成实验有对应回报,但采购其所需器材有对应花费。问:该如何安排方案从而使得最终获利最大。
这是典型的最大权闭合子图模型
关于所谓的最大权闭合子图,我一开始一直不太明白,后来看了很多dalao的博客才稍微懂了你以为你懂了
这里引用若干我觉得写的很好的dalao的博客来帮助理解:
Sources:Mickey-snow , 岳知涵, Dilthey
概念
最大权闭合子图是一类问题,可以抽象地描述为从一个带有点权的有向图 G=(V,E) 中选择一个点权最大的子图,必须满足所有选择的点在原图中可以抵达的所有点都必须包含在这个子图中。
建图
建立源点s和汇点t
将s与所有点权为正的点连边,容量为点权
将所有点权为负的点与t连边,容量为点权的绝对值
添加 E 中 所有的边,容量为 ∞
求解
所有点权为正的点权之和 - 网络流图中的最小割 = 最大权闭合子图的点权之和
证明
求证:设最小割为[S,T],则选的点的集合为S-{s}有最优性,且最优值为正权点和-最小割。
证明:∵最小割必不包含正无穷的边,∴最小割集中的边(u,v)一定满足u=s或v=t。
∵对于最小割集中的任一条边(u,v),均满足u属于S,且v属于T,([S,T]割的基本性质)
∴S-{s}={s附近没被割的点}∪{t附近被割的点}={没被割的正权点}∪{被割的负权点}
则 ans=正权点和-被割的正权点+(-被割的负权点)=正权点和-最小割值
最小割是最小值,则此时解一定是最大值。证毕。
输出方案
根据最后一次增广后的dis数组,若某个点的值不为0,代表该点是最大权闭合子图中的点
ACODE:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MX = 4e5 + 7;
int S,T,n,m;
struct Dinic
{
int hcnt,head[MX];
int dis[MX],cur[MX];
struct Edge
{
int v,w,next;
}e[MX << 1];
inline void add(int u,int v,int w){
e[hcnt].v = v;e[hcnt].w = w;e[hcnt].next = head[u];head[u] = hcnt++;
e[hcnt].v = u;e[hcnt].w = 0;e[hcnt].next = head[v];head[v] = hcnt++;
}
inline void init(){
for(int i = S;i <= T;++i) head[i] = -1;
hcnt = 0;
}
inline bool bfs(){
for(int i = S;i <= T;++i) dis[i] = 0;
queue<int>q;
q.push(S);dis[S] = 1;
while(!q.empty()){
int u = q.front();q.pop();
for(int i = head[u];~i;i = e[i].next){
int v = e[i].v,w = e[i].w;
if(w > 0 && !dis[v]){
dis[v] = dis[u] + 1;
if(v == T) return true;
q.push(v);
}
}
}
return dis[T];
}
inline int dfs(int u,int in){
if(u == T) return in;
int out = 0;
int res = in;
for(int i = cur[u];~i;i = e[i].next){
cur[u] = i;
int v = e[i].v,w = e[i].w;
if(res <= 0) break;
if(w <= 0 || dis[v] != dis[u] + 1) continue;
int flow = dfs(v,min(res,w));
e[i].w -= flow,e[i^1].w += flow;
res -= flow,out += flow;
}
return out;
}
int sol(){
int res = 0;
while(bfs()){
for(int i = S;i <= T;++i) cur[i] = head[i];
res += dfs(S,INF);
}
return res;
}
//输出方案
void print(){
for(int i = 1;i <= m;++i){
if(dis[i]) printf("%d ",i);
}
printf("\n");
for(int i = 1;i <= n;++i){
if(dis[i+m]) printf("%d ",i);
}
printf("\n");
}
}dinic;
int main()
{
int sum = 0;
scanf("%d%d",&m,&n);
S = 0,T = n + m + 1;
dinic.init();
for(int i = 1;i <= m;++i){
int w;scanf("%d",&w);
sum += w;
dinic.add(S,i,w);
while(true){
char c;int v;
scanf("%d%c",&v,&c);
dinic.add(i,v+m,INF);
if(c == '\n' || c == '\r') break;
}
}
for(int i = 1;i <= n;++i){
int w;scanf("%d",&w);
dinic.add(i+m,T,w);
}
int res = dinic.sol();
dinic.print();
printf("%d\n",sum - res);
return 0;
}
最小路径覆盖
题意:
给定有向图 G=(V,E) 。设 P 是 G 的一个简单路(顶点不相交)的集合。如果 V 中每个定点恰好在P的一条路上,则称 P 是 G 的一个路径覆盖。P中路径可以从 V 的任何一个定点开始,长度也是任意的,特别地,可以为 0 。G 的最小路径覆盖是 G 所含路径条数最少的路径覆盖。设计一个有效算法求一个 GAP (有向无环图) G 的最小路径覆盖。
求解
最小点覆盖 = V - 最大匹配
将原图拆成二分图,用匈牙利算法计算最大匹配,同时以并查集维护路径
输出方案
并查集
Trick:
连边的时候保证编号小的边( L )连向编号大的边( R ),故输出路径时只需要从大到小依次寻找即可
ACODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MX = 3e3 + 7;
int n,m;
bool mp[MX][MX];
int match[MX];
bool vis[MX];
//匈牙利算法
bool dfs(int u){
for(int i = 1;i <= n;++i){
if(vis[i] || !mp[u][i]) continue;
vis[i] = true;
if(!match[i] || dfs(match[i])){
match[i] = u;
return true;
}
}
return false;
}
void work(){
int cnt = 0;
for(int i = 1;i <= n;++i){
memset(vis,false,sizeof(vis));
if(dfs(i)) cnt++;
}
//输出路径
memset(vis,false,sizeof(vis));
for(int i = n;i >= 1;--i){
if(match[i] && !vis[i]){
int x = i;
while(true){
printf("%d ",x);
vis[x] = true;
x = match[x];
if(!x || vis[x]) break;
}
printf("\n");
}
else if(!vis[i]){
printf("%d\n",i);
}
}
//最小点覆盖 = V - 最大匹配
printf("%d\n",n - cnt);
}
int main(){
scanf("%d%d",&n,&m);
while(m--){
int u,v;scanf("%d%d",&u, &v);
//trick
if(u > v) swap(u,v);
mp[u][v] = true;
}
work();
return 0;
}
负载平衡
套用某巨巨的话就是
“假网络流真傻x题”
但毕竟是网络流24题还是用网络流写吧
建图
① S - > 每个仓库,容量为仓库原货物量,费用为0
② 每个仓库 - > T,容量为平均后最终每个仓库的货物,费用为0
③ 相邻两个仓库直接建双向边,容量为INF,费用为1
跑最小费用最大流即可出答案
ACODE:
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int MX = 3e5 + 7;
typedef long long ll;
const int INF = 0x3f3f3f3f;
inline ll read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct node
{
int x,y,c,cc,next;
}a[MX];
int len=1,last[MX],maxn=1e9;
int vis[MX];int dis[MX];
int n,m,s,t,ans=0;
inline void add(int x,int y,int c,int cc)
{
len++;
a[len].x=x;a[len].y=y;a[len].c=c;a[len].cc=cc;
a[len].next=last[x];last[x]=len;
len++;
a[len].x=y;a[len].y=x;a[len].c=0;a[len].cc=-cc;
a[len].next=last[y];last[y]=len;
}
inline bool spfa(int s,int t)
{
memset(vis,0,sizeof(vis));
for(int i=0;i<=n;i++)dis[i]=maxn;
vis[t]=1;dis[t]=0;
deque<int>p;p.push_back(t);
while(!p.empty())
{
int now=p.front();p.pop_front();
for(int k=last[now];k;k=a[k].next)
{
if(a[k^1].c>0)
{
int y=a[k].y;
if(dis[y]>dis[now]-a[k].cc)
{
dis[y]=dis[now]-a[k].cc;
if(vis[y]==0)
{
vis[y]=1;
if(!p.empty() && dis[y]<dis[now])p.push_front(y);
else p.push_back(y);
}
}
}
}
vis[now]=0;
}
if(dis[s]==maxn)return(false);
else return(true);
}
int dfs(int x,int low)
{
if(x==t){vis[t]=1;return low;}
int used=0,aa;vis[x]=1;
for(int k=last[x];k; k=a[k].next)
{
int y=a[k].y;
if(vis[y]==0 && a[k].c>0 && dis[x]-a[k].cc==dis[y])
{
aa=dfs(y,min(a[k].c,low-used));
if(aa>0)ans+=aa*a[k].cc,a[k].c-=aa,a[k^1].c+=aa,used+=aa;
if(used==low)break;
}
}
return used;
}
inline int costflow()
{
int flow=0;
while(spfa(s,t))
{
vis[t]=1;
while(vis[t])
{
memset(vis,0,sizeof(vis));
flow+=dfs(s,maxn);
}
}
return(flow);
}
void init(){
len = 1;
memset(last,0,sizeof(last));
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
ans = 0;
}
int val[MX];
int main()
{
n = read();
s = 0,t = n+1;
init();
int sum = 0;
for(int i = 1;i <= n;++i) val[i] = read(),sum += val[i];
int ave = sum / n;
for(int i = 1;i <= n;++i){
add(s,i,val[i],0);
add(i,t,ave,0);
if(i == 1){
add(i,i+1,INF,1);
add(i,n,INF,1);
}
else if(i == n){
add(i,1,INF,1);
add(i,i-1,INF,1);
}
else{
add(i,i+1,INF,1);
add(i,i-1,INF,1);
}
}
costflow();
printf("%d\n",ans);
return 0;
}