网络流记录

Luogu P4298 [CTSC2008]祭祀

毒瘤网络流题.

前置知识:Dilworth

第一行:

|反链|=|最小可重复链覆盖|= n − n- n最大匹配= n − n- n最小点覆盖

对于第二行:

实际上,比较麻烦的是构造方案.

设拆点二分图的最大独立集为 I I I,对于拆点后都属于 I I I的点集设为 A , A, A,最大匹配为$m $
则有: ∣ I ∣ − ∣ A ∣ < = n |I|-|A|<=n IA<=n(单次出现的点)
∣ I ∣ = 2 n − m |I|=2n-m I=2nm,所以 ∣ A ∣ > = n − m |A|>=n-m A>=nm.
又因为每一条链最多选一个点,最多又 n − m n-m nm条链,所以 ∣ A ∣ = n − m |A|=n-m A=nm.

再证明一下, A A A为反链.

最大独立集是最小点覆盖的补集.

如果我们在残余网络中与st联通的标记一下.

那么左部已标记,右边未标记为最大独立集.

所以相连的点不可能同时被标记.否则和标记的结果矛盾.

对于第三行:

我们暴力选择一个点加入反链,然后求一下剩余部分的反链大小即可.

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=210,M=N*N,inf=-1u/2;

void qr(int &x) {scanf("%d",&x);}

int n,m,b[N][N],ans,c[N],d[N],q[N],l,r,st,ed;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}

bool v[N];
void bfs() {
	q[l=r=1]=ed; memset(c,0,sizeof c); memset(d,0,sizeof d);
	++c[d[ed]=1]; memcpy(cur,last,ed+1<<2);
	for(int x=q[l];l<=r;x=q[++l])
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]) {++c[d[y]=d[x]+1]; q[++r]=y;}
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z&&d[y]+1==d[x]) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!--c[d[x]]) d[st]=ed+1;
	++c[++d[x]]; cur[x]=last[x]; return s;
}

int calc() {
/*最大反链=n-最小可重复链覆盖=总点数-最大匹配 
证明: 设拆点二分图的最大独立集为I,对于拆点后都属于I的点集设为A,最大匹配为m 
则有: I-|A|<=n(I可以把拆出的点都包含)
又I=2n-m,所以|A|>=n-m.
又因为每一条链最多选一个点,最多又n-m条链,所以|A|=n-m*/
	int tot=0,res;
	len=1; memset(last,0,sizeof last);
	for(int i=1;i<=n;i++) if(!v[i]) q[++tot]=i,add(st,i,1),add(i+n,ed,1);
	for(int i=1;i<=tot;i++) {
		int x=q[i];
	 	for(int j=1;j<=tot;j++) if(j^i) {
	 		int y=q[j];
	 		if(b[x][y]) 
			 	add(x,y+n,1);
	 	}
	 }
	bfs(); res=tot; tot=tot*2+1;
	while(d[st]<=tot) 
		res-=dfs(st,inf);
	return res;
}

bool vis[N];
void DFS(int x) {
	vis[x]=1;
	for(int k=last[x],y;k;k=a[k].next)
		if(a[k].c&&!vis[y=a[k].y]) DFS(y);
}

void solve1() {
	printf("%d\n",ans=calc()); DFS(st);
	for(int i=1;i<=n;i++) putchar((vis[i]&!vis[i+n])+'0');
	puts("");
}

int main() {
	qr(n); qr(m); st=0; ed=n*2+1;
	for(int i=1,x,y;i<=m;i++) qr(x),qr(y),b[x][y]=1;
	for(int i=1;i<=n;i++) b[i][i]=1;
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				b[i][j]|=b[i][k]&b[k][j];
	solve1();
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) v[j]=b[i][j]||b[j][i];
		putchar((calc()+1==ans)+'0');
	}
	return 0;
}
	

LOJ #115. 无源汇有上下界可行流

给你一个无源汇网络,每条边的流量 [ L i , R i ] [L_i,R_i] [Li,Ri],求一个可行流

我们先钦定每个边的流量为 L i L_i Li,然后考虑增加某些边(附加边)的流量(容量上限为 R i − L i R_i-L_i RiLi)使得流量守恒.

流量守恒式子: ∑ i n f l o w ( i n , x ) = ∑ o u t f l o w ( x , o u t ) \sum_{in} flow(in,x)=\sum_{out} flow(x,out) inflow(in,x)=outflow(x,out).

我们设 s u m x = f l o w i n − f l o w o u t sum_x=flowin-flowout sumx=flowinflowout.

则可以表示 x x x附加边流量的需求.

  1. s u m x < 0 , 说 明 x 需 要 更 多 的 流 入 sum_x<0,说明x需要更多的流入 sumx<0,x
  2. s u m x > 0 , 说 明 x 需 要 更 多 的 流 出 sum_x>0,说明x需要更多的流出 sumx>0,x
  3. s u m x = 0 , 说 明 x 增 加 的 流 入 = 流 出 sum_x=0,说明x增加的流入=流出 sumx=0,x=

由于:

  1. 无源汇
  2. 附加边在原图上跑流量(忽略 L i L_i Li的影响)并不需要满足流量守恒.

这启示我们要加源汇 s s , t t ss,tt ss,tt.

然后根据 s u m x sum_x sumx连边.

  1. s u m x < 0 , 附 加 边 流 入 的 s u m x 流 量 需 要 流 出 的 方 向 , 所 以 连 ( x , e d , − s u m x ) ( 给 他 一 个 宣 泄 的 地 方 ) sum_x<0,附加边流入的sum_x流量需要流出的方向,所以连(x,ed,-sum_x)(给他一个宣泄的地方) sumx<0,sumx,(x,ed,sumx)()
  2. s u m x > 0 , 附 加 边 流 出 的 s u m x 流 量 需 要 流 入 的 方 向 , 所 以 连 ( s t , x , s u m x ) sum_x>0,附加边流出的sum_x流量需要流入的方向,所以连(st,x,sum_x) sumx>0,sumx,(st,x,sumx)

一个显然的式子 ∑ s u m x = 0 \sum sum_x=0 sumx=0.

所以跑完最大流,如果跟 s s ss ss相连的边的容量流尽,说明所有的需求均满足,合法.


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=210,M=2.1e4+10,inf=1<<30;

template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,st,ed,d[N],c[N],q[N],sum[N],L[M],R[M],ans,s;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c)  {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}

void bfs() {
	int l,r; q[l=r=1]=ed; ++c[d[ed]=1];
	memcpy(cur,last,sizeof cur);
	for(int x=q[l];l<=r;x=q[++l])
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]) {++c[d[y]=d[x]+1]; q[++r]=y;}
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=min(f-s,a[k].c);
		if(z&&d[y]+1==d[x]) {
			s+=t=dfs(y,z);
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!--c[d[x]]) d[st]=n+5;
	++c[++d[x]]; cur[x]=last[x]; return s;
}

int main() {
	qr(n); qr(m); 
	for(int i=1,x,y;i<=m;i++) {
		qr(x); qr(y); qr(L[i]); qr(R[i]);
		add(x,y,R[i]-L[i]);
		sum[y]+=L[i];
		sum[x]-=L[i];
	}
	st=n+1; ed=n+2;
	for(int i=1;i<=n;i++) {
		int x=sum[i];
		if(x>0) add(st,i,x),s+=x;
		if(x<0) add(i,ed,-x);
	}
	bfs(); while(d[st]<=ed) ans+=dfs(st,inf);
	if(ans^s) puts("NO");
	else {
		puts("YES");
		for(int i=1;i<=m;i++) pr2(R[i]-a[i*2].c);
	}
	return 0;
}

LOJ #116. 有源汇有上下界最大流

做这题之前有必要重新了解网络流的定义.

转自百度百科

可行流 ( f e a s i b l e   f l o w ) (feasible~ flow) (feasible flow)图论的一个重要概念。它是满足一定条件的网络流。

对一个网络的某些点指定为发点,规定出提供能力;某些点指定为收点,规定出接收能力。

若一个流对每一发点满足总流出量与总流入量之差不大于提供能力,对每一收点满足总流入量与总流出量之差不小于接收能力,则称这个流为可行流

也就是网络的流量实际上=汇点流入量-汇点流出量.

步入正题.

这个不是无源汇咋办?直接连 ( T , S , I N F ) (T,S,INF) (T,S,INF),使得原图可以跑出无源汇有上下界可行流,这条边流去的实际流量就是一种可行流.

为了最大化,我们直接再以原图 s t , e d st,ed st,ed跑一次最大流即可.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=210,M=2.1e4+10,inf=1<<30;

template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,st,ed,S,T,d[N],c[N],q[N],sum[N],L[M],R[M],ans,s,cnt;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c)  {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}

void bfs() {
	int l,r; q[l=r=1]=ed; 
	memset(d,0,sizeof d); 
	memset(c,0,sizeof c); 
	++c[d[ed]=1];
	memcpy(cur,last,sizeof cur);
	for(int x=q[l];l<=r;x=q[++l])
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]) {++c[d[y]=d[x]+1]; q[++r]=y;}
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=min(f-s,a[k].c);
		if(z&&d[y]+1==d[x]) {
			s+=t=dfs(y,z);
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!--c[d[x]]) d[st]=n+5;
	++c[++d[x]]; cur[x]=last[x]; return s;
}

int main() {
	qr(n); qr(m); qr(S); qr(T);
	for(int i=1,x,y;i<=m;i++) {
		qr(x); qr(y); qr(L[i]); qr(R[i]);
		add(x,y,R[i]-L[i]);
		sum[y]+=L[i];
		sum[x]-=L[i];
	}
	st=n+1; ed=n+2;
	for(int i=1;i<=n;i++) {
		int x=sum[i];
		if(x>0) add(st,i,x),s+=x;
		if(x<0) add(i,ed,-x);
	}
	add(T,S,inf);
	bfs(); while(d[st]<=n) ans+=dfs(st,inf);
	if(ans^s) puts("please go home to sleep");
	else {
		ans=0; st=S; ed=T; 
		bfs(); while(d[st]<=n) ans+=dfs(st,inf); 
		printf("%d\n",ans+a[len].c); 
	}
	return 0;
}
 

LOJ #117. 有源汇有上下界最小流

类似上题,构造出一组可行解后退流即可.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=5e4+10,M=25e4+N*4,inf=1<<30;

void qr(int &x) {
	char c=gc; x=0;
	while(!isdigit(c))c=gc;
	while(isdigit(c))x=x*10+c-'0',c=gc;
}

int b[N],n,m,st,ed,d[N],c[N],q[N],l,r,S,T,ans,s;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int c) {a[++len]=(edge){y,last[x],c}; last[x]=len;}
void add(int x,int y,int c) {ins(x,y,c); ins(y,x,0);}

void bfs() {
	q[l=r=1]=ed;
	memset(d,0,sizeof d);
	memset(c,0,sizeof c);
	++c[d[ed]=1];
	memcpy(cur,last,sizeof cur);
	for(int x=ed;l<=r;x=q[++l])
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]) { ++c[d[y]=d[x]+1]; q[++r]=y;}
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=min(f-s,a[k].c);
		if(z&&d[y]+1==d[x]) {
			s+=t=dfs(y,z);
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!--c[d[x]]) d[st]=ed+1;
	++c[++d[x]]; cur[x]=last[x]; return s;
}

int main() {
	qr(n); qr(m); qr(S); qr(T);
	for(int i=1,x,y,z,w;i<=m;i++) {
		qr(x); qr(y); qr(z); qr(w);
		add(x,y,w-z);
		b[y]+=z;
		b[x]-=z;
	}
	st=n+1; ed=n+2;
	for(int i=1;i<=n;i++) {
		int x=b[i];
		if(x>0) add(st,i,x),s+=x;
		if(x<0) add(i,ed,-x);
	}
	add(T,S,inf);
	bfs(); while(d[st]<=ed) ans+=dfs(st,inf);
	if(ans<s) puts("please go home to sleep");
	else {
		st=T; ed=S; ans=a[len].c; a[len].c=a[len^1].c=0;bfs(); 
		while(d[st]<=n) ans-=dfs(st,inf);
		printf("%d\n",ans);
	}
	return 0;
}
 

有源汇上下界最小费用可行流

题目

转成无源汇再搞EK.

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define gc getchar()
using namespace std;
const int N=330,M=1e4+4*N,inf=1<<30;

int n,m,q[N],d[N],f[N],p[N],st,ed,ans,l,r,sum[N]; bool v[N];
struct edge{int y,next,d,c;}a[M]; int len=1,last[N];
void ins(int x,int y,int d,int c) {a[++len]=(edge){y,last[x],d,c}; last[x]=len;}
void add(int x,int y,int d,int c) {ins(x,y,d,c); ins(y,x,-d,0);}

int EK() {
	int res=0;
	while(1) {
		l=r=0; q[r++]=st; f[st]=inf; 
		memset(d,63,sizeof(d)); d[st]=0;
		while(l^r) {
			int x=q[l++]; v[x]=0; if(l==N-5) l=0;
			for(int k=last[x],y;k;k=a[k].next)
				if(a[k].c&&d[y=a[k].y]>d[x]+a[k].d) {
					d[y]=d[x]+a[k].d;
					f[y]=min(f[x],a[k].c);
					p[y]=k;
					if(!v[y]) {
						q[r++]=y; v[y]=1;
						if(r==N-5) r=0;
					}
				}
		}
		if(d[ed]==d[0]) return res;
		int x=ed,y=f[x]; 
		res+=d[x]*y;
		while(x^st) {
			int k=p[x];
			a[k].c-=y;
			a[k^1].c+=y;
			x=a[k^1].y;
		}
	}
}

int main() {
	scanf("%d",&n);
	for(int i=1,k,x,y;i<=n;i++) {
		scanf("%d",&k);  sum[i]-=k;
		while(k--) {
			scanf("%d %d",&x,&y);
			sum[x]++; ans+=y; add(i,x,y,inf);
		}
		if(i>1) add(i,1,0,inf);
	}
	st=n+1; ed=n+2;
	for(int i=1,x;i<=n;i++) {
		x=sum[i];
		if(x>0) add(st,i,0,x);
		if(x<0) add(i,ed,0,-x);
	}
	ans+=EK();
	printf("%d\n",ans); return 0;
}
			

最小割树

最小割树是求解无向图任意两点间的最小割的有力工具.

算法流程:

对于一个点集,任选两个点 x , y x,y x,y,求一次最小割,划分出两个点集 S , T S,T S,T.

然后递归处理 S , T S,T S,T,知道集合只剩余1个点.

(这可以看作一棵分治树,实际上我们称之为最小割树)

最小割树满足一个很强的性质 : 两点之间的最小割 等于 在最小割树上两点间路径中边的最小权(权值为划分时的最小割)。

  • 引理1:定义 c u t ( x , y ) 为 x , y 间 的 最 小 割 cut(x,y)为x,y间的最小割 cut(x,y)x,y.

    若最小割分割出了两个连通块 S , T ( x ∈ S , y ∈ T ) S,T(x\in S,y\in T) S,T(xS,yT),

    ∀ u ∈ S , v ∈ T , c u t ( u , v ) ≤ c u t ( x , y ) \forall u\in S,v\in T ,cut(u,v)\le cut(x,y) uS,vT,cut(u,v)cut(x,y).

    证明:由于只要割掉 c u t ( x , y ) cut(x,y) cut(x,y)对应的边, S , T S,T S,T就不连通,所以 c u t ( u , v ) ≤ c u t ( x , y ) cut(u,v)\le cut(x,y) cut(u,v)cut(x,y).

  • 引理2: c u t ( x , y ) , c u t ( x , z ) , c u t ( y , z ) cut(x,y),cut(x,z),cut(y,z) cut(x,y),cut(x,z),cut(y,z)的最小值至少出现两次.

    假设 c u t ( x , y ) cut(x,y) cut(x,y)为最小值, z ∈ S z\in S zS.

    则有 c u t ( z , y ) ≤ c u t ( x , y ) cut(z,y)\le cut(x,y) cut(z,y)cut(x,y),由于 c u t ( x , y ) cut(x,y) cut(x,y)最小,所以 c u t ( z , y ) = c u t ( x , y ) cut(z,y)=cut(x,y) cut(z,y)=cut(x,y),证毕!

    • 推论1: c u t ( x , y ) ≥ min ⁡ ( c u t ( x , z ) , c u t ( z , y ) ) cut(x,y)\ge \min(cut(x,z),cut(z,y)) cut(x,y)min(cut(x,z),cut(z,y)).讨论 c u t ( x , y ) cut(x,y) cut(x,y)是否是最小值即可.
    • 推论2:由推论1拓展得: c u t ( x , y ) ≥ m i n ( c u t ( x , a 1 ) , c u t ( a 1 , a 2 ) , . . . c u t ( a k , y ) ) cut(x,y)\ge min(cut(x,a_1),cut(a_1,a_2),...cut(a_k,y)) cut(x,y)min(cut(x,a1),cut(a1,a2),...cut(ak,y)).
  • 引理3(最小割树定理):

    ( x , y ) (x,y) (x,y)的最小割为路径上的边权最小值.

    证明:设边权依次为 a 1 , a 2 . . . a k a_1,a_2...a_k a1,a2...ak.

    由引理1可得: c u t ( x , y ) ≤ a i , i ∈ [ 1 , k ] ∩ N cut(x,y)\le a_i,i\in [1,k]\cap \N cut(x,y)ai,i[1,k]N.

    有推论2可得: c u t ( x , y ) ≥ m i n ( c u t ( x , a 1 ) , c u t ( a 1 , a 2 ) , . . . c u t ( a k , y ) ) cut(x,y)\ge min(cut(x,a_1),cut(a_1,a_2),...cut(a_k,y)) cut(x,y)min(cut(x,a1),cut(a1,a2),...cut(ak,y)).

    所以成立.

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define gc getchar()
using namespace std;
const int N=510,M=3100,inf=1<<30;

void qr(int &x) {
	char c=gc; x=0;
	while(!isdigit(c))c=gc;
	while(isdigit(c))x=x*10+c-'0',c=gc;
}
void qw(int x) {
	if(x>9) qw(x/10);
	putchar(x%10+'0');
}
void pr2(int x) {qw(x); puts("");}

int n,m,d[N],q[N],l,r,st,ed;
struct edge{int y,next,c;}a[M],b[M],A[2*N]; int len=1,last[N],cur[N],Len=1,Last[N];
void add(int x,int y,int c) {
	a[++len]=(edge){y,last[x],c}; last[x]=len;
	a[++len]=(edge){x,last[y],c}; last[y]=len;
}
void ADD(int x,int y,int c) {
	A[++Len]=(edge){y,Last[x],c}; Last[x]=Len;
	A[++Len]=(edge){x,Last[y],c}; Last[y]=Len;
}

bool bfs() {
	q[l=r=0]=st; memset(d,0,sizeof d); d[st]=1;
	memcpy(cur,last,sizeof cur);
	for(int x=st;l<=r;x=q[++l])
		for(int k=last[x],y;k;k=a[k].next)
			if(a[k].c&&!d[y=a[k].y]) {d[y]=d[x]+1; q[++r]=y;}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=min(a[k].c,f-s);
		if(z&&d[y]==d[x]+1) {
			s+=t=dfs(y,z);
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int dicnic() {
	int s=0;
	memcpy(a,b,sizeof a);
	while(bfs()) 
		s+=dfs(st,inf);
	return s;
}

int p[N],u[N],v[N];
void solve(int l,int r) {
	if(l==r) return ;
	st=p[l]; ed=p[r];
	ADD(st,ed,dicnic());
	int x=0,y=0;
	for(int i=l;i<=r;i++)
		if(d[p[i]]) u[++x]=p[i];
		else v[++y]=p[i];
	int mid=l+x-1;
	for(int i=l;i<=r;i++)
		p[i]=x?u[x--]:v[y--];
	solve(l,mid); solve(mid+1,r);
}

int vis[N],now,f[N][N];
void DFS(int x,int t) {
	vis[x]=now; f[now][x]=t;
	for(int k=Last[x],y;k;k=A[k].next)
		if(vis[y=A[k].y]^now) DFS(y,min(t,A[k].c));
}

int main() {
	qr(n); qr(m);  n++;
	for(int i=1,x,y,z;i<=m;i++) qr(x),qr(y),qr(z),add(++x,++y,z);
	memcpy(b,a,sizeof b);
	for(int i=1;i<=n;i++) p[i]=i;
	solve(1,n);
	for(now=1;now<=n;now++) DFS(now,inf);
	qr(m); int x,y; while(m--) {
		qr(x); qr(y);
		pr2(f[++x][++y]);
	}
	return 0;
}

Luogu P5029 T’ill It’s Over

给你n个1,你需要尽可能多地把他们变为k.

有m种操作,每种操作都有一个限定次数.

操作有4种类型:

  1. x y 把一个x变为y
  2. x y a 把一个 [ x , y ] [x,y] [x,y]内的数变为a
  3. x a b把一个x变为 [ a , b ] [a,b] [a,b]中的一个数.
  4. x y a b把一个 [ x , y ] [x,y] [x,y]内的数变为 [ a , b ] [a,b] [a,b]内的一个数.

线段树优化建边.

线段树分两棵,代表入树和出树.

对于入树,线段树儿子向父亲连有向边.

对于出树,父亲向儿子连边.

我们可以把4中类型划归为第4种: [ x , y ] − > [ a , b ] [x,y]->[a,b] [x,y]>[a,b].

我们先把他们对应的线段树节点找到,然后对于入,我们把节点连向一个新节点 l e f t left left,对于出,我们用新节点 r i g h t right right连向对应线段树节点.

最后连 ( l e f t , r i g h t , l i m i t ) (left,right,limit) (left,right,limit),表示限制次数.

小细节:对于 [ 1 , n ] [1,n] [1,n]的线段树查区间的话,对应的线段树节点准确来讲是 2 log ⁡ n 2\log n 2logn的,所以空间要注意一下.

#include<bits/stdc++.h>
#define gc getchar()
using namespace std;
const int N=4e5+10,M=N*8,inf=2e9;
template<class o>void qr(o&x) {
	char c=gc;int f=1;x=0;
	while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o>void qw(o x) {
	if(x/10)qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x);puts("");
}

int n,m,kk,st,ed,d[N],ans;

//Dicnic
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}

bool bfs() {
	memset(d,0,sizeof d); 
	static int q[N],l,r;
	q[l=r=1]=st; d[st]=1;
	memcpy(cur,last,sizeof cur);
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
			}
	}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z>0&&d[y]==d[x]+1) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int dicnic() {
	int s=0;
	while(bfs())
		s+=dfs(st,inf);
	return s;
}

//Disc/Queries
struct rec {
	int op,lim,x,y,a,b;
} q[N];
int val[N],num;
void disc() {
	sort(val+1,val+num+1);
	num=unique(val+1,val+num+1)-(val+1);
}
int find(int x) {return lower_bound(val+1,val+num+1,x)-val;}

//Segment Tree
int cnt;
struct Segment_Tree {
	int son[2][N],root,id[N];
	#define lc son[0][x]
	#define rc son[1][x]
	void bt(int &x,int l,int r,int op) {
		x=++cnt;
		if(l==r) {id[l]=x; return ;}
		int mid=(l+r)/2;
		bt(lc,l,mid,op);
		bt(rc,mid+1,r,op);
		if(op==1) add(lc,x,inf),add(rc,x,inf);
		else add(x,lc,inf),add(x,rc,inf);
	}
	int sta[44],top;
	void ask(int x,int l,int r,int L,int R) {
		if(L<=l&&r<=R) {sta[++top]=x; return ;}
		int mid=(l+r)>>1;
		if(L<=mid) ask(lc,l,mid,L,R);
		if(mid< R) ask(rc,mid+1,r,L,R);
	}
	void Find(int L,int R) {top=0;ask(root,1,num,L,R);}
} in,out;//入树,出树.

int main() {
	qr(n); qr(m); qr(kk); if(kk==1) {pr2(n); return 0;}
	for(int i=1;i<=m;i++) {
		qr(q[i].op); qr(q[i].lim);
		switch(q[i].op) {
			case 1:qr(q[i].x); qr(q[i].a); break;
			case 2:qr(q[i].x); qr(q[i].y); qr(q[i].a); break;
			case 3:qr(q[i].x); qr(q[i].a); qr(q[i].b); break;
			case 4:qr(q[i].x); qr(q[i].y); qr(q[i].a); qr(q[i].b); break;
		}
		val[++num]=q[i].x;
		val[++num]=q[i].y; 
		val[++num]=q[i].a;
		val[++num]=q[i].b;
	}
	val[++num]=1; val[++num]=kk; disc();
	in.bt(in.root,1,num,1);
	out.bt(out.root,1,num,2);
	st=in.id[find(1)]; add(out.id[find(kk)],ed=++cnt,n);
	for(int i=1;i<=num;i++) add(out.id[i],in.id[i],inf);
	for(int i=1;i<=m;i++) {
		int op=q[i].op,t=q[i].lim,x=find(q[i].x),y=q[i].y,l=find(q[i].a),r=q[i].b,left,right;
		if(op==1) add(in.id[x],out.id[l],t);
		else if(op==3) {
			out.Find(l,find(r));
			right=++cnt;
			add(in.id[x],right,t);
			for(int j=1;j<=out.top;j++) add(right,out.sta[j],t);
		}
		else if(op==2) {
			in.Find(x,find(y));
			left=++cnt;
			add(left,out.id[l],t);
			for(int j=1;j<=in.top;j++) add(in.sta[j],left,t);
		}
		else {
			left=++cnt; right=++cnt;
			in.Find(x,find(y));
			out.Find(l,find(r));
			for(int j=1;j<=in.top;j++) add(in.sta[j],left,t);
			for(int j=1;j<=out.top;j++) add(right,out.sta[j],t);
			add(left,right,t);
		}
	}
	pr2(dicnic());
	return 0;
}


CF852D Exploration plan

预处理距离,二分长度,跑最大流.

#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=810,M=N*N,inf=2e9;
template<class o>void qr(o&x) {
	char c=gc;int f=1;x=0;
	while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o>void qw(o x) {
	if(x/10)qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x);puts("");
}

int n,m,st,ed,d[N],q[N],ans,b[N][N];
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}

bool bfs() {
	memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
	memcpy(cur,last,sizeof cur);
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
			}
	}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z>0&&d[y]==d[x]+1) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int p[N],cnt,K;

int dicnic(int x) {
	len=1; memset(last,0,sizeof last); st=0; ed=n+cnt+1;
	for(int i=1;i<=cnt;i++) {
		add(st,i,1);//得到点 
		for(int j=1;j<=n;j++) 
			if(b[p[i]][j]<=x) add(i,j+cnt,1);
	}
	for(int i=1;i<=n;i++) add(i+cnt,ed,1);//接纳点 
	int s=0;
	while(bfs())
		s+=dfs(st,inf);
	return s;
}

int main() {
	qr(n); qr(m); qr(cnt); qr(K);
	for(int i=1;i<=cnt;i++) qr(p[i]);
	memset(b,63,sizeof b);
	for(int i=1,x,y,t;i<=m;i++) qr(x),qr(y),qr(t),b[x][y]=b[y][x]=min(b[x][y],t);
	for(int i=1;i<=n;i++) b[i][i]=0;
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				b[i][j]=min(b[i][j],b[i][k]+b[k][j]);
	int l=0,r=1731315,mid;
	while(l^r) {
		mid=(l+r)>>1;
		if(dicnic(mid)>=K) r=mid;
		else l=mid+1;
	}
	if(r>1731311) puts("-1");
	else pr2(r);
	return 0;
}


CF546E Soldier and Traveling

有一个图,你需要把每个点上的人移动到至多经过1条边的地方,使得最后每个点上的人满足要求.

拆点+用反向边流量构造.

#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=210,M=N*N,inf=2e9;
template<class o>void qr(o&x) {
	char c=gc;int f=1;x=0;
	while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o>void qw(o x) {
	if(x/10)qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x);puts("");
}

int n,m,st,ed,d[N],q[N],ans,u[N],v[N],A,B;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}

bool bfs() {
	memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
	memcpy(cur,last,sizeof cur);
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
			}
	}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z>0&&d[y]==d[x]+1) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int dicnic() {
	int s=0;
	while(bfs())
		s+=dfs(st,inf);
	return s;
}

void out() {puts("NO"); exit(0);}

int p[N];

int main() {
	qr(n); qr(m); st=0; ed=2*n+1;
	for(int i=1;i<=n;i++) qr(u[i]),A+=u[i],add(st,i,u[i]),add(i,i+n,inf);
	for(int i=1;i<=n;i++) qr(v[i]),B+=v[i],add(i+n,ed,v[i]);
	if(A^B) out();
	for(int i=1,x,y;i<=m;i++)
		qr(x),qr(y),add(x,y+n,inf),add(y,x+n,inf);
	if(dicnic()^A) out();
	puts("YES");
	for(int i=1;i<=n;i++) {
		memset(p+1,0,n<<2);
		for(int k=last[i],y;k;k=a[k].next) {
			y=a[k].y; 
			if(y==st) continue;
			p[y-n]=a[k^1].c;
		}
		for(int j=1;j<=n;j++) pr1(p[j]);
		puts("");
	}
	return 0;
}


Luogu P1231 教辅的组成

三分图,注意把书拆点限流.

#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=4e4+10,M=N*4,inf=2e9;
template<class o>void qr(o&x) {
	char c=gc;int f=1;x=0;
	while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o>void qw(o x) {
	if(x/10)qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x);puts("");
}

int n,m,st,ed,d[N],q[N],ans;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}

bool bfs() {
	memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
	memcpy(cur,last,sizeof cur);
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
			}
	}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z>0&&d[y]==d[x]+1) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int dicnic() {
	int s=0;
	while(bfs())
		s+=dfs(st,inf);
	return s;
}

int main() {
	int u,v,w,x,y; qr(v); qr(u); qr(w); st=0; ed=u+v*2+w+1;
	for(int i=1;i<=u;i++) add(st,i,1);
	for(int i=u+1;i<=u+v;i++) add(i,i+v,1);//把书拆点 
	for(int i=u+v*2+1;i<ed;i++) add(i,ed,1);
	qr(m); while(m--) {
		qr(y); qr(x); y+=u;
		add(x,y,1);
	}
	qr(m); while(m--) {
		qr(x); qr(y); x+=u+v; y+=u+v*2;
		add(x,y,1);
	}
	pr2(dicnic());
	return 0;
}


Luogu P2825 [HEOI2016/TJOI2016]游戏

发掘1性质.

炸弹连接了竖连通块和横连通块,每个连通块只能连一条边,最大匹配实际上就是最大化炸弹数.

#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int C=55,N=C*C*2,M=N*6,inf=2e9;
template<class o>void qr(o&x) {
	char c=gc;int f=1;x=0;
	while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o>void qw(o x) {
	if(x/10)qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x);puts("");
}

int n,m,st,ed,d[N],q[N],ans;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}

bool bfs() {
	memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
	memcpy(cur,last,sizeof cur);
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
			}
	}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z>0&&d[y]==d[x]+1) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int dicnic() {
	int s=0;
	while(bfs())
		s+=dfs(st,inf);
	return s;
}

int row[N][N],col[N][N],s1,s2;
char s[N][N];

int main() {
	qr(n); qr(m);
	for(int i=1;i<=n;i++) {
		scanf("%s",s[i]+1); ++s1; 
		for(int j=1;j<=m;j++) {
			if(s[i][j]=='#') s1++;
			else row[i][j]=s1;
		}
	}
	for(int j=1;j<=m;j++) {
		++s2;
		for(int i=1;i<=n;i++) {
			if(s[i][j]=='#') s2++;
			else col[i][j]=s2;
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) 
			if(s[i][j]=='*') add(row[i][j],col[i][j]+s1,1);
	st=0; ed=s1+s2+1; 
	for(int i=1;i<=s1;i++) add(st,i,1);
	for(int j=1;j<=s2;j++) add(j+s1,ed,1);
	pr2(dicnic());
	return 0;
}


P6517 [CEOI2010 day1] alliances

比较难处理的是"人类"的情况.

这个时候我们考虑拆点,把每个点拆成从横竖两种联盟情况.

#include<map>
#include<queue>
#include<cmath>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define gc getchar()
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int C=77,N=C*C*3,M=N*10,inf=2e9;
template<class o>void qr(o&x) {
	char c=gc;int f=1;x=0;
	while(!isdigit(c)){if(c=='-')f=-1;c=gc;}
	while(isdigit(c))x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o>void qw(o x) {
	if(x/10)qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o>void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x);puts("");
}

int n,m,st,ed,d[N],q[N],ans;
struct edge{int y,next,c;}a[M]; int len=1,last[N],cur[N];
void ins(int x,int y,int z) {a[++len]=(edge){y,last[x],z}; last[x]=len;}
void add(int x,int y,int z) {ins(x,y,z); ins(y,x,0);}

bool bfs() {
	memset(d,0,sizeof d); int l,r; q[l=r=1]=st; d[st]=1;
	memcpy(cur,last,sizeof cur);
	while(l<=r) {
		int x=q[l++];
		for(int k=last[x],y;k;k=a[k].next)
			if(!d[y=a[k].y]&&a[k].c) {
				d[y]=d[x]+1;
				q[++r]=y;
			}
	}
	return d[ed];
}

int dfs(int x,int f) {
	if(x==ed) return f;
	int s=0,t;
	for(int &k=cur[x],y,z;k;k=a[k].next) {
		y=a[k].y; z=a[k].c;
		if(z>0&&d[y]==d[x]+1) {
			s+=t=dfs(y,min(f-s,z));
			a[k].c-=t; a[k^1].c+=t;
			if(s==f) return f;
		}
	}
	if(!s) d[x]=0;
	return s;
}

int dicnic() {
	int s=0;
	while(bfs())
		s+=dfs(st,inf);
	return s;
}

int b[C][C],pos[C][C],cnt,e[C][C][4];
bool v[C][C][4];
char str[C*3][C*3];

const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};

void out() {puts("Impossible!"); exit(0);}

int main() {
	qr(n); qr(m); st=0; ed=n*m*3+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) qr(b[i][j]),pos[i][j]=++cnt;
	int sa=0,sb=0; cnt*=2;
	for(int i=1;i<=n;i++)
		for(int j=1,id;j<=m;j++)
			if(b[i][j]) {
				id=pos[i][j]*2;
				if(!((i+j)&1)) {
					sa+=b[i][j];
					if(b[i][j]^2) add(st,++cnt,b[i][j]),add(cnt,id-1,4),add(cnt,id,4);
					else add(st,id-1,1),add(st,id,1);
					for(int t=0;t<4;t++) {
						int x=i+dx[t],y=j+dy[t];
						if(x&&y&&x<=n&&y<=m) e[i][j][t]=len+1,add(id-t/2,pos[x][y]*2-t/2,1);
					}
				}
				else {
					sb+=b[i][j];
					if(b[i][j]^2) ++cnt,add(id-1,cnt,4),add(id,cnt,4),add(cnt,ed,b[i][j]);
					else add(id-1,ed,1),add(id,ed,1);
				}
			}
	if((sa^sb)||dicnic()^sa) out();
	else {
		for(int i=1;i<=n;i++)	
			for(int j=2-(i&1);j<=m;j+=2)
				for(int t=0;t<4;t++)
					if(e[i][j][t]&&!a[e[i][j][t]].c) 
						v[i][j][t]=v[i+dx[t]][j+dy[t]][t^1]=1;
		memset(str,'.',sizeof str);
		for(int i=1,x=2;i<=n;i++,x+=3)
			for(int j=1,y=2;j<=m;j++,y+=3)
				if(b[i][j]) {
					str[x][y]='O';
					for(int t=0;t<4;t++)
						if(v[i][j][t])
							str[x+dx[t]][y+dy[t]]='X';
				}
		m=3*m+1;
		for(int i=1;i<=3*n;i++) 
			str[i][m]=0,puts(str[i]+1);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值