网络流算是在OI中一个博大精深的问题了。使用它解题的关键就是知道如何建图。网络流24题就是其中建图比较典型的题目了。下面我将按照我刷题的顺序写下每道题的解题报告。
( u , v , f ) (u,v,f) (u,v,f) 一条从 u u u 到 v v v,流量为 f f f 的边。
( u , v , f , c ) (u,v,f,c) (u,v,f,c) 一条从 u u u 到 v v v,流量为 f f f,费用为 c c c 的边。
题意:有 m + n m+n m+n 个飞行员,其中 m m m 个为英国皇家飞行员, n n n 个为外籍飞行员。已知有若干对皇家飞行员和外籍飞行员可以配对。求最多可以配成多少对,并输出方案。 1 ≤ n , m ≤ 100 1 \leq n,m \leq 100 1≤n,m≤100。
题解:二分图最大匹配模板。但是由于是网络流24题,所以我这里写的是网络流的方法。
超级源点 S S S 与 1 1 1 至 m m m 连流量为 1 1 1 的边, m + 1 m+1 m+1 至 n n n 与超级汇点 T T T 连流量为 1 1 1 的边,中间连题目中所给的边,流量为 + ∞ +\infty +∞。
至于输出方案,只要检查对于中读入的边,流过去的流量是否非零,即反向边的容量非零即可。
代码:
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n,m;
int ecnt=1,head[100005],dep[100005];
struct edge{
int to,next,cap;
} e[100005];
bool b[100005];
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].next=head[u];head[u]=ecnt;
}
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int now=q.front();q.pop();
for(int i=head[now];i;i=e[i].next){
int v=e[i].to;
if(dep[v]==-1&&e[i].cap){
dep[v]=dep[now]+1;
q.push(v);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int w,ret=0;
for(int i=head[x];i;i=e[i].next){
int v=e[i].to;
if(dep[v]==dep[x]+1&&e[i].cap){
w=dfs(v,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
if(!ret) dep[x]=-1;
return ret;
}
int dinic(int s,int t){
int tot=0;
while(bfs(s,t))
tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int main(){//源点0,汇点n+1
scanf("%d%d",&m,&n);
while(1){
int x,y;scanf("%d%d",&x,&y);
if(x==-1&&y==-1) break;
addedge(x,y,0x3f3f3f3f);//建中间的边
addedge(y,x,0);
}
for(int i=1;i<=m;i++) addedge(0,i,1),addedge(i,0,0);//建源点连向1~m的边
for(int i=m+1;i<=n;i++) addedge(i,n+1,1),addedge(n+1,i,0);//建m+1~n连向汇点的边
int tot=dinic(0,n+1);
if(tot==0){
puts("No Solution!");
return 0;
}
printf("%d\n",tot);
for(int i=2;i<=ecnt;i=i+2){
if(e[i].to!=n+1&&e[i^1].to!=0)
if(e[i].to!=n+1&&e[i^1].to!=n+1)//判断是否为中间的边
if(e[i^1].cap!=0){//如果反向边容量不为0
printf("%d %d\n",e[i^1].to,e[i].to);
}
}
return 0;
}
题意:有一个公司要应对软件中的 n n n 个 b u g bug bug ,有 m m m 个修复 b u g bug bug 的程序,对于每个程序有四个集合 b i , 1 , b i , 2 , f i , 1 , f i , 2 b_{i,1},b_{i,2},f_{i,1},f_{i,2} bi,1,bi,2,fi,1,fi,2,如果当前 b u g bug bug 的集合包含 b i , 1 b_{i,1} bi,1 中的所有 b u g bug bug 并且不包含 b i , 2 b_{i,2} bi,2 中的所有 b u g bug bug 的时候,使用这个程序可以花费 t i t_i ti 的时间修复 f i , 1 f_{i,1} fi,1 中的所有 b u g bug bug,而又会新增添 f 2 , i f_{2,i} f2,i 中的所有 b u g bug bug。求修复所有 b u g bug bug 需要的最小时间。 1 ≤ n ≤ 20 1 \leq n \leq 20 1≤n≤20, 1 ≤ m ≤ 100 1 \leq m \leq 100 1≤m≤100。
题解:这道题不是网络流啊!
状压 d p dp dp, d p i dp_i dpi 表示当 b u g bug bug 集合为 i i i 的时候所需要的时间,最短路转移,就可以过了。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n,m,a[105],dp[1<<21],b1[105],b2[105],f1[105],f2[105];
bool legal(int s,int i){//判断状态为s的时候是否可以使用程序i
if((b1[i]|s)==s&&!(b2[i]&s)) return true;
return false;
}
void spfa(){
queue<int> q;
q.push((1<<n)-1);
dp[(1<<n)-1]=0;
while(!q.empty()){
int s=q.front();
q.pop();
for(int i=1;i<=m;i++)
if(legal(s,i)){
int t=(s&(~f1[i]))|f2[i];
if(dp[s]+a[i]<dp[t]){
dp[t]=dp[s]+a[i];//最短路转移
q.push(t);
}
}
}
}
int main(){
memset(dp,1,sizeof(dp));
cin>>n>>m;
for(int i=1;i<=m&&cin>>a[i];i++){
char ch;
for(int j=1;j<=n;j++){
char ch;cin>>ch;
if(ch=='+')
b1[i]+=1<<j-1;
else if(ch=='-')
b2[i]+=1<<(j-1);
}
for(int j=1;j<=n;j++){
char ch;cin>>ch;
if(ch=='-')
f1[i]+=1<<(j-1);
else if(ch=='+')
f2[i]+=1<<(j-1);
}
}
spfa();
cout<<(dp[0]==0x01010101?0:dp[0])<<endl;
return 0;
}
题意:有一个餐厅在 n n n 天中第 i i i 天要用 r i r_i ri 块餐巾。餐厅可以购买新的餐巾,每块餐巾的费用为 p p p 分,或者把用过的餐巾送到快洗部,洗一块需 m m m 天,费用为 f f f 分,或者送到慢洗部,洗一块需 l l l 天,费用为 s s s 分。 1 ≤ n ≤ 2000 1 \leq n \leq 2000 1≤n≤2000。
题解:费用流的经典题,建图较为恶心。
看了洛谷的题解
我们将每一天拆成早上和晚上两个点,每天晚上会受到用过的餐巾,每天早上又会受到干净的餐巾。
按以下方式建图:
然后跑最小费用最大流。
这里用到了一个最小(大)费用最大流的一个重要思想,最大流保证构造满足题目条件(如本题中每一天早上向汇点连边,流满表示够用,如果不流满就不合法了),最小(大)费用保证代价最小。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
#define int long long
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[100005];
struct edge{
int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int n,m,t1,t2,m1,m2;
signed main(){//源点0,汇点2n+1
scanf("%lld",&n);
int st=0,ed=2*n+1;
for(int i=1;i<=n;i++){
int x;scanf("%lld",&x);
addedge(st,i,x,0);addedge(i,st,0,0);//条件1
addedge(i+n,ed,x,0);addedge(ed,i+n,0,0);//条件2
}
scanf("%lld%lld%lld%lld%lld",&m,&t1,&m1,&t2,&m2);
for(int i=1;i<=n;i++){
if(i+1<=n) addedge(i,i+1,1e9,0),addedge(i+1,i,0,0); //条件3
if(i+t1<=n) addedge(i,i+n+t1,1e9,m1),addedge(i+n+t1,i,0,-m1);//条件4
if(i+t2<=n) addedge(i,i+n+t2,1e9,m2),addedge(i+n+t2,i,0,-m2);//条件5
addedge(st,i+n,1e9,m);addedge(i+n,st,0,-m);//条件6
}
cout<<Dinic(st,ed)<<endl;
return 0;
}
题意:有一张网格图,左下角 ( 0 , 0 ) (0,0) (0,0),右上角 ( Q , P ) (Q,P) (Q,P),有 a a a 个出发位置,对于每一个位置给出三个数 k , x , y k,x,y k,x,y,表示有 k k k 个机器人从 ( x , y ) (x,y) (x,y) 出发。有 b b b 个终点,也给出三个数 k , x , y k,x,y k,x,y,表示有 k k k 个机器人要到 ( x , y ) (x,y) (x,y)。每个机器人可以向上或向右走到终点。每条向右或向上的路径上都有一个生物标本,采集一个生物标本可以获得一些价值。一条路上如果生物标本已被采集过就没有了。求总价值的最大值。
题解:费用流的气息很明显。
建出图来,从源点连向每一个起点连一条边 ( S , i , k , 0 ) (S,i,k,0) (S,i,k,0),再从每一个终点连向汇点连一条边 ( i , T , k , 0 ) (i,T,k,0) (i,T,k,0)。对于每一条网格图中的道路 ( x , y , z ) (x,y,z) (x,y,z),连两条边 ( x , y , 1 , z ) (x,y,1,z) (x,y,1,z) 与 ( x , y , ∞ , 0 ) (x,y,\infty,0) (x,y,∞,0),因为道路可以通过多次,但标本只能收集一次。然后跑最大费用最大流。这里有一个技巧,将每条边的价值都取相反数,这样就变成了一个最小费用最大流,然后答案再取相反数就行了。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
#define int long long//开long long
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[100005];
struct edge{
int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
e[++ecnt].to=u;e[ecnt].cap=0;e[ecnt].cost=-c;e[ecnt].nxt=head[v];head[v]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int a=read(),b=read(),n=read(),m=read();
inline int id(int x,int y){
return (m+1)*x+y+1;
}
signed main(){//源点1234,汇点5678(不要问我为什么)
fz(i,0,n){
fz(j,0,m-1){
int num=read();
addedge(id(i,j),id(i,j+1),1,-num);
addedge(id(i,j),id(i,j+1),0x3f3f3f3f,0);//向每个点东边节点连边
}
}
fz(i,0,m){
fz(j,0,n-1){
int num=read();
addedge(id(j,i),id(j+1,i),1,-num);
addedge(id(j,i),id(j+1,i),0x3f3f3f3f,0);//向每个点南边节点连边
}
}
fz(i,1,a){
int k=read(),x=read(),y=read();
addedge(1234,id(x,y),k,0);//从源点向每个起点连边
}
fz(i,1,b){
int k=read(),x=read(),y=read();
addedge(id(x,y),5678,k,0);//从每个终点向汇点连边
}
cout<<-Dinic(1234,5678)<<endl;
return 0;
}
题意:有一个 n × m n \times m n×m 的矩阵,你可以从中选取一些元素使得任意两个数都不相邻,求和的最大值。
解法:网络流之最小割
我们考虑先选中所有方格,再想办法删去权值和尽量小的一批方格。
我们可以建一张图,节点表示矩阵中每一个元素,两节点之间有一条边表示这两个点相邻。我们不难发现,当把矩阵进行黑白间隔染色,相邻两点一定是一黑一白,因此我们构造出的矩阵一定是一个二分图。
那么思路就出来了。我们建一个虚拟源点 S S S,和虚拟汇点 T T T,我们对于所有白色格子上的点,连一条从 S S S 到这个点的边,流量为点权,删掉这条边,就表明不选这个点。同理,对于所有黑色格子上的点,连一条从这个点到 T T T 的边,流量为点权,删掉这条边也表明不选这个点。而二分图内部连着互斥的点,边权为 i n f inf inf (即删掉这条边没有意义),那么我们要求的就是这张图的最小割,根据最大流 = = = 最小割可以通过跑一次最大流求出答案。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read(),m=read();
int dx[]={1,0,-1,0};
int dy[]={0,1,0,-1};
inline int id(int x,int y){
return (x-1)*m+y;
}
int head[100005];
struct edge{
int to,nxt,cap;
} e[100005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
int sum=0;
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int main(){//源点0,汇点nm+1
fz(i,1,n) fz(j,1,m){
int val=read();
sum+=val;
if((i+j)%2){
for(int k=0;k<4;k++){
int x=i+dx[k],y=j+dy[k];
if(x<1||x>n||y<1||y>m) continue;
addedge(id(i,j),id(x,y),0x3f3f3f3f);
addedge(id(x,y),id(i,j),0);//向周围点连边
}
addedge(0,id(i,j),val);//源点向每个点连边
addedge(id(i,j),0,0);
}
else{
addedge(id(i,j),n*m+1,val);//每个点向汇点连边
addedge(n*m+1,id(i,j),0);
}
}
cout<<sum-Dinic(0,n*m+1)<<endl;//记得用总数减
return 0;
}
题意:有 m m m 个仓库和 n n n 个零售商店。第 i i i 个仓库有 a i a_i ai个单位的货物,第 j j j 个零售商店需要 b j b_j bj 个单位的货物。从第 i i i 个货物运往 第 j j j 个零售店需要 c i , j c_{i,j} ci,j 的代价。求将仓库中所有货物运送到零售商店的代价的最小值和最大值。
题解:算比较简单的费用流的题。
从源点 S S S 向每个仓库 i i i 连一条边 ( S , i , a i , 0 ) (S,i,a_i,0) (S,i,ai,0),从每个零售商店 i i i 向汇点 T T T 连一条边 ( i , T , b i , 0 ) (i,T,b_i,0) (i,T,bi,0)。仓库 i i i 和零售商店 j j j 之间连一条边 ( i , j , + ∞ , c i , j ) (i,j,+\infty,c_{i,j}) (i,j,+∞,ci,j),然后跑最小(大)费用最大流即可。
这题的思想与【餐巾计划问题】相同,都是最大流保证合法性,最小(大)费用保证代价最小(大)。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[100005];
struct edge{
int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int n=read(),m=read(),a[105],b[105],c[105][105];
int main(){//源点0,汇点n+m+1
fz(i,1,n){
a[i]=read();
addedge(0,i,a[i],0);//从源点向每个仓库连边
addedge(i,0,0,0);
}
fz(i,1,m){
b[i]=read();
addedge(i+n,n+m+1,b[i],0);//从每个零售商店向汇点连边
addedge(n+m+1,i+n,0,0);
}
fz(i,1,n){
fz(j,1,m){
c[i][j]=read();
addedge(i,j+n,0x3f3f3f3f,c[i][j]);//从每个仓库向每个零售商店连边
addedge(j+n,i,0,-c[i][j]);
}
}
cout<<Dinic(0,n+m+1)<<endl;
ecnt=1;//清除整张图
memset(head,0,sizeof(head));
fz(i,1,n){
addedge(0,i,a[i],0);
addedge(i,0,0,0);
}
fz(i,1,m){
addedge(i+n,n+m+1,b[i],0);
addedge(n+m+1,i+n,0,0);
}
fz(i,1,n){
fz(j,1,m){
addedge(i,j+n,0x3f3f3f3f,-c[i][j]);
addedge(j+n,i,0,c[i][j]);
}
}
cout<<-Dinic(0,n+m+1)<<endl;
return 0;
}
题意:在一个 n × n n \times n n×n 个方格的国际象棋棋盘上,骑士可以攻击的棋盘日字形的方格。棋盘上某些方格设置了障碍,骑士不得进入。求最多能在棋盘上放置多少个骑士。
题解:与方格取数问题类似,只不过增添了一个障碍设置环节,特判一下就行了。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int head[1000005];
struct edge{
int to,nxt,cap;
} e[1000005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[1000005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
int sum=0;
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int dx[]={-1,-2,-2,-1,1,2,2,1};
int dy[]={2,1,-1,-2,2,1,-1,-2};
int n=read(),k=read();
bool vis[205][205];
inline int id(int i,int j){
return (i-1)*n+j;
}
int main(){//源点0,汇点n*n+1
fz(i,1,k){
int x=read(),y=read();
vis[x][y]=1;
}
fz(i,1,n){
fz(j,1,n){
if(vis[i][j]) continue;
if((i+j)%2){
for(int s=0;s<8;s++){
int x=i+dx[s],y=j+dy[s];
if(x<1||x>n||y<1||y>n) continue;
if(vis[x][y]) continue;
addedge(id(i,j),id(x,y),0x3f3f3f3f);
addedge(id(x,y),id(i,j),0);
// cout<
}
addedge(0,id(i,j),1);
addedge(id(i,j),0,0);
}
else{
addedge(id(i,j),n*n+1,1);
addedge(n*n+1,id(i,j),0);
}
}
}
cout<<n*n-k-Dinic(0,n*n+1)<<endl;//总数减
return 0;
}
题意:有 n n n 件工作要分配给 n n n 个人做。第 i i i 个人做第 j j j 件工作产生的效益为 c i , j c_{i,j} ci,j 。求总效益的最大值。
题解:与运输问题本质上是一致的,只不过把最小值改成了最大值。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read();
int ecnt=1,head[100005];
struct edge{
int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[100005];
int dist[100005],flow[100005],pre[100005],pos[100005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int x[105][105];
int main(){//源点0,汇点2n+1
fz(i,1,n){
fz(j,1,n){
x[i][j]=read();
addedge(i,j+n,0x3f3f3f3f,x[i][j]);
addedge(j+n,i,0,-x[i][j]);
}
addedge(0,i,1,0);
addedge(i,0,0,0);
addedge(i+n,2*n+1,1,0);
addedge(2*n+1,i+n,0,0);
}
cout<<Dinic(0,2*n+1)<<endl;
ecnt=1;
memset(head,0,sizeof(head));
fz(i,1,n){
fz(j,1,n){
addedge(i,j+n,0x3f3f3f3f,-x[i][j]);
addedge(j+n,i,0,x[i][j]);
}
addedge(0,i,1,0);
addedge(i,0,0,0);
addedge(i+n,2*n+1,1,0);
addedge(2*n+1,i+n,0,0);
}
cout<<-Dinic(0,2*n+1)<<endl;
return 0;
}
题意:有 k k k 个人要从地球飞到月球。在太空中有 n n n 个太空站, m m m 个宇宙飞船在其中穿梭。这些宇宙飞船都周期性地停靠,即,在时刻 0 0 0 停靠 s 1 s_1 s1,时刻 1 1 1 停靠 s 2 s_2 s2,……,时刻 r − 1 r-1 r−1 停靠 s r s_r sr,时刻 r r r 停靠 s 1 s_1 s1,……。( − 1 -1 −1 表示月球, 0 0 0 表示地球)。当有宇宙飞船停靠在当前空间站的时候,可以上这个宇宙飞船。每个人可以在任何时候下宇宙飞船。每个人可以在空间站内停留任意时间。求将这 k k k 个人全部转移到月球的最小时间。无解输出 0 0 0。
题解:对于每一时刻,我们都建立 n + 2 n+2 n+2 个节点,表示所有的太空站,和地球月球。
之后对于每一时刻,如果此时有一艘太空船正从 x x x 往 y y y 去,那么,我们从上一时刻的 x x x 到这一时刻的 y y y 连一条流量为该太空船的容量的边。对于每一时刻,从前一时刻的每个星球向这一时刻的每个星球连一条边权为 + ∞ +\infty +∞ 的边(人可以停留在空间站嘛)。然后跑最大流。如果发现某一时刻最大流超过 k k k 就输出。对于无解的情况,就循环到一个较大的数,如果还不行就输出 0 0 0。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read(),m=read(),k=read();
int head[100005];
struct edge{
int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
int sum=0;
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int s[100005],len[100005];
int cy[105][1005];
inline int id(int x,int y){//第x时刻的空间站y的编号
return x*(n+2)+y;
}
int main(){//源点50000,汇点50001
fz(i,1,m){
s[i]=read();len[i]=read();
fz(j,0,len[i]-1){
cy[i][j]=read();
cy[i][j]+=2;//可能出现负数
}
}
int sum=0;
fz(i,0,571){//一个小插曲:这里的571是一个非常吉利的质数,拿着个数作hashbase基本不会被卡
addedge(50000,id(i,2),0x3f3f3f3f);//源点向地球
addedge(id(i,2),50000,0);
addedge(id(i,1),50001,0x3f3f3f3f);//月球向汇点
addedge(50001,id(i,1),0);
if(i==0) continue;
fz(j,1,n+2){
addedge(id(i-1,j),id(i,j),0x3f3f3f3f);//上一个时刻向这一个时刻
addedge(id(i,j),id(i-1,j),0);
// cout<
}
fz(j,1,m){
addedge(id(i-1,cy[j][(i-1)%len[j]]),id(i,cy[j][i%len[j]]),s[j]);//每个宇宙飞船上一个停靠的位置向这一个停靠的位置
addedge(id(i,cy[j][i%len[j]]),id(i-1,cy[j][(i-1)%len[j]]),0);
// cout<
}
int x=Dinic(50000,50001);
sum+=x;
if(sum>=k){//最大流超过k
cout<<i<<endl;
return 0;
}
// cout<
}
puts("0");//无解啦!
return 0;
}
题意:有 n n n 个仪器,安装第 i i i 个需要花费 w i w_i wi 元。有 n n n 个实验,进行第 i i i 个需要若干个实验仪器,可以获得 p i p_i pi 的利益。求最大利润并输出方案。
题解:我还是太蒻了,一碰到“费用”这种东西就被带偏了,光想着怎么建费用流,虽然思路基本正确,但是本题是无法用费用流解决的。
最大流之最小割
我们考虑建图:
然后跑最小割即最大流就是答案。
接下来考虑输出方案:考虑枚举删除器材和汇点 T T T 之间的边,如果删去后的最大流和原来的最大流的差值等于这条边的边权,那么这条边就是必须满流的,也就是这个器材是必要的。再根据需要的器材我们容易知道要做的实验有哪些。
/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
bool ___=1;
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read(),m=read();
int head[1005];
struct edge{
int to,nxt,cap;
inline void clear(){
to=nxt=cap=0;
}
} e[30005],ee[30005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
// cout<<2<
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
// cout<
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
// cout<
if(dep[to]==-1&&e[i].cap>0){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
// cout<
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
int sum=0;
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
vector<int> v[505];
int c[505],val[505];
bool s1[505],s2[505];
int main(){//源点0,汇点1000
int sum=0;
fz(i,1,n){
val[i]=read();
sum+=val[i];
addedge(0,i,val[i]);//源点向每个实验连边
addedge(i,0,0);
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
while(sscanf(tools+ulen,"%d",&tool)==1){
v[i].push_back(tool);
addedge(i,tool+n,0x3f3f3f3f);//每个实验向需要的器材连边
addedge(tool+n,i,0);
if(tool==0)
ulen++;
else{
while(tool){
tool/=10;
ulen++;
}
}
ulen++;
}
}
fz(i,1,m){
c[i]=read();
addedge(i+n,1000,c[i]);//每个器材向汇点连边
addedge(1000,i+n,0);
}
memcpy(ee,e,sizeof(e));
int ans=sum-Dinic(0,1000);
for(int i=head[1000];i;i=e[i].nxt){
memcpy(e,ee,sizeof(ee));
edge tmp=e[i^1];
e[i^1].cap=0;//删边求出安装哪些器材
int t=sum-Dinic(0,1000);
// cout<
// cout<
if((t-ans-c[e[i].to-n])==0) s2[e[i].to-n]=1;
e[i^1]=tmp;
}
fz(i,1,n){
s1[i]=1;
for(int j=0;j<v[i].size();j++){
if(!s2[v[i][j]]) s1[i]=0;//根据器材反推实验
}
}
fz(i,1,n) if(s1[i]) cout<<i<<" ";
puts("");
fz(i,1,m) if(s2[i]) cout<<i<<" ";
puts("");
cout<<ans<<endl;
return 0;
}
题意:有 m m m 个单位聚餐,第 i i i 个单位有 d i d_i di 个人。有 n n n 个圆桌,第 i i i 个可以容纳 c i c_i ci 个人。保证 ∑ d i = ∑ c i \sum{d_i}=\sum{c_i} ∑di=∑ci。已知一个圆桌只能坐同一个单位的人。给出一个安排座位的方案,或宣布无解。
题解:暴力建图,不需要任何技巧,从源点向每个单位连 d i d_i di 的流量,从每个单位向每张桌子连 1 1 1 单位的流量,再从每张桌子向汇点连 c i c_i ci 的流量。如果(最大流等于所有单位总人数),则有解。输出方案的时候就检查内部拿些边流过去的流量为 1 1 1,即反向边的容量为 1 1 1 就可以了。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int head[100005];
struct edge{
int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int n=read(),m=read(),sum=0;
int main(){
fz(i,1,n){//源点0,汇点n+m+1
int x=read();sum+=x;
addedge(0,i,x);//从源点向每个单位连边
addedge(i,0,0);
}
fz(i,1,m){
int x=read();
addedge(i+n,n+m+1,x);//从每个圆桌向汇点连边
addedge(n+m+1,i+n,0);
}
fz(i,1,n){
fz(j,1,m){
addedge(i,j+n,1);//从每个单位向每个圆桌连边
addedge(j+n,i,0);
}
}
int g=Dinic(0,n+m+1);
if(g==sum){
cout<<"1\n";
fz(i,1,n){
for(int j=head[i];j;j=e[j].nxt){
if(e[j].to!=0&&e[j].cap==0){//输出方案
cout<<e[j].to-n<<" ";
}
}
puts("");
}
}
else puts("0");
return 0;
}
题意:一个试题库中有 n n n 道试题。每道试题都有一些所属类别。现要从题库中抽取 m m m 道题组成试卷。并要求试卷类型 i i i 必须恰好包含 t i t_i ti 题。设计一个满足要求的组卷算法。
题解:不难想到用试题与类型建立一个二分图。对于每一个试题 i i i 和所有它所属于的类型 j j j 之间连一条边 ( i , j , 1 ) (i,j,1) (i,j,1),对于每一个试题 i i i,连一条边 ( S , i , 1 ) (S,i,1) (S,i,1),对于每一个类型 j j j,连一条边 ( j , T , t i ) (j,T,t_i) (j,T,ti),然后跑最大流就是答案。输出方案和前面类似,这里就不再赘述了。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int head[100005];
struct edge{
int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int k=read(),n=read(),sum=0;
vector<int> ans[22];
int main(){//源点0,汇点n+k+1
fz(i,1,k){
int t=read();
addedge(i+n,n+k+1,t);
addedge(n+k+1,i+n,0);
sum+=t;
}
fz(i,1,n){
addedge(0,i,1);
addedge(i,0,0);
int p=read();
fz(j,1,p){
int x=read();
addedge(i,x+n,1);
addedge(x+n,i,1);
}
}
int num=Dinic(0,n+k+1);
if(num!=sum){
return puts("No Solution!"),0;
}
fz(i,1,n){
for(int j=head[i];j;j=e[j].nxt){
if(e[j].to!=0&&e[j].cap==0){
ans[e[j].to-n].push_back(i);
}
}
}
fz(i,1,k){
cout<<i<<": ";
for(int j=0;j<ans[i].size();j++){
cout<<ans[i][j]<<" ";
}
puts("");
}
return 0;
}
题意:给出一张 D A G DAG DAG,求出其最小路径覆盖(即最少需要多少条有向路径才能覆盖这张图里所有顶点)。并输出方案。
题解:我们先假设所有点都单独成一个路径,这显然不是最优解,因此我们要想着合并两条路径。
每个节点只能有一条出边,一条入边。如果我们将每个点拆成一个入点和一个出点,那么:
入点只能连向出点
每个入点只能连向一个出点
每个出点只能被一个入点连
这……不是二分图匹配吗?这样我们就回到了【飞行员配对问题】。于是我们就可以愉快的AC了。
/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int head[100005];
struct edge{
int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int n=read(),m=read();
struct DSU{
int fa[100005];
inline void init(){
fz(i,1,n) fa[i]=i;
}
inline int find(int x){
return (fa[x]==x)?x:fa[x]=find(fa[x]);
}
inline void merge(int x,int y){
int a=find(x),b=find(y);
if(a==b) return;
fa[a]=b;
}
} dsu;
vector<int> ans[155];
int main(){
fz(i,1,m){
int x=read(),y=read();
addedge(x*2,y*2-1,1);
addedge(y*2-1,x*2,0);
}
fz(i,1,n){
addedge(0,i*2,1);
addedge(i*2,0,0);
addedge(i*2-1,2*n+1,1);
addedge(2*n+1,i*2-1,0);
}
int anss=n-Dinic(0,2*n+1);
dsu.init();
fz(i,1,n){
for(int j=head[i*2];j;j=e[j].nxt){
if(e[j].to>0&&e[j].cap==0){
dsu.merge(i,(e[j].to+1)/2);
}
}
}
fz(i,1,n){
ans[dsu.find(i)].push_back(i);
}
fz(i,1,n){
if(dsu.find(i)==i){
for(int j=0;j<ans[i].size();j++) cout<<ans[i][j]<<" ";
puts("");
}
}
cout<<anss<<endl;
return 0;
}
题意:假设有 n n n 根柱子,现要按下述规则在这n根柱子中依次放入编号为 1 , 2 , 3 … 1,2,3\dots 1,2,3…的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。
题意:如果我们在和为平方数的点之间连边,那么“能够放在 n n n 根柱子上”,就转化为了“能够用 n n n 条有向路径覆盖”,这样就和上一道题相同了
/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read();
int head[100005];
struct edge{
int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
struct DSU{
int fa[100005];
inline void init(int t){
fz(i,1,t) fa[i]=i;
}
inline int find(int x){
return (fa[x]==x)?x:fa[x]=find(fa[x]);
}
inline void merge(int x,int y){
int a=find(x),b=find(y);
if(a==b) return;
fa[a]=b;
}
} dsu;
vector<int> ansv[155];
int main(){
int ans=0,sum=0;
while(1){
ans++;
for(int i=1;i<=100;i++){
if(i*i-ans>=1&&i*i-ans<ans){
// cout<
addedge((i*i-ans)*2,ans*2-1,1);
addedge(ans*2-1,(i*i-ans)*2,0);
}
}
addedge(0,ans*2,1);
addedge(ans*2,0,0);
addedge(ans*2-1,50000,1);
addedge(50000,ans*2-1,0);
sum+=Dinic(0,50000);
// cout<
if(ans-sum>n){
ans--;
break;
}
}
ecnt=1;
memset(head,0,sizeof(head));
fz(i,1,ans){
fz(j,1,100){
if(j*j-i>=1&&j*j-i<i){
// cout<
addedge((j*j-i)*2,i*2-1,1);
addedge(i*2-1,(j*j-i)*2,0);
}
}
addedge(0,i*2,1);
addedge(i*2,0,0);
addedge(i*2-1,50000,1);
addedge(50000,i*2-1,0);
}
Dinic(0,50000);
cout<<ans<<endl;
dsu.init(ans);
fz(i,1,ans){
for(int j=head[i*2];j;j=e[j].nxt){
if(e[j].to>0&&e[j].cap==0){
dsu.merge(i,(e[j].to+1)/2);
}
}
}
fz(i,1,ans){
ansv[dsu.find(i)].push_back(i);
}
fz(i,1,ans){
if(dsu.find(i)==i){
for(int j=0;j<ansv[i].size();j++) cout<<ansv[i][j]<<" ";
puts("");
}
}
return 0;
}
题意:给出长度为 n n n 的数组 a a a,求出:
题解:首先动态规划求出第一问。
考虑建图:
跑出来的最大流就是第而问的答案。
对于第三问,只需将源点到 1 , n 1,n 1,n 的入点, 1 , n 1,n 1,n 的出点到汇点, 1 , n 1,n 1,n 之间的入点和出点之间的边的容量改为 ∞ \infty ∞,然后跑最大流就是答案。
注意特判 l = 1 l=1 l=1。
/*
Êý¾Ý²»Çå¿Õ£¬±¬ÁãÁ½ÐÐÀá¡£
¶à²â²»¶ÁÍ꣬±¬ÁãÁ½ÐÐÀá¡£
±ß½ç²»ÌØÅУ¬±¬ÁãÁ½ÐÐÀá¡£
Ì°ÐIJ»Ö¤Ã÷£¬±¬ÁãÁ½ÐÐÀá¡£
D P ˳Ðò´í£¬±¬ÁãÁ½ÐÐÀá¡£
´óСÉٵȺţ¬±¬ÁãÁ½ÐÐÀá¡£
±äÁ¿²»Í³Ò»£¬±¬ÁãÁ½ÐÐÀá¡£
Ô½½ç²»Åжϣ¬±¬ÁãÁ½ÐÐÀá¡£
µ÷ÊÔ²»×¢ÊÍ£¬±¬ÁãÁ½ÐÐÀá¡£
Òç³ö²» l l£¬±¬ÁãÁ½ÐÐÀá¡£
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read(),a[505],dp[505],mx;
int head[100005];
struct edge{
int to,nxt,cap;
} e[300005];
int ecnt=1;
inline void addedge(int u,int v,int f){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].nxt=head[u];head[u]=ecnt;
}
int dep[100005];
inline bool bfs(int s,int t){
queue<int> q;
memset(dep,-1,sizeof(dep));
q.push(s);dep[s]=0;
while(!q.empty()){
int cur=q.front();q.pop();
for(int i=head[cur];i;i=e[i].nxt){
int to=e[i].to;
if(dep[to]==-1&&e[i].cap){
dep[to]=dep[cur]+1;
q.push(to);
}
}
}
if(dep[t]!=-1) return 1;
return 0;
}
inline int dfs(int x,int t,int f){
if(x==t) return f;
int ret=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(dep[y]==dep[x]+1&&e[i].cap){
int w=dfs(y,t,min(f-ret,e[i].cap));
e[i].cap-=w;
e[i^1].cap+=w;
ret+=w;
if(ret==f) return f;
}
}
return ret;
}
inline int Dinic(int s,int t){
int tot=0;
while(bfs(s,t)) tot+=dfs(s,t,0x3f3f3f3f);
return tot;
}
int main(){
fz(i,1,n) a[i]=read();
fz(i,1,n){
dp[i]=1;
for(int j=1;j<i;j++){
if(a[j]<=a[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
mx=max(mx,dp[i]);
}
cout<<mx<<endl;
if(mx==1){
cout<<n<<endl<<n<<endl;return 0;
}
fz(i,1,n){
if(dp[i]==1){
addedge(0,i*2-1,1);
addedge(i*2-1,0,0);
}
if(dp[i]==mx){
addedge(i*2,2*n+1,1);
addedge(2*n+1,i*2,0);
}
for(int j=i+1;j<=n;j++){
if(a[j]>=a[i]&&dp[j]==dp[i]+1){
addedge(i*2,j*2-1,1);
addedge(j*2-1,i*2,0);
}
}
addedge(i*2-1,i*2,1);
addedge(i*2,i*2-1,0);
}
cout<<Dinic(0,2*n+1)<<endl;
ecnt=1;memset(head,0,sizeof(head));
fz(i,1,n-1){
if(dp[i]==1){
addedge(0,i*2-1,1);
addedge(i*2-1,0,0);
}
if(dp[i]==mx){
addedge(i*2,2*n+1,1);
addedge(2*n+1,i*2,0);
}
for(int j=i+1;j<=n;j++){
if(a[j]>=a[i]&&dp[j]==dp[i]+1){
addedge(i*2,j*2-1,1);
addedge(j*2-1,i*2,0);
}
}
addedge(i*2-1,i*2,1);
addedge(i*2,i*2-1,0);
}
addedge(0,1,INT_MAX);
addedge(1,0,0);
addedge(1,2,INT_MAX);
addedge(2,1,0);
if(dp[n]==mx){
addedge(2*n-1,2*n,INT_MAX);
addedge(2*n,2*n-1,0);
addedge(2*n,2*n+1,INT_MAX);
addedge(2*n+1,2*n,0);
}
cout<<Dinic(0,2*n+1)<<endl;
return 0;
}
题意:有 n n n 个开区间 [ l i , r i l_i,r_i li,ri],你要选出一些开区间,使得任意一个点被覆盖的次数不超过 k k k。定义一个开区间 [ l , r l,r l,r] 的价值为 r − l r-l r−l,求出取出开区间价值的最大值。
题解:
设所有开区间中右端点最大值为 m x mx mx。
我们考虑建图。
然后跑最大费用最大流就行了,注意离散化。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[100005];
struct edge{
int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[6010];
int dist[6010],flow[6010],pre[6010],pos[6010];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int n=read(),k=read(),l[1005],r[1005],key[1005],cnt=0,hs[1005],cnt2=0;
int main(){
fz(i,1,n) l[i]=read(),r[i]=read(),key[++cnt]=l[i],key[++cnt]=r[i];
sort(key+1,key+cnt+1);
fz(i,1,cnt){
if(key[i]!=key[i-1]){
hs[++cnt2]=key[i];
}
}
fz(i,1,n){
int len=r[i]-l[i];
l[i]=lower_bound(hs+1,hs+cnt2+1,l[i])-hs;
r[i]=lower_bound(hs+1,hs+cnt2+1,r[i])-hs;
// cout<
addedge(l[i],r[i],1,-len);
addedge(r[i],l[i],0,len);
}
fz(i,1,cnt2-1){
addedge(i,i+1,0x3f3f3f3f,0);
addedge(i+1,i,0,0);
}
addedge(cnt2+1,1,k,0);
addedge(1,cnt2+1,0,0);
addedge(cnt2,cnt2+2,k,0);
addedge(cnt2+2,cnt2,0,0);
cout<<-Dinic(cnt2+1,cnt2+2)<<endl;
return 0;
}
题意:有 n n n 条开线段 ( x 1 , y 1 ) (x1,y1) (x1,y1) 到 ( x 2 , y 2 ) (x2,y2) (x2,y2)。你需要选择一些线段,使得对于任意 p p p,直线 x = p x=p x=p 与这些线段交点个数的总和不超过 k k k。定义一条线段的价值为这条线段的长度,求出选出线段价值总和的最大值。
题解:与上一题基本一致,只不过需要特判 x 1 = x 2 x1=x2 x1=x2 的情况。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
#define int long long
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[100005];
struct edge{
int to,nxt,cap,cost;
} e[100005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[10010];
int dist[10010],flow[10010],pre[10010],pos[10010];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int n=read(),k=read(),l[10010],r[10010],len[10010],key[10010],cnt=0,hs[1005],cnt2=0;
signed main(){
fz(i,1,n){
int x=read(),y=read(),xx=read(),yy=read();
len[i]=((int)(sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy))));
x*=2;xx*=2;
if(x==xx) l[i]=x,r[i]=x+1;
else l[i]=min(x,xx)+1,r[i]=max(x,xx);
}
key[0]=-0x3f3f3f3f;
fz(i,1,n) key[++cnt]=l[i],key[++cnt]=r[i];
sort(key+1,key+cnt+1);
fz(i,1,cnt){
if(key[i]!=key[i-1]){
hs[++cnt2]=key[i];
}
}
fz(i,1,n){
l[i]=lower_bound(hs+1,hs+cnt2+1,l[i])-hs;
r[i]=lower_bound(hs+1,hs+cnt2+1,r[i])-hs;
// cout<
addedge(l[i],r[i],1,-len[i]);
addedge(r[i],l[i],0,len[i]);
}
fz(i,1,cnt2-1){
addedge(i,i+1,0x3f3f3f3f,0);
addedge(i+1,i,0,0);
}
addedge(cnt2+1,1,k,0);
addedge(1,cnt2+1,0,0);
addedge(cnt2,cnt2+2,k,0);
addedge(cnt2+2,cnt2,0,0);
cout<<-Dinic(cnt2+1,cnt2+2)<<endl;
return 0;
}
题意:有一个 n × m n\times m n×m 的网格图,有些格子上有加油站,汽车初始位置在 ( 0 , 0 ) (0,0) (0,0)(左上角),要走到 ( n , m ) (n,m) (n,m)(右下角),每行驶一个需要消耗一格油,加满油 后行驶 k k k 格后就没油了。汽车向右或向下形式不需要代价,向上或向左需要 B B B 的代价。如果汽车行驶到一个加油站,那么 必须 花费 A A A 的代价加满油。否则可以花费 C C C 的代价建立加油站并加满油。求行驶到 ( n , m ) (n,m) (n,m) 的最小代价。
我们就可以按照剩余流量,分层建图。
令第 0 0 0 层为满油层,第 K K K 层为空油层。规定坐标 [ z , x , y ] [z,x,y] [z,x,y] 的意义为:第 z z z 层的 ( x , y ) (x,y) (x,y) 位置。
首先,对于一个加油站的位置 ( x , y ) (x,y) (x,y):
如果有 z ≠ 0 z \neq 0 z=0 ,连一条边 ( [ z , x , y ] , [ 0 , x , y ] , ∞ , A ) ([z,x,y],[0,x,y],\infty,A) ([z,x,y],[0,x,y],∞,A) 。
否则,即 z = 0 z=0 z=0 ,向下一层的邻居节点连边。
这时候就有人问了,到加油站不是强制加油吗,为什么第 0 0 0 层时却不用加油?
因为第 0 0 0 层的状态只有在刚加满油的时候才会出现。其它时候,当你从其他地方开进一个加油站时,一定不会在第 0 0 0 层。
然后,对于一个非加油站:
默认可以建油站,连一条边 ( [ z , x , y ] , [ 0 , x , y ] , I N F , A + C ) ([z,x,y],[0,x,y],INF,A+C) ([z,x,y],[0,x,y],INF,A+C) 。
那又有问题了,同一个节点,油站建一次就行了凭什么再来时还要再建?
因为我们的路径必然无环。有环的局面必然是向上或向左绕路去加油的,但已经修了加油站,就不会再想着去绕路了。
同时,如果 z ≠ K z \neq K z=K ,可以向下一层的邻居节点连边。
关于源点和汇点,初始状态必然只有 ( S , [ 0 , 0 , 0 ] , 1 , 0 ) (S,[0,0,0],1,0) (S,[0,0,0],1,0) 一种。
但是对于所有的 z ∈ [ 0 , K ] z\in [0,K] z∈[0,K] ,都可以有 ( [ z , n − 1 , n − 1 ] , T , 1 , 0 ) ([z,n-1,n-1],T,1,0) ([z,n−1,n−1],T,1,0) 。
所以图就建完了。答案即为最小费用最大流。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
#define int long long
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[1000005];
struct edge{
int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f3f3f3f3fll;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int g[105][105];
int n=read(),k=read(),a=read(),b=read(),c=read();
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
const int cst[]={0,0,b,b};
inline int id(int x,int y,int lv){
return n*n*lv+((x-1)*n+y-1);
}
signed main(){
fz(i,1,n) fz(j,1,n) cin>>g[i][j];
fz(i,1,n){
fz(j,1,n){
fz(l,0,k){
if(g[i][j]){
if(l!=0){
addedge(id(i,j,l),id(i,j,0),1,a);
addedge(id(i,j,0),id(i,j,l),0,-a);
}
}
else{
if(l!=0){
addedge(id(i,j,l),id(i,j,0),1,a+c);
addedge(id(i,j,0),id(i,j,l),0,-a-c);
}
}
if((!g[i][j]&&l!=k)||(g[i][j]&&l==0)){
for(int o=0;o<4;o++){
if(i+dx[o]<1||i+dx[o]>n||j+dy[o]<1||j+dy[o]>n) continue;
addedge(id(i,j,l),id(i+dx[o],j+dy[o],l+1),1,cst[o]);
addedge(id(i+dx[o],j+dy[o],l+1),id(i,j,l),0,-cst[o]);
}
}
}
}
}
addedge(300000,id(1,1,0),1,0);
addedge(id(1,1,0),300000,0,0);
fz(l,0,k){
addedge(id(n,n,l),300001,1,0);
addedge(300001,id(n,n,l),0,0);
}
// puts("a");
cout<<Dinic(300000,300001)<<endl;
return 0;
}
题意:给定一个由 n n n行数字组成的数字梯形如下图所示。梯形的第一行有 m m m个数字。从梯形的顶部的 m m m个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。
规则 1 1 1:从梯形的顶至底的 m m m条路径互不相交。
规则 2 2 2:从梯形的顶至底的 m m m条路径仅在数字结点处相交。
规则 3 3 3:从梯形的顶至底的 m m m条路径允许在数字结点相交或边相交。
求出在三种规则下所有路径经过的数的和的最大值
题解:拆点。将每个点的入点与出点之间连一条流量为 1 1 1,费用为这个点上的数字的边。在每个点的出点与它下面两个点的入点间连一条流量为 1 1 1,费用为 0 0 0 的边。对于上底的每个点,连一条从源点流向这个点的入点的边,流量为 1 1 1,费用为 0 0 0,对于下底的每个点,连一条从这个点的出点到汇点的边。跑最大流,就是第一问的答案。
将连向汇点的所有边,以及每个点入点和出点之间的边,流量都改为 ∞ \infty ∞,跑最大流就是第二问的答案。
将所有除了源点连向的边的流量都改为 ∞ \infty ∞,跑最大流就是第三问的答案。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int l=read(),r=read(),a[45][45];
int ecnt=1,head[1000005];
struct edge{
int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
inline int id(int i,int j){
int sum=0;
fz(t,1,i-1){
sum+=l+t-1;
}
return sum+j;
}
signed main(){
r+=l-1;
fz(i,1,r-l+1){
fz(j,1,l+i-1){
cin>>a[i][j];
}
}
fz(i,1,r-l+1){
fz(j,1,l+i-1){
addedge(2*id(i,j)-1,2*id(i,j),1,-a[i][j]);
addedge(2*id(i,j),2*id(i,j)-1,0,a[i][j]);
// cout<
if(i==r-l+1) continue;
addedge(2*id(i,j),2*id(i+1,j)-1,1,0);
addedge(2*id(i+1,j)-1,2*id(i,j),0,0);
addedge(2*id(i,j),2*id(i+1,j+1)-1,1,0);
addedge(2*id(i+1,j+1)-1,2*id(i,j),0,0);
}
// puts("");
}
fz(i,1,l) addedge(30000,id(1,i)*2-1,1,0),addedge(id(1,i)*2-1,30000,0,0);
fz(i,1,r) addedge(id(r-l+1,i)*2,30001,1,0),addedge(30001,id(r-l+1,i)*2,0,0);
cout<<-Dinic(30000,30001)<<endl;
ecnt=1;
memset(head,0,sizeof(head));
fz(i,1,r-l+1){
fz(j,1,l+i-1){
addedge(2*id(i,j)-1,2*id(i,j),0x3f3f3f3f,-a[i][j]);
addedge(2*id(i,j),2*id(i,j)-1,0,a[i][j]);
// cout<
if(i==r-l+1) continue;
addedge(2*id(i,j),2*id(i+1,j)-1,1,0);
addedge(2*id(i+1,j)-1,2*id(i,j),0,0);
addedge(2*id(i,j),2*id(i+1,j+1)-1,1,0);
addedge(2*id(i+1,j+1)-1,2*id(i,j),0,0);
}
// puts("");
}
fz(i,1,l) addedge(30000,id(1,i)*2-1,1,0),addedge(id(1,i)*2-1,30000,0,0);
fz(i,1,r) addedge(id(r-l+1,i)*2,30001,0x3f3f3f3f,0),addedge(30001,id(r-l+1,i)*2,0,0);
cout<<-Dinic(30000,30001)<<endl;
ecnt=1;
memset(head,0,sizeof(head));
fz(i,1,r-l+1){
fz(j,1,l+i-1){
addedge(2*id(i,j)-1,2*id(i,j),0x3f3f3f3f,-a[i][j]);
addedge(2*id(i,j),2*id(i,j)-1,0,a[i][j]);
// cout<
if(i==r-l+1) continue;
addedge(2*id(i,j),2*id(i+1,j)-1,0x3f3f3f3f,0);
addedge(2*id(i+1,j)-1,2*id(i,j),0,0);
addedge(2*id(i,j),2*id(i+1,j+1)-1,0x3f3f3f3f,0);
addedge(2*id(i+1,j+1)-1,2*id(i,j),0,0);
}
// puts("");
}
fz(i,1,l) addedge(30000,id(1,i)*2-1,1,0),addedge(id(1,i)*2-1,30000,0,0);
fz(i,1,r) addedge(id(r-l+1,i)*2,30001,0x3f3f3f3f,0),addedge(30001,id(r-l+1,i)*2,0,0);
cout<<-Dinic(30000,30001)<<endl;
return 0;
}
题意:有 n n n 火星探测器位于 ( 0 , 0 ) (0,0) (0,0),要走到 ( n , m ) (n,m) (n,m)。有一些点上有障碍物,还有一些有岩石标本。已知每块岩石标本只能被采集一次。岩石标本被采集后,其他探测车可以从原来岩石标本所在处通过。探测车不能通过有障碍的地面。本题限定探测车只能从登陆处沿着向南或向东的方向朝传送器移动,而且多个探测车可以在同一时间占据同一位置。如果某个探测车在到达传送器以前不能继续前进,则该车所采集的岩石标本将全部损失。求获得岩石标本个数的最大值
题解:拆点,然后老套路,在拆出的两个点之间连一条流量为 ∞ \infty ∞,费用为 0 0 0 的边。如果这个点是一块石头,再连一条边权为 1 1 1,费用为 1 1 1 的边。然后跑最大费用最大流。输出方案直接暴搜就行了
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int ecnt=1,head[1000005];
struct edge{
int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int n=read(),p=read(),q=read(),a[40][40];
int dx[]={1,0};
int dy[]={0,1};
int id(int i,int j){
return (i-1)*q+j;
}
char ans[100005];
int len=0,occ[1000005];
inline void dfs(int x,int y){
// cout<
for(int i=head[id(x,y)*2];i;i=e[i].nxt){
int z=e[i].to;
if(occ[i]>=e[i^1].cap) continue;
if(z==(id(x+1,y)*2-1)&&x+1<=p){
occ[i]++;
ans[++len]='0';
// cout<<0;
dfs(x+1,y);
return;
}
if(z==(id(x,y+1)*2-1)&&y+1<=q){
occ[i]++;
// cout<<1;
ans[++len]='1';
dfs(x,y+1);
return;
}
}
}
int main(){
swap(p,q);
fz(i,1,p) fz(j,1,q) a[i][j]=read();
fz(i,1,p){
fz(j,1,q){
if(a[i][j]==1) continue;
addedge(id(i,j)*2-1,id(i,j)*2,0x3f3f3f3f,0);
addedge(id(i,j)*2,id(i,j)*2-1,0,0);
// cout<
if(a[i][j]==2){
addedge(id(i,j)*2-1,id(i,j)*2,1,-1);
addedge(id(i,j)*2,id(i,j)*2-1,0,1);
}
}
}
fz(i,1,p){
fz(j,1,q){
if(a[i][j]==1) continue;
// cout<<"i="<
for(int l=0;l<2;l++){
int x=i+dx[l],y=j+dy[l];
if(x<1||x>p||y<1||y>q) continue;
if(a[x][y]==1) continue;
// cout<
addedge(id(i,j)*2,id(x,y)*2-1,0x3f3f3f3f,0);
addedge(id(x,y)*2-1,id(i,j)*2,0,0);
}
}
}
addedge(0,id(1,1)*2-1,n,0);
addedge(id(1,1)*2-1,0,0,0);
addedge(id(p,q)*2,2*p*q+1,n,0);
addedge(2*p*q+1,id(p,q)*2,0,0);
Dinic(0,2*p*q+1);
fz(i,1,n){
len=0;
dfs(1,1);
fz(j,1,len) cout<<i<<" "<<ans[j]<<endl;
}
return 0;
}
题意:有 n n n 个仓库形成一个环,第 i i i 个有 a i a_i ai 个库存。每次可以在相邻仓库之间搬运库存。现在要将每个仓库里的库存数量相同。求最少搬运量。
题解:我们不难发现,不管怎么搬来搬去,这 n n n 个仓库的库存总量一定是定值,也就是说,最后每个仓库的库存一定是 a a a 数组的平均值,假设为 g g g。
对于所有的 a i a_i ai,我们可以算出 a i − g a_i-g ai−g 的值。如果这个值为正,那么意味着这个仓库需要运一些到其他仓库,就连一条边 ( S , i , a i − g , 0 ) (S,i,a_i-g,0) (S,i,ai−g,0),否则,意味着这个仓库需要从别的仓库中获得一些库存,就连一条边 ( i , T , g − a i , 0 ) (i,T,g-a_i,0) (i,T,g−ai,0)。对于相邻两个仓库 i , j i,j i,j,由于它们可以无限制地运送库存,连一条边 ( i , j , ∞ , 1 ) (i,j,\infty,1) (i,j,∞,1)。
然后跑最小费用最大流就可以了。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read(),a[105];
int ecnt=1,head[1000005];
struct edge{
int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));
memset(dist,63,sizeof(dist));
queue<int> q;
vis[s]=0;
dist[s]=0;
flow[s]=0x3f3f3f3f;
q.push(s);
while(!q.empty()){
int x=q.front();
// cout<
vis[x]=true;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
// cout<
if(e[i].cap>0&&dist[y]>dist[x]+e[i].cost){
dist[y]=dist[x]+e[i].cost;pos[y]=x;pre[y]=i;
flow[y]=min(flow[x],e[i].cap);
// cout<
if(vis[y]){
q.push(y);
vis[y]=false;
}
}
}
q.pop();
}
// cout<
return dist[t]<0x3f3f3f3f;
}
inline int Dinic(int s,int t){
int sum=0;
while(spfa(s,t)){
sum+=flow[t]*dist[t];
for(int i=t;i!=s;i=pos[i]){
e[pre[i]].cap-=flow[t];
e[pre[i]^1].cap+=flow[t];
}
}
return sum;
}
int main(){
int sum=0;
fz(i,1,n) a[i]=read(),sum+=a[i];
sum/=n;
fz(i,1,n){
if(a[i]<sum) addedge(0,i,sum-a[i],0),addedge(i,0,0,0);
else addedge(i,n+1,a[i]-sum,0),addedge(n+1,i,0,0);
}
fz(i,1,n-1){
addedge(i,i+1,0x3f3f3f3f,1);
addedge(i+1,i,0,-1);
addedge(i+1,i,0x3f3f3f3f,1);
addedge(i,i+1,0,-1);
}
addedge(1,n,0x3f3f3f3f,1);
addedge(n,1,0,-1);
addedge(n,1,0x3f3f3f3f,1);
addedge(1,n,0,-1);
cout<<Dinic(0,n+1)<<endl;
return 0;
}
题意:有 n n n 个城市,从西向东依次是 s 1 , s 2 , … , s n s_1,s_2,\dots,s_n s1,s2,…,sn。有 m m m 个航班连接这 n n n 个城市。有一个人要从最西端的城市旅行到最东端的城市,然后再回到最西端的城市。已知除了西端的城市被访问两次,其他城市要么没有被访问,要么只被访问一次。求最多可以访问多少个城市,并输出路径。无解输出 N o S o l u t i o n ! \mathrm{No\ Solution!} No Solution!
题解:看到城市只能访问一次,你第一个想到的应该是……
我们将每个点拆成两个点——入点和出点(老套路)。然后在除了第一个城市和最后一个城市,每个城市入点和出点之间连一条边,容量为 1 1 1,费用为 1 1 1,在第一个和最后一个城市的入点和出点之间连一条边,容量为 2 2 2,费用为 1 1 1。如果两点之间有一条边,那么在第一个点的出点和第二个点的入点间连一条边,容量为 1 1 1,费用为 0 0 0。连一条从源点到第一个城市的入点,容量为 2 2 2,费用为 0 0 0 的边,连一条从第 n n n 个点的出点,流量为 2 2 2 ,费用为 0 0 0 的边。跑最小费用最大流,最小费用 − 2 -2 −2 就是答案(第一个城市和最后一个城市被算了两次,故减 2 2 2)。无解的情况就是最大流 ≠ 2 \neq 2 =2。
然后是输出方案,我们只用找到两条从 1 1 1 到 n n n 的路径,并且没有交点,然后将第一条正序输出,第二条倒序输出。注意 n n n 会输出两次。
然后你美滋滋地交上去,以为可以 A C \mathrm{AC} AC,然鹅
然后你会发现可以构造出一组毒瘤数据把你的程序卡得半死:
2 1
ycx
tzc
ycx tzc
如果你的程序输出无解,那么您太强了,您完美地掉进了这道题的坑里。我们不难发现,如果最大流为 1 1 1,并且 1 1 1 与 n n n 之间有直接的边,那么也是有解的,因此需要加一个小小的特判。(我竟然能在今天中午吃火锅的时候想到这个我也是服了我了)
所以说这题看似简单实则不容易一遍 A C AC AC。
/*
数据不清空,爆零两行泪。
多测不读完,爆零两行泪。
边界不特判,爆零两行泪。
贪心不证明,爆零两行泪。
D P 顺序错,爆零两行泪。
大小少等号,爆零两行泪。
变量不统一,爆零两行泪。
越界不判断,爆零两行泪。
调试不注释,爆零两行泪。
溢出不 l l,爆零两行泪。
*/
#include
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define put(x) putchar(x)
#define eoln put('\n')
#define space put(' ')
inline int read(){
int x=0,neg=1;char c=getchar();
while(!isdigit(c)){
if(c=='-') neg=-1;
c=getchar();
}
while(isdigit(c)) x=x*10+c-'0',c=getchar();
return x*neg;
}
inline void print(int x){
if(x<0){
putchar('-');
print(abs(x));
return;
}
if(x<=9) putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
int n=read(),m=read(),fl=0;
map<string,int> mp;
int ecnt=1,head[1000005];
struct edge{
int to,nxt,cap,cost;
} e[1000005];
inline void addedge(int u,int v,int f,int c){
e[++ecnt].to=v;e[ecnt].cap=f;e[ecnt].cost=c;e[ecnt].nxt=head[u];head[u]=ecnt;
}
bool vis[1000005];
int dist[1000005],flow[1000005],pre[1000005],pos[1000005];
inline bool spfa(int s,int t){
memset(vis,1,sizeof(vis));