网络流,二分图做题笔记

看到需要做类似匹配操作的问题可以考虑往网络流等转化。

最长 k 可重区间集问题

注意到一个方案合法等价于我们可以把选的区间分成不超过 \(k\) 组,每组内部没有重叠。
这样的话考虑让流量对应选的组数,每个区间 \(l_i \rightarrow r_i\) 连边,费用为 \(r_i-l_i\),最后限制一下流量不超过 \(k\) 直接上费用流就是对的。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pll pair<long long,long long>
#define i28 __int128 
#define fir first
#define INF (1e9) 
#define sec second
#define pb push_back
#define eb emplace_back

const int N=1000+9,M=1e5+9;
const int MOD=1e9+7,base=251;
const double eps=1e-14;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline void chkmax(ll &x,ll y){x=x<y?y:x;}
inline void chkmin(ll &x,ll y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=1ll*ret*a%p;
        a=1ll*a*a%p;
        b>>=1;
    }
    return ret; 
}

#define int long long
int mxn,flow,cost;
int h[N],edgecnt=2;
//remember to reset the edgecnt
struct edge{
    int to,val,nxt,cost;
}e[M<<1];
void add2(int u,int v,int w,int c){
    e[edgecnt]={v,w,h[u],c};
    h[u]=edgecnt++;
}
void add(int u,int v,int w,int c){
    add2(u,v,w,c);
    add2(v,u,0,-c);
}
int s,t,maxflow,mincost;
int pre[N],dis[N];
bitset<N> inq;
bool spfa(){
    fill(dis,dis+mxn+2,LLONG_MAX);
    memset(pre,0,sizeof(pre));
    queue<int> q;
    q.push(s);
    dis[s]=0;
    inq[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inq[u]=0;
        for(int i=h[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(e[i].val>0&&dis[v]>dis[u]+e[i].cost){
                dis[v]=dis[u]+e[i].cost;
                pre[v]=i;
                if(!inq[v]){
                    q.push(v);
                    inq[v]=1;
                }
            }
        }
    }
    return dis[t]!=LLONG_MAX;
}
void EK(){
    while(spfa()){
        int flow=LLONG_MAX;
        for(int i=pre[t];i;i=pre[e[i^1].to]){
            flow=min(flow,e[i].val);
        }
        for(int i=pre[t];i;i=pre[e[i^1].to]){
            e[i].val-=flow;
            e[i^1].val+=flow;
        }
        maxflow+=flow;
        mincost+=flow*dis[t];
    }
}
int n,k,L[N],R[N];
int tot,b[N*2];
void Mian(){
	cin>>n>>k;
	for(int i=1;i<=n;++i) cin>>L[i]>>R[i];
	for(int i=1;i<=n;++i) b[++tot]=L[i],b[++tot]=R[i];
	sort(b+1,b+tot+1);
	tot=unique(b+1,b+tot+1)-b-1;
	for(int i=1;i<=n;++i)
		L[i]=lower_bound(b+1,b+tot+1,L[i])-b,
		R[i]=lower_bound(b+1,b+tot+1,R[i])-b;
	
	mxn=tot;
	s=0,t=tot+1;
	add(s,1,k,0);
	add(tot,t,INF,0);
	for(int i=1;i<tot;++i) add(i,i+1,INF,0);
	for(int i=1;i<=n;++i) add(L[i],R[i],1,b[L[i]]-b[R[i]]);
	EK();
	cout<<-mincost; 
}
void Mianclr(){
	
} 
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
	
	//freopen("P10544_3.in","r",stdin);
	//freopen("P10544_3.op","w",stdout);
	
    int c,T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}


剪刀石头布

神题。

先考虑三个点,如果这三个点没有形成三元环,那么必然存在一个点出度为 \(2\),一个点入度为 \(2\),一个点入读出度都为 \(1\)

这样一个入度为 \(k\) 的点就一定对应损失了 \(\frac{k\times (k-1)}{2}\) 个三元环。设 \(deg_i\)\(i\) 的入度,则答案就是 \(\frac{n\times (n-1)\times (n-2)}{6}-\sum\frac{deg_i\times (deg_i-1)}{2}\),也就是我们要求后面那个式子的最小值。

接下来就是把一些入度分配给一些点,使 \(\sum\frac{deg_i\times (deg_i-1)}{2}\) 最小。令 \(F(k)=\frac{k\times (k-1)}{2}\),套路地在把每个点拆成 \(in_i,out_i\),并在中间建 \(n\) 个点,连边容量为 \(1\),费用分别为 \(F(1),F(2)-F(1),F(3)-F(2),\dots\),由于 \(F\) 是凸的,所以自动选择费用小的点走时正确性也是对的。

这样直接跑费用流就对了,非常厉害。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pll pair<long long,long long>
#define i28 __int128 
#define fir first
#define INF (1e9) 
#define sec second
#define pb push_back
#define eb emplace_back

const int N=300+9,M=1e5+9;
const int MOD=1e9+7,base=251;
const double eps=1e-14;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline void chkmax(ll &x,ll y){x=x<y?y:x;}
inline void chkmin(ll &x,ll y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=1ll*ret*a%p;
        a=1ll*a*a%p;
        b>>=1;
    }
    return ret; 
}

#define int long long
int n,m,flow,cost,a[N][N];
vector<pii> din[N];

struct MincostMaxflow{
    #define MAXN (int)3e4+9
    #define MAXM (int)1e5+9

    int head[MAXN],tot=1; 
	//remember to reset the tot
    int n,nxt[MAXM*2],to[MAXM*2],cost[MAXM*2],w[MAXM*2];
    void add2(int u,int v,int c,int flow){
        nxt[++tot]=head[u];
        head[u]=tot; to[tot]=v;
        cost[tot]=c; w[tot]=flow;
    }
	void add(int u,int v,int flow,int c){
		add2(u,v,c,flow);
		add2(v,u,-c,0);
	}
    int s,t,mxflow,micost;
    bool vis[MAXN];

    int h[MAXN];
    void spfa(){
        for(int i=1;i<=n;++i) 
            h[i]=INF,vis[i]=0;
        queue<int> q;
        h[s]=0; 
        vis[s]=1; q.push(s);
        while(!q.empty()){
            int u=q.front(); q.pop();
            vis[u]=0;
            for(int i=head[u];i;i=nxt[i]){
                if(w[i] && h[to[i]]>h[u]+cost[i]){
                    h[to[i]]=h[u]+cost[i];
                    if(!vis[to[i]]){
                        q.push(to[i]);
                        vis[to[i]]=1;
                    }
                }
            }
        }
    } 
    int dis[MAXN],cur[MAXN];
    bool Dijkstra(){
        for(int i=1;i<=n;++i)
            dis[i]=INF,vis[i]=0;
        priority_queue<pii> q;
        dis[s]=0; q.push({0,s});
        while(!q.empty()){
            int u=q.top().sec; q.pop();
            if(vis[u]) continue;
            vis[u]=1;
            for(int i=head[u];i;i=nxt[i]){
                if(w[i] && dis[to[i]]>dis[u]+cost[i]+h[u]-h[to[i]]){
                    dis[to[i]]=dis[u]+cost[i]+h[u]-h[to[i]];
                    q.push({-dis[to[i]],to[i]});
                }
            }
        }
        return dis[t]!=INF;
    }
    int dfs(int u,int flow){
        if(u==t) return flow;
        int out=0;
        vis[u]=1;
        for(int i=cur[u];i && flow;i=nxt[i]){
            cur[u]=i;
            if(w[i] && !vis[to[i]] && h[to[i]]==h[u]+cost[i]){
                int x=dfs(to[i],min(w[i],flow));
                w[i]-=x;  w[i^1]+=x;
                flow-=x; out+=x;
                micost+=cost[i]*x;
            }
        }
        vis[u]=0;
        return out;
    }
    pii dinic(){
        mxflow=micost=0;
        spfa(); 
        while(Dijkstra()){ 
            for(int i=1;i<=n;++i) h[i]=h[i]+dis[i];
            for(int i=1;i<=n;++i) cur[i]=head[i],vis[i]=0; 
            mxflow+=dfs(s,INF);
        }
        return {mxflow,micost};
    }
}G;
int deg[N],tot; 
int F(int x){return x*(x-1)/2;} 
int id(int i,int j){return (i-1)*(n+2)+j;}
void Mian(){
	cin>>n;
	tot=n*(n+2); G.s=++tot,G.t=++tot;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j)
			G.add(id(i,n+1),id(i,j),1,F(j)-F(j-1));
		for(int j=1;j<=n;++j)
			G.add(id(i,j),id(i,n+2),1,0);
		G.add(id(i,n+2),G.t,INF,0);
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			int k; cin>>k;
			a[i][j]=k;
			if(i>=j) continue;
			if(k==0) G.add(G.s,id(i,n+1),1,0);
			if(k==1) G.add(G.s,id(j,n+1),1,0);
			if(k==2){
				int uu=++tot;
				G.add(G.s,tot,1,0);
				G.add(tot,id(i,n+1),1,0); din[i].pb({j,G.tot});
				G.add(tot,id(j,n+1),1,0); din[j].pb({i,G.tot});
			} 
		}
	}
	G.n=tot;
	cout<<n*(n-1)*(n-2)/6-G.dinic().sec<<'\n';
	for(int i=1;i<=n;++i){
		for(auto y:din[i]){
			int v=y.fir,id=y.sec;
			if(G.w[id]) a[v][i]=1;
			else a[v][i]=0;
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j) cout<<a[i][j]<<' ';
		cout<<'\n';
	}
}
void Mianclr(){
	
} 
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
	
	//freopen("P10544_3.in","r",stdin);
	//freopen("P10544_3.op","w",stdout);
	
    int c,T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}


Binary Tree on Plane

拆点,把每个点拆成 \(in_i,out_i\)

让流量表示儿子数量:

  • \(s \rightarrow in_i\),容量为 \(2\),费用为 \(0\)
  • \(out_i \rightarrow t\),容量为 \(1\),费用为 \(0\)
  • 同时对于每一组合法的 \(u\rightarrow v\),让 \(in_u \rightarrow out_v\),容量为 \(1\),费用为 \(u,v\) 的欧几里得距离。

这样最大流就表示了这棵树最大的合法边数,显然不为 \(n-1\) 则无解,同时最小费用也就是这棵树的最小边权和。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pll pair<long long,long long>
#define i28 __int128 
#define fir first
#define INF (1e9) 
#define sec second
#define pb push_back
#define eb emplace_back

const int N=1000+9,M=1e5+9;
const int MOD=1e9+7,base=251;
const double eps=1e-14;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline void chkmax(ll &x,ll y){x=x<y?y:x;}
inline void chkmin(ll &x,ll y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=1ll*ret*a%p;
        a=1ll*a*a%p;
        b>>=1;
    }
    return ret; 
}

int n,m,flow,h[N];
double mincost;
int edgecnt=2;
//remember to reset the edgecnt
struct edge{
    int to,val,nxt;
    double cost;
} e[M<<1];
void add2(int u,int v,int w,double c){
    e[edgecnt]={v,w,h[u],c};
    h[u]=edgecnt++;
}
void add(int u,int v,double c,int w){
    add2(u,v,w,c);
    add2(v,u,0,-c);
}
int s,t,maxflow;
int pre[N];
double dis[N];
bitset<N> inq;
bool spfa(){
    for(int i=1;i<=n+2;++i) dis[i]=INF;
    memset(pre,0,sizeof(pre));
    queue<int> q;
    q.push(s);
    dis[s]=0;
    inq[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inq[u]=0;
        for(int i=h[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(e[i].val>0 && dis[v]>dis[u]+e[i].cost){
                dis[v]=dis[u]+e[i].cost;
                pre[v]=i;
                if(!inq[v]){
                    q.push(v);
                    inq[v]=1;
                }
            }
        }
    }
    return dis[t]!=INF;
}
void EK(){
    while(spfa()){ 
        int flow=INF;
        for(int i=pre[t];i;i=pre[e[i^1].to]){
            flow=min(flow,e[i].val);
        }
        for(int i=pre[t];i;i=pre[e[i^1].to]){
            e[i].val-=flow;
            e[i^1].val+=flow;
        }  
        maxflow+=flow;
        mincost+=flow*dis[t];
    }
}
struct node{
	double x,y;
} a[N];
double dist(int x,int y){
	return 1.0*sqrt((a[x].x-a[y].x)*(a[x].x-a[y].x)+
					(a[x].y-a[y].y)*(a[x].y-a[y].y));
} 
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i].x>>a[i].y;
	s=2*n+1; t=2*n+2;
	for(int i=1;i<=n;++i){
		add(s,i,0,2);
		add(i+n,t,0,1);
	} 
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(a[j].y<a[i].y) 
				add(i,j+n,dist(i,j),1);
		}
	} 
	int tmp=n;
	n=2*n+2; EK();
	if(maxflow!=tmp-1) cout<<-1;
	else printf("%.15lf",mincost);
}
void Mianclr(){
	
} 
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
	
	//freopen("P10544_3.in","r",stdin);
	//freopen("P10544_3.op","w",stdout);
	
    int c,T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}


善意的投票

最大流-最小割定理。

考虑一个点与 \(S\) 联通表示同意睡午觉,与 \(T\) 联通表示不同意睡午觉。这样对于一个同意的人 \(i\),如果他和 \(T\) 相连(改变了意愿)则需要付出 \(1\) 的代价,连边 \(S \rightarrow i\),容量为 \(1\)。不同意的人同理连边 \(i\rightarrow T\),容量为 \(1\)

对于一对好朋友,如果不相同(一个连 \(S\) 一个连 \(T\))则需要付出 \(1\) 的代价。直接连双向边,容量均为 \(1\) 即可,我们总是会切断从连 \(S\) 的点到连 \(T\) 的点的那条边。

这样答案就是这个图最小割,即求出此图的最大流即可。

AC Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pll pair<long long,long long>
#define i28 __int128 
#define fir first
#define INF (1e9) 
#define sec second
#define pb push_back
#define eb emplace_back

const int N=300+9,M=1e5+9;
const int MOD=1e9+7,base=251;
const double eps=1e-14;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline void chkmax(ll &x,ll y){x=x<y?y:x;}
inline void chkmin(ll &x,ll y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
    int ret=1;
    while(b){
        if(b&1) ret=1ll*ret*a%p;
        a=1ll*a*a%p;
        b>>=1;
    }
    return ret; 
}

struct Dinic{
    #define MAXN 10000+9
    #define MAXM 100000*2+9

    int n,m,s,t;
    int head[MAXN],tot=1;
    int nxt[MAXM],to[MAXM],w[MAXM];
    void add2(int u,int v,int x){
        nxt[++tot]=head[u];
        head[u]=tot;
        to[tot]=v;
        w[tot]=x;
    }
    void add(int u,int v,int x){
        add2(u,v,x);
        add2(v,u,0);
    }
    int dep[N],cur[N];
    queue<int> q;
    bool bfs(){
        for(int i=1;i<=n;++i) dep[i]=0;
        for(int i=1;i<=n;++i) cur[i]=head[i];
        dep[s]=1; q.push(s);
        while(!q.empty()){
            int u=q.front(); q.pop();
            for(int i=head[u];i;i=nxt[i]){
                if(!dep[to[i]] && w[i]){
                    dep[to[i]]=dep[u]+1;
                    q.push(to[i]);
                }
            }
        }
        return dep[t];
    }
    int dfs(int u,int flow){
        if(u==t) return flow;
        int out=0;
        for(int i=cur[u];i && flow;i=nxt[i]){
            cur[u]=i;
            if(w[i] && dep[to[i]]==dep[u]+1){
                int x=dfs(to[i],min(flow,w[i]));
                flow-=x; out+=x;
                w[i]-=x; w[i^1]+=x;
            }
        }
        return out;
    }
    int solve(){
        int ans=0;
        while(bfs())
            ans+=dfs(s,INF);
        return ans;
    }
} G;
void Mian(){
	cin>>G.n>>G.m;
	G.s=G.n+1,G.t=G.n+2; 
	for(int i=1;i<=G.n;++i){
		int k; cin>>k;
		if(k) G.add(G.s,i,1);
		else G.add(i,G.t,1);
	} 
	for(int i=1;i<=G.m;++i){
		int u,v; cin>>u>>v;
		G.add(u,v,1);
		G.add(v,u,1); 
	}
	G.n+=2;
	cout<<G.solve(); 
}
void Mianclr(){
	
} 
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
	
	//freopen("P10544_3.in","r",stdin);
	//freopen("P10544_3.op","w",stdout);
	
    int c,T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}


posted @ 2026-04-08 21:59  Mi2uk1  阅读(1)  评论(0)    收藏  举报