题目列表:
飞行员配对方案问题
题目背景
第二次世界大战时期..
题目描述
英国皇家空军从沦陷国征募了大量外籍飞行员。由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的2 名飞行员,其中1 名是英国飞行员,另1名是外籍飞行员。在众多的飞行员中,每一名外籍飞行员都可以与其他若干名英国飞行员很好地配合。如何选择配对飞行的飞行员才能使一次派出最多的飞机。对于给定的外籍飞行员与英国飞行员的配合情况,试设计一个算法找出最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
对于给定的外籍飞行员与英国飞行员的配合情况,编程找出一个最佳飞行员配对方案,使皇家空军一次能派出最多的飞机。
输入输出格式
输入格式:
第 1 行有 2 个正整数 m 和 n。n 是皇家空军的飞行员总数(n<100);m 是外籍飞行员数(m<=n)。外籍飞行员编号为 1~m;英国飞行员编号为 m+1~n。
接下来每行有 2 个正整数 i 和 j,表示外籍飞行员 i 可以和英国飞行员 j 配合。最后以 2个-1 结束。
输出格式:
第 1 行是最佳飞行员配对方案一次能派出的最多的飞机数 M。接下来 M 行是最佳飞行员配对方案。每行有 2个正整数 i 和 j,表示在最佳飞行员配对方案中,飞行员 i 和飞行员 j 配对。如果所求的最佳飞行员配对方案不存在,则输出‘No Solution!’。
题解:
本质上是二分图最大匹配,直接建图就好。
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
const int maxn=5000;
namespace Dinic {
struct Edge{
int u,v,cap,flow;
Edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){
}
};
std::vector<Edge> edges;
std::vector<int> G[maxn];
int n,s,t;
int d[maxn];
bool vis[maxn];
int cur[maxn];
void addEdge(int u,int v,int cap) {
edges.push_back(Edge(u,v,cap,0));
edges.push_back(Edge(v,u,0,0));
int m=edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
bool BFS() {
std::queue<int> Q;
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty()) {
int u=Q.front();
Q.pop();
for(register int i=0;i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(!vis[e.v]&&e.cap>e.flow) {
d[e.v]=d[u]+1;
Q.push(e.v);
vis[e.v]=1;
}
}
}
return vis[t];
}
int DFS(int u,int a) {
if(u==t||a==0) return a;
int flow=0,f;
for(register int &i=cur[u];i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(d[e.v]==d[u]+1&&(f=DFS(e.v,std::min(a,e.cap-e.flow)))>0) {
e.flow+=f;
a-=f;
edges[G[u][i]^1].flow-=f;
flow+=f;
if(a==0) break;
}
}
return flow;
}
int maxFlow(int s,int t) {
Dinic::s=s;
Dinic::t=t;
int ans=0;
while(BFS()) {
memset(cur,0,sizeof(cur));
ans+=DFS(s,t);
}
return ans;
}
}
int n,m,a,b;
int main() {
// freopen("test.txt","r",stdin);
scanf("%d%d",&m,&n);
scanf("%d%d",&a,&b);
while(a!=-1&&b!=-1) {
Dinic::addEdge(a,b,1);
scanf("%d%d",&a,&b);
}
for(register int i=1;i<=m;i++) {
Dinic::addEdge(0,i,1);
}
for(register int i=1;i<=n;i++) {
Dinic::addEdge(i+m,n+m+1,1);
}
int mx=Dinic::maxFlow(0,n+m+1);
if(mx==0) {
puts("No Solution!");
return 0;
}
printf("%d
",mx);
for(register int i=1;i<=m;i++) {
for(register int j=0;j<Dinic::G[i].size();++j) {
Dinic::Edge& e = Dinic::edges[Dinic::G[i][j]];
if(e.flow==1){
printf("%d %d
",i,e.v);
break;
}
}
}
return 0;
}
软件补丁问题
题目背景
none!
题目描述
T 公司发现其研制的一个软件中有 n 个错误,随即为该软件发放了一批共 m 个补丁程序。每一个补丁程序都有其特定的适用环境,某个补丁只有在软件中包含某些错误而同时又不包含另一些错误时才可以使用。一个补丁在排除某些错误的同时,往往会加入另一些错误。
换句话说,对于每一个补丁 i,都有 2 个与之相应的错误集合 B1[i]和 B2[i],使得仅当软件包含 B1[i]中的所有错误,而不包含 B2[i]中的任何错误时,才可以使用补丁 i。补丁 i 将修复软件中的某些错误 F1[i],而同时加入另一些错误 F2[i]。另外,每个补丁都耗费一定的时间。
试设计一个算法,利用 T 公司提供的 m 个补丁程序将原软件修复成一个没有错误的软件,并使修复后的软件耗时最少。对于给定的 n 个错误和 m 个补丁程序,找到总耗时最少的软件修复方案。
输入输出格式
输入格式:
第 1 行有 2 个正整数 n 和 m,n 表示错误总数,m表示补丁总数,1<=n<=20, 1<=m<=100。
接下来 m 行给出了 m 个补丁的信息。每行包括一个正整数,表示运行补丁程序 i 所需时间,以及 2 个长度为 n 的字符串,中间用一个空格符隔开。
第 1 个字符串中,如果第 k 个字符 bk 为“+”,则表示第 k 个错误属于 B1[i],若为“-”,则表示第 k 个错误属于 B21[i],若为“0”,则第 k 个错误既不属于 B1[i]也不属于 B2[i],即软件中是否包含第 k 个错误并不影响补丁 i 的可用性。
第 2 个字符串中,如果第 k 个字符 bk为“-”,则表示第 k 个错误属于 F1[i],若为“+”,则表示第 k 个错误属于 F2[i],若为“0”,则第 k 个错误既不属于 F1[i]也不属于 F2[i],即软件中是否包含第 k 个错误不会因使用补丁i 而改变。
输出格式:
程序运行结束时,将总耗时数输出。如果问题无解,则输出 0。
题解
没想到网络流做法,可以直接跑最短路。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<string>
#include<ctime>
#define LL long long
using namespace std;
const int N=21,M=101,inf=1e9;
int n,m,val[M],b1[M],b2[M],f1[M],f2[M],dis[1<<N],q[1<<(N+1)],h,t;
bool inq[1<<N];
char s[N];
int read() {int d=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') d=(d<<3)+(d<<1)+c-48,c=getchar(); return d*f;}
void init()
{
n=read(); m=read();
for (int i=1;i<=m;i++)
{
val[i]=read();
scanf("%s",&s);
for (int j=0;j<n;j++)
if (s[j]=='+') b1[i]|=(1<<j);
else if (s[j]=='-') b2[i]|=(1<<j);
scanf("%s",&s);
for (int j=0;j<n;j++)
if (s[j]=='+') f2[i]|=(1<<j);
else if (s[j]=='-') f1[i]|=(1<<j);
}
}
void spfa()
{
int ljj=(1<<n)-1;
for (int i=0;i<ljj;i++) dis[i]=inf;
h=1; t=1;
q[1]=ljj; dis[ljj]=0;
while (h<=t)
{
int x=q[h++]; inq[x]=0;
for (int i=1;i<=m;i++)
if (!(x&b2[i])&&(x|b1[i])==x)
{
int y=x&(~f1[i])|f2[i];
if (dis[x]+val[i]<dis[y])
{
dis[y]=dis[x]+val[i];
if (!inq[y]) q[++t]=y,inq[y]=1;
}
}
}
}
int main()
{
init();
spfa();
if (dis[0]==inf) printf("0");
else printf("%d",dis[0]);
return 0;
}
餐巾计划问题
题解
对于第i天,拆点i1,i2,分别表示用过的脏餐巾和可以用的餐巾
S向i1连容量为x,费用为0的边,表示每天可以产生x块脏餐巾;i1向i1+1连一条容量为无穷大,费用为0的边,表示今天剩的脏餐巾可以等到明天处理
i2向T连容量为x,费用为0的边,表示需要x块新餐巾
i1向i2+m(快洗天数)连一条容量为无穷大,费用为f(快洗费用)的边(慢洗同理)
S向i2连一条容量为x,费用为p的边,表示每天可以新买的餐巾
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
typedef long long ll;
const ll maxm = 5e4+10,maxn = 4e4+10,inf=0x3fffffff;
int wet[maxm],fee[maxm],nxt[maxm],to[maxm],hd[maxn],n,a[maxn],cnt=1,S,T,p,m,f,md,s,cur[maxn];
bool vis[maxn];
ll dis[maxn];
std::queue<int> Q;
inline void add(int u,int v,ll w,ll cost) {
nxt[++cnt]=hd[u];
to[cnt]=v;
wet[cnt]=w;
fee[cnt]=cost;
hd[u]=cnt;
}
inline bool spfa(void) {
memset(dis,63,sizeof dis);
memset(vis,0,sizeof vis);
for(int i =S;i<=T;++i) cur[i]=hd[i];
Q.push(S);
dis[S]=0;
while(!Q.empty()) {
int u = Q.front();
Q.pop();
vis[u]=0;
for(int i = hd[u];i;i=nxt[i]) {
int nx = to[i];
if(dis[nx]>dis[u]+fee[i]&&wet[i]>0) {
dis[nx]=dis[u]+fee[i];
if(!vis[nx]) {
vis[nx]=1;
Q.push(nx);
}
}
}
}
return dis[T]<dis[4005];
}
inline int dfs(int u,ll flow,ll &ans) {
vis[u]=1;
if(u==T) {
ans+=1LL*dis[u]*flow;
return flow;
}
ll tmp = 0;
for(int &i=cur[u];i;i=nxt[i]) {
int nx = to[i];
if(wet[i]>0&&dis[nx]==dis[u]+fee[i]&&!vis[nx]) {
ll ret = dfs(nx,std::min(flow,(ll)wet[i]),ans);
flow-=ret;
wet[i]-=ret;
wet[i^1]+=ret;
tmp+=ret;
if(flow==0) break;
}
}
return tmp;
}
inline void dinic(void) {
ll ans = 0;
while(spfa()) {
vis[T]=1;
while(vis[T]) {
vis[T]=0;
dfs(S,inf,ans);
}
}
printf("%lld
",ans);
}
signed main() {
scanf("%d",&n);
S=0;T=n+n+1;
for(int i = 1;i<=n;++i) scanf("%d",&a[i]);
scanf("%d%d%d%d%d",&p,&m,&f,&md,&s);
for(int i = 1;i<=n;++i) {
add(S,i,a[i],0);
add(i,S,0,0);
}
for(int i = 1;i<n;++i) {
add(i,i+1,inf,0);
add(i+1,i,0,0);
}
for(int i = 1;i<=n-m;++i) {
add(i,i+m+n,inf,f);
add(i+m+n,i,0,-f);
}
for(int i = 1;i<=n-md;++i) {
add(i,i+md+n,inf,s);
add(i+md+n,i,0,-s);
}
for(int i = n+1;i<=n+n;++i) {
add(i,T,a[i-n],0);
add(T,i,0,0);
add(S,i,a[i-n],p);
add(i,S,0,-p);
}
dinic();
return 0;
}
【CTSC1999】家园
题解:
暂时没想到正解,但是想了一个胡搞的办法。先对每一天建图,然后跑一个简单的分层最大流。如果最大流<k,那么再加一天,再跑。如果当天数很多的时候,还是没有使最大流大于等于k,那么证明无解。
这道题先坑着,到时候再填。
#include<cstdio>
#include<cstring>
#define min(x,y) ((x)<(y)?(x):(y))
#define inf 2147483647
struct node{int x,y,c,next,other;} a[100000];
int last[100000],peo[201],le[201];
int map[201][201];
int n,m,k,len=0,ans=0,sum=0,st=0,ed=9999;
void ins(int x,int y,int c)
{
a[++len].x=x;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;a[len].other=len+1;
a[++len].x=y;a[len].y=x;a[len].c=0;a[len].next=last[y];last[y]=len;a[len].other=len-1;
}
int f[100000],h[100000];
bool bfs()
{
int head=1,tail=2;
memset(h,0,sizeof(h));
h[st]=1;
f[1]=st;
while(head<tail)
{
int x=f[head];
for(int i=last[x];i!=-1;i=a[i].next)
{
int y=a[i].y;
if(a[i].c>0&&h[y]==0)
{
h[y]=h[x]+1;
f[tail++]=y;
}
}
head++;
}
if(h[ed]>0) return true; else return false;
}
int dfs(int x,int f)
{
int s=0,t;
if(x==ed) return f;
for(int i=last[x];i!=-1;i=a[i].next)
{
int y=a[i].y;
if(a[i].c>0&&h[y]==h[x]+1&&f>s)
{
s+=(t=(dfs(y,min(f-s,a[i].c))));
a[i].c-=t;
a[a[i].other].c+=t;
}
}
if(s==0) h[x]=0;
return s;
}
int dinic()
{
int flow=0;
while(bfs())
flow+=dfs(st,inf);
return flow;
}
int main()
{
memset(last,-1,sizeof(last));
scanf("%d %d %d",&n,&m,&k);
n+=2;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&peo[i],&le[i]);
for(int j=0;j<le[i];j++)
{
scanf("%d",&map[i][j]);
map[i][j]+=2;
}
}
while(ans<500)
{
ins(ans*n+1,ed,inf);
ins(st,ans*n+2,inf);
if(ans!=0)
{
for(int i=1;i<=n;i++)
ins((ans-1)*n+i,ans*n+i,inf);
for(int i=1;i<=m;i++)
{
int x=map[i][(ans-1)%le[i]];
int y=map[i][ans%le[i]];
ins((ans-1)*n+x,ans*n+y,peo[i]);
}
}
sum+=dinic();
if(sum>=k) break;
ans++;
}
if(ans==500) printf("0"); else printf("%d",ans);
}
魔术球问题
题解 :
这是个普及组问题。有一个显而易见的贪心算法:尽可能在已经放上的柱子上放球,如果实在无法放置,再开新柱子。
贪心证明:如果当前可以在某个柱子上放,不放,而是新开一个柱子,那么如果不是为了本可以放的柱子上放其他的珠子,那么答案不会变得更优。如果是为了放其他的珠子,则可以直接把那个珠子放在一个新柱子上,答案也不会变得更差。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
const int maxn = 1e4,maxm = 1e6+10;
bool sqr[maxn];
int n;
std::vector<int> d[60];
int ans = 0,cnt=0;
inline void prelude() {
scanf("%d",&n);
for(int i = 1;i*i<maxn;++i) {
sqr[i*i]=1;
}
}
inline void solve() {
while(true) {
int i = 0;
while(i<cnt) {
if(sqr[d[i].back()+ans+1]) {
++ans;
d[i].push_back(ans);
i=0;
}
else ++i;
}
if(cnt<n) {
++ans;
d[cnt++].push_back(ans);
}
else break;
}
}
int main() {
prelude();
solve();
printf("%d
",ans);
for(int i = 0;i<cnt;++i) {
for(int j = 0;j<d[i].size();++j) {
printf("%d ",d[i][j]);
}
puts("");
}
return 0;
}
骑士共存问题
题解:
我们观察到,在一个国际象棋棋盘上,马只能从一种颜色的方格跳到另一种颜色的方格。我们以方格颜色,建成二分图。能够互相攻击的点连边就行。问题转换成了求这张图的最大独立集。我们可以用网络流建图来跑,当然也可以用匈牙利算法。
细节:注意障碍的格子不能统计到二分图内。
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
const int maxn=100000;
const int INF=1<<25;
namespace Dinic {
struct Edge{
int u,v,cap,flow;
Edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){
}
};
std::vector<Edge> edges;
std::vector<int> G[maxn];
int n,s,t;
int d[maxn];
bool vis[maxn];
int cur[maxn];
void addEdge(int u,int v,int cap) {
edges.push_back(Edge(u,v,cap,0));
edges.push_back(Edge(v,u,0,0));
int m=edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
bool BFS() {
std::queue<int> Q;
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty()) {
int u=Q.front();
Q.pop();
for(register int i=0;i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(!vis[e.v]&&e.cap>e.flow) {
d[e.v]=d[u]+1;
Q.push(e.v);
vis[e.v]=1;
}
}
}
return vis[t];
}
int DFS(int u,int a) {
if(u==t||a==0) return a;
int flow=0,f;
for(register int &i=cur[u];i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(d[e.v]==d[u]+1&&(f=DFS(e.v,std::min(a,e.cap-e.flow)))>0) {
e.flow+=f;
a-=f;
edges[G[u][i]^1].flow-=f;
flow+=f;
if(a==0) break;
}
}
return flow;
}
int maxFlow(int s,int t) {
Dinic::s=s;
Dinic::t=t;
int ans=0;
while(BFS()) {
memset(cur,0,sizeof(cur));
ans+=DFS(s,t);
}
return ans;
}
}
int n,m,X[maxn],Y[maxn],a,b;
int tx=0,ty=0;
int map[500][500],tot=0,tt;
int move[8][2]={-2,1,-1,2,1,2,2,1,2,-1,1,-2,-1,-2,-2,-1};
int main() {
scanf("%d%d",&n,&m);
tt=m;
while(m--) {
scanf("%d%d",&a,&b);
map[a][b]=-1;
}
for(register int i=1;i<=n;i++) {
for(register int j=1;j<=n;j++) {
if(map[i][j]!=-1) map[i][j]=++tot;
}
}
/* for(register int i=1;i<=n;i++) {
for(register int j=1;j<=n;j++) {
if(map[i][j]!=-1) {
int u=map[i][j];
// printf("%d %d
",i,j);
for(register int k=0;k<8;k++) {
int x=i+move[k][0],y=j+move[k][1];
// printf(">%d %d
",x,y);
if(x>=1&&x<=n&&y>=1&&y<=n&&map[x][y]!=-1) {
int v=map[x][y];
Dinic::addEdge(u,v,INF);
}
}
// puts("=========");
}
}
}*/
for(register int i=1;i<=n;i++) {
for(register int j=(i%2?1:2);j<=n;j+=2) {
if(map[i][j]!=-1){
X[++tx]=map[i][j];
for(register int k=0;k<8;k++) {
int x=i+move[k][0],y=j+move[k][1];
if(x>=1&&x<=n&&y>=1&&y<=n&&map[x][y]!=-1) {
int v=map[x][y];
Dinic::addEdge(X[tx],v,INF);
// printf("%d %d -> %d %d
",i,j,x,y);
}
}
}
}
}
for(register int i=1;i<=n;i++) {
for(register int j=(i%2?2:1);j<=n;j+=2) {
if(map[i][j]!=-1)
Y[++ty]=map[i][j];
}
}
// printf("%d %d
",tx,ty);
for(register int i=1;i<=tx;i++) {
Dinic::addEdge(tot+1,X[i],1);
// printf("%d %d")
}
for(register int i=1;i<=ty;i++) {
Dinic::addEdge(Y[i],tot+2,1);
}
int ans=n*n-Dinic::maxFlow(tot+1,tot+2)-tt;
printf("%d",ans);
return 0;
}
圆桌问题
题解:
两种解法:一种是一个显而易见的贪心,把桌子和单位排序,然后顺次满足就行。
网络流做法:我们从每个单位向每张桌子连一条容量为1的边,每个桌子向汇点连容量为桌子容量的边,源点向每个单位连人数的边,然后跑最大流。如果满流,说明有解,按边的信息输出就行。
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
const int maxn=5000;
namespace Dinic {
struct Edge{
int u,v,cap,flow;
Edge(int u,int v,int cap,int flow):u(u),v(v),cap(cap),flow(flow){
}
};
std::vector<Edge> edges;
std::vector<int> G[maxn];
int n,s,t;
int d[maxn];
bool vis[maxn];
int cur[maxn];
void addEdge(int u,int v,int cap) {
edges.push_back(Edge(u,v,cap,0));
edges.push_back(Edge(v,u,0,0));
int m=edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
bool BFS() {
std::queue<int> Q;
memset(vis,0,sizeof(vis));
memset(d,0,sizeof(d));
Q.push(s);
d[s]=0;
vis[s]=1;
while(!Q.empty()) {
int u=Q.front();
Q.pop();
for(register int i=0;i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(!vis[e.v]&&e.cap>e.flow) {
d[e.v]=d[u]+1;
Q.push(e.v);
vis[e.v]=1;
}
}
}
return vis[t];
}
int DFS(int u,int a) {
if(u==t||a==0) return a;
int flow=0,f;
for(register int &i=cur[u];i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(d[e.v]==d[u]+1&&(f=DFS(e.v,std::min(a,e.cap-e.flow)))>0) {
e.flow+=f;
a-=f;
edges[G[u][i]^1].flow-=f;
flow+=f;
if(a==0) break;
}
}
return flow;
}
int maxFlow(int s,int t) {
Dinic::s=s;
Dinic::t=t;
int ans=0;
while(BFS()) {
memset(cur,0,sizeof(cur));
ans+=DFS(s,t);
}
return ans;
}
}
const int maxd=300,maxz=300;
int d,z,a,tt=0;
int X[maxd],Y[maxz];
std::vector<int> F[maxz];
int main() {
scanf("%d%d",&d,&z);
for(register int i=1;i<=d;i++) {
for(register int j=1;j<=z;j++) {
Dinic::addEdge(i,d+j,1);
}
}
for(register int i=1;i<=d;i++) {
scanf("%d",&a);
tt+=a;
Dinic::addEdge(d+z+1,i,a);
}
for(register int i=1;i<=z;i++) {
scanf("%d",&a);
Dinic::addEdge(i+d,d+z+2,a);
}
int pp=Dinic::maxFlow(d+z+1,d+z+2);
if(pp!=tt) {
puts("0");
// printf("%d %d",pp,tt);
return 0;
} else {
puts("1");
for(register int i=1;i<=d;i++) {
for(register int j=0;j<Dinic::G[i].size();j++) {
Dinic::Edge& e = Dinic::edges[Dinic::G[i][j]];
if(e.cap==1) {
if(e.flow==1) {
F[i].push_back(e.v-d);
}
}
}
}
for(register int i=1;i<=d;i++) {
for(register int j=0;j<F[i].size();j++) {
printf("%d ",F[i][j]);
}
puts("");
}
}
return 0;
}
负载平衡问题
题解:
水题。可以断环为链然后倍长一下,直接dp.
网络流做法:对多出来的点从源点连边,少的从汇点连边,跑最小费用最大流.
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
namespace MCMF {
const int maxn=10000,INF=10000000;
struct Edge {
int from,to,cap,cost,flow;
Edge(int f,int t,int d,int c):from(f),to(t),cap(d),cost(c){
flow=0;
}
};
int n,m,s,t;
std::vector<Edge> edges;
std::vector<int> G[maxn];
bool inq[maxn];
int d[maxn];
int p[maxn];
int a[maxn];
void init(int x) {
n=x;
for(register int i=0;i<=n;i++) {
G[i].clear();
}
edges.clear();
}
void addEdge(int from,int to,int cap,int cost) {
edges.push_back(Edge(from,to,cap,cost));
edges.push_back(Edge(to,from,0,-cost));
int m=edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BellmanFord(int s,int t,int &flow,int &cost) {
for(register int i=0;i<=n+1;i++) d[i]=INF;
memset(inq,0,sizeof(inq));
d[s]=0;
inq[s]=1;
p[s]=0;
a[s]=INF;
std::queue<int> Q;
Q.push(s);
while(!Q.empty()) {
int u=Q.front();Q.pop();
inq[u]=0;
for(register int i=0;i<G[u].size();++i) {
Edge& e=edges[G[u][i]];
if(e.cap>e.flow&&d[e.to]>d[u]+e.cost) {
d[e.to]=d[u]+e.cost;
p[e.to]=G[u][i];
a[e.to]=std::min(a[u],e.cap-e.flow);
if(!inq[e.to]) {
Q.push(e.to);
inq[e.to]=1;
}
}
}
}
if(d[t]==INF) return false;
flow+=a[t];
cost+=d[t]*a[t];
int u=t;
while(u!=s) {
edges[p[u]].flow+=a[t];
edges[p[u]^1].flow-=a[t];
u=edges[p[u]].from;
}
return true;
}
int minCost(int s,int t,int& flow,int& cost) {
flow=0,cost=0;
while(BellmanFord(s,t,flow,cost));
return cost;
}
void debug() {
for(register int i=0;i<=n+1;i++) {
for(register int j=0;j<G[i].size();j++) {
Edge&e = edges[G[i][j]];
printf("%d %d %d %d %d
",e.from,e.to,e.cap,e.flow,e.cost);
}
}
}
}
const int maxn=200,INF=1000000;
int pj=0,n,m,s,t,tot=0,a,b,c,f,hu[maxn];
std::vector<int> X,Y;
int main(){
scanf("%d",&n);
MCMF::init(n+1);
while(n--) {
tot++;
scanf("%d",&a);
pj+=a;
// printf("%d
",pj);
hu[tot]=a;
}
// printf("%d
",pj);
pj/=tot;
for(register int i=1;i<=tot;i++) {
if(hu[i]>=pj) {
X.push_back(i);
} else Y.push_back(i);
}
for(register int i=0;i<X.size();i++) {
for(register int j=0;j<Y.size();j++) {
MCMF::addEdge(X[i],Y[j],INF,std::min(std::abs(X[i]-Y[j]),std::abs(tot-std::abs(X[i]-Y[j]))));
}
MCMF::addEdge(0,X[i],hu[X[i]]-pj,0);
}
for(register int i=0;i<Y.size();i++) {
MCMF::addEdge(Y[i],tot+1,pj-hu[Y[i]],0);
}
// printf("%d
",pj);
// MCMF::debug();
int cost,flow;
MCMF::minCost(0,tot+1,flow,cost);
printf("%d",cost);
return 0;
}
最长不下降子序列问题
题解:
第一问用dp求解,(f[i])表示以(i)结尾的LIS的长度.第二问对所有的(f[u]=f[v]+1 且 a[u]>=a[v],u>v) 从v向u连边.有使用次数限制,对每个点拆点,控制节点流量,对所有的f[u]=1 , S向u连容量为1的边,对所有的f[u]=s,从u向T连容量为1的边。第三问,把只和a1,an,S,T有关的边容量设为无穷大,跑最大流即可。
小技巧: 第三问直接在第二问的残量网络上尝试增广S-T即可,不必清空整张图。
#include<bits/stdc++.h>
#define inf 1000000007
#define N 2000005
#define M 505
using namespace std;
struct Edge{
int u,v,next,f;
}G[N];
int head[N],tot=0,a[M],dp[M],n,len,s,t,ans;
void addedge(int u,int v,int f){
G[tot].u=u;G[tot].v=v;G[tot].f=f;G[tot].next=head[u];head[u]=tot++;
G[tot].u=v;G[tot].v=u;G[tot].f=0;G[tot].next=head[v];head[v]=tot++;
}
int level[100*M];
bool bfs(int s,int t){
memset(level,0,sizeof(level));
queue<int>q;q.push(s);level[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
if(u==t)return 1;
for(int i=head[u];i!=-1;i=G[i].next){
int v=G[i].v,f=G[i].f;
if(level[v]==0&&f)q.push(v),level[v]=level[u]+1;
}
}
return 0;
}
int dfs(int u,int maxf,int t){
if (u==t)return maxf;
int rat=0;
for (int i=head[u];i!=-1&&rat<maxf;i=G[i].next){
int v=G[i].v;int f=G[i].f;
if (level[v]==level[u]+1&&f){
int Min=min(maxf-rat,f);
f=dfs(v,Min,t);
G[i].f-=f;G[i^1].f+=f;rat+=f;
}
}
if (!rat)level[u]=N;
return rat;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),dp[i]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(a[j]<=a[i])dp[i]=max(dp[i],dp[j]+1);
for(int i=1;i<=n;i++)len=max(len,dp[i]);
printf("%d
",len);
s=0;t=5000;
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)if(dp[i]==1)addedge(s,i,1);
for(int i=1;i<=n;i++)if(dp[i]==len)addedge(i+n,t,1);
for(int i=1;i<=n;i++)addedge(i,i+n,1);
for(int i=1;i<=n;i++)
for(int j=1;j<i;j++)
if(a[j]<=a[i]&&dp[j]+1==dp[i])addedge(j+n,i,1);
while(bfs(s,t))ans+=dfs(s,inf,t);printf("%d
",ans);
addedge(1,1+n,inf);addedge(s,1,inf);
if(dp[n]==len)addedge(n,n*2,inf),addedge(n*2,t,inf);
while(bfs(s,t))ans+=dfs(s,inf,t);
printf("%d
",ans);
return 0;
}