网络流专题


很明显的一道最大流匹配题目
唯一要注意的点是题目要求是一头牛只能搭配一个饮料和事物
所以要拆点

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define INF 0x7fffffff
const int maxn=505;
const int maxm=200000;
int N,F,D,cnt,S,T;
int head[maxm],num[maxn],maxflow;
struct node{
	int to,next,w;
}edg[maxm];
void add(int u,int v,int w){
	++cnt;
	edg[cnt].to=v;
	edg[cnt].w=w;
	edg[cnt].next=head[u];
	head[u]=cnt;
}
queue<int>Q;
bool bfs(){
	while(!Q.empty()){
	   Q.pop();
	}
	for(int i=S;i<=T+N;i++)num[i]=-1;
	num[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int now=Q.front();Q.pop();
		for(int i=head[now];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&num[to]==-1){
			num[to]=num[now]+1;
			Q.push(to);
		}
		} 
	} 
	return (num[T]!=-1);
}
int dfs(int u,int f){
	if(u==T){
		return f;
	}
	int flow;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&num[to]==num[u]+1&&(flow=dfs(to,min(f,w)))){
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
void dinic(){
	int minn;
	while(bfs()){
		while(minn=dfs(S,INF)){
			maxflow+=minn;
		}
	}
}
int main(){
	scanf("%d%d%d",&N,&F,&D);
	cnt++;
	S=1,T=1+N+F+D+1;
	for(int i=1;i<=F;i++)add(S,i+1,1),add(i+1,S,0);
	for(int i=1;i<=D;i++)add(i+1+F+N,T,1),add(T,i+1+F+N,0);
	for(int i=1;i<=N;i++)add(1+F+i,1+F+N+D+1+i,1),add(1+F+N+D+i+1,1+F+i,0);
	for(int i=1;i<=N;i++){
		int ff,dd;
		scanf("%d%d",&ff,&dd);
		for(int t,j=1;j<=ff;j++){
			scanf("%d",&t);
			add(t+1,1+F+i,1);
			add(1+F+i,t+1,0);
		}
		for(int t,j=1;j<=dd;j++){
			scanf("%d",&t);
			add(1+F+N+D+1+i,1+F+N+t,1);
			add(1+F+N+t,1+F+N+D+1+i,0);
		}
	}
	dinic();
	printf("%d\n",maxflow);
     return 0;
}

其实网络流的题目还是很明显的
你读完题目就知道这个是网络流的题目
建边
超源点S连接每个试题,因为每个试题只能选一次,所以边权为1
每个试题连接多个不同的类型,因为每个试题只能代表一种类型,所以边权为1
每个类型连接超汇点T,因为每个类型有不同的要求数量,所以边权具体题目给出
输出
这个题关键就是输出
记得dinic算法是建立了反向边的,所以只要该类型通向试题的反向边权大于0,就可以输出

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define INF 0x7fffffff
const int maxn=2e3+5;
const int maxm=1e6+5;
int K,N,M,S,T,ans,cnt;
int head[maxm],num[maxn],vis[maxn];
queue<int>Q;
struct node{
	int to,w,next;
}edg[maxm];
void add(int u,int v,int w){
	++cnt;
	edg[cnt].to=v;
	edg[cnt].w=w;
	edg[cnt].next=head[u];
	head[u]=cnt;
}
bool bfs(){
	while(!Q.empty()){
		Q.pop();
	}
	for(int i=S;i<=T;i++)vis[i]=-1;
	vis[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,w=edg[i].w;
			if(w&&vis[to]==-1){
				vis[to]=vis[u]+1;
				Q.push(to);
			}
		}
	}
	return (vis[T]!=-1);
}
int dfs(int u,int f){
	if(u==T)return f;
	int flow=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&vis[to]==vis[u]+1&&(flow=dfs(to,min(w,f)))){
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
void dinic(){
	while(bfs()){
		int ff;
		while(ff=dfs(S,INF)){
			ans+=ff;
		}
	}
}
void print(int u){
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w>0&&to<=S+N)
		printf(" %d",to-S);
	}
}
int main(){
	scanf("%d%d",&K,&N);
	++cnt;S=1;T=1+N+K+1;
	for(int i=1;i<=K;i++){
		scanf("%d",&num[i]),M+=num[i];
		add(S+N+i,T,num[i]);
		add(T,S+N+i,0);
	}
	for(int i=1;i<=N;i++)add(S,S+i,1),add(S+i,S,0);
	for(int i=1;i<=N;i++){
		int p;scanf("%d",&p);
		while(p--){
			int t;scanf("%d",&t);
			add(S+i,S+N+t,1);
			add(S+N+t,S+i,0);
		}
	}
	dinic();
	if(ans<M){
		printf("No Solution!\n");
		return 0;
	}
	else {
		for(int i=1;i<=K;i++){
			printf("%d:",i);
			print(i+S+N);
			printf("\n");
		}
	}
     return 0;
}


分析
很好能get到这个题是最小割的题目
关键的点就是在于朋友之间建边要双向建边,一开始我一直没想明白,做题的时候也很难想到
那就说明对最小割的理解还不够深刻
一般我们建立S->T的单向边是因为只有S流向T,而这个题不一样
尽管A和B两人的意见不一样,那可以B妥协A,A可以妥协B,就是说
S可以流向T,T同样可以流向S

点击查看代码
  #include<bits/stdc++.h>
    #define il inline
    using namespace std;
    const int N=100005,inf=23333333;
    int n,m,s,t=520,h[N],cnt=1,dis[N],ans;
    struct edge{
    int to,net,v;
    }e[N*4];
    il void add(int u,int v,int w)
    {
        e[++cnt].to=v,e[cnt].net=h[u],e[cnt].v=w,h[u]=cnt;
        e[++cnt].to=u,e[cnt].net=h[v],e[cnt].v=0,h[v]=cnt;
    }
    queue<int>q;
    il bool bfs()
    {
        memset(dis,-1,sizeof(dis));
        q.push(s),dis[s]=0;
        while(!q.empty())
        {
            int u=q.front();q.pop();
            for(int i=h[u];i;i=e[i].net)
            if(dis[e[i].to]==-1&&e[i].v>0)dis[e[i].to]=dis[u]+1,q.push(e[i].to);
        }
        return dis[t]!=-1;
    }
    il int dfs(int u,int op)
    {
        if(u==t)return op;
        int used=0;
        for(int i=h[u];i;i=e[i].net)
        {
            int v=e[i].to;
            if(dis[v]==dis[u]+1&&e[i].v>0)
            {
                used=dfs(v,min(op,e[i].v));
                if(!used)continue;
                e[i].v-=used,e[i^1].v+=used;
               return used;
            }
        }
        return 0;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        int x,y;
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            if(x==1)add(s,i,1);
            else add(i,t,1);
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            add(x,y,1),add(y,x,1);
        }
        while(bfs())ans+=dfs(s,inf);
        cout<<ans;
        return 0;
}


这是最小割的经典题目类型
最大权闭合图
建图:
S与每个实验相连,边权为资金费
T与每个仪器相连,边权为消耗费用(相当于已经取绝对值了)
每个实验与相应的仪器相连,边权设为无穷大(因为割边一定不能割实验与仪器的边)
最后就是跑一遍dinic最大流
最大流=最小割
最大权闭合子图的权值和 = max{被选择的点权和} = 正点权和−min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和−最小割
最后一个要解决的问题就是要输出这个最大权闭合图
考虑最后一次bfs,能够到达的点就是在该图内

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define inf 0x7fffffff
#define ll long long
const int maxn=155;
const int maxm=1e6;
int S,T,M,N,cnt,tot,ans;
int head[maxn],dp[maxn];
struct node{
	int to,w,next;
}edg[maxm];
void add(int u,int v,int w){
	edg[++cnt].next=head[u];edg[cnt].to=v;edg[cnt].w=w;head[u]=cnt;
	edg[++cnt].next=head[v];edg[cnt].to=u;edg[cnt].w=0;head[v]=cnt;
}
queue<int>Q;
bool bfs(){
	while(!Q.empty()){
		Q.pop();
	}
	for(int i=S;i<=T;i++)dp[i]=-1;
	dp[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,w=edg[i].w;
			if(w&&dp[to]==-1){
				dp[to]=dp[u]+1;
				Q.push(to);
			}
		}
	}
	return dp[T]!=-1;
}
int dfs(int u,int f){
	if(u==T)return f;
	int flow=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&dp[to]==dp[u]+1&&(flow=dfs(to,min(f,w)))){
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
void dinic(){
	while(bfs()){
		int minn;
		while(minn=dfs(S,inf)){
			ans+=minn;
		}
	}
	
}
int main(){
	++cnt;
	S=0;T=150;
	scanf("%d%d",&M,&N);
    for (int i = 1,c; i <= M; i++) {
		scanf("%d", &c), tot += c;
		add(S, i, c);
		while (getchar() == ' ') {
			scanf("%d", &c);
			add(i, c + M, inf);
		}
	}
	for(int i=1,c;i<=N;i++){
		scanf("%d",&c);
		add(i+M,T,c);
	}
	dinic();
	for (int i = 1; i <= M; i++) if (dp[i]!=-1) cout << i << ' '; puts("");
	for (int i = 1; i <= N; i++) if (dp[i + M]!=-1) cout << i << ' '; puts("");
	printf("%d\n",tot-ans);
     return 0;
}


又是一道最大流模板
因为每个人只能选一个房间,所以对每个人进行拆点,最后套个dinic模板即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define open(s) freopen( s".in", "r", stdin ), freopen( s".out", "w", stdout )
#define MAXN 405
#define MAXM 40005

int n, p, q;
int hd[MAXN], nxt[MAXM << 1], to[MAXM << 1], val[MAXM << 1], tot(1);
int ans, dis[MAXN];
queue<int> Q;

int x, y;
int S, T;

void Add( int x, int y, int z ){ nxt[++tot] = hd[x]; hd[x] = tot; to[tot] = y; val[tot] = z; }

bool BFS(){
	while( !Q.empty() ) Q.pop();
	memset( dis, 0, sizeof dis );
	Q.push(S); dis[S] = 1;
	while( !Q.empty() ){
		x = Q.front(); Q.pop();
		for ( int i = hd[x]; i; i = nxt[i] )
			if ( val[i] && !dis[to[i]] ){
				dis[to[i]] = dis[x] + 1;
				Q.push( to[i] );
				if ( to[i] == T ) return 1;
			}
	}
	return 0;
}

int DFS( int x, int fl ){
	if ( x == T ) return fl;
	int  k;
	for ( int i = hd[x]; i ; i = nxt[i] ){
		if ( val[i] && dis[to[i]] == dis[x] + 1 ){
			k = DFS( to[i], min( fl, val[i] ) );
			if(!k)continue;
			val[i] -= k; val[i^1] += k; 
			return k;
		}
	}
	return 0;
}

int main(){
	scanf( "%d%d%d", &n, &p, &q );
	S = 0; T = 1 + n + n + p + q;
	for ( int i = 1; i <= n; ++i ) Add( i, i + n, 1 ), Add( i + n, i, 0 );
	for ( int i = 1; i <= p; ++i ) Add( S, i + n + n, 1 ), Add( i + n + n, S, 0 );
	for ( int i = 1; i <= q; ++i ) Add( i + n + n + p, T, 1 ), Add( T, i + n + n + p, 0 );
	
	for ( int i = 1; i <= n; ++i )
		for ( int j = 1; j <= p; ++j ){
			int t; scanf( "%d", &t );
			if ( t ) Add( j + n + n, i, 1 ), Add( i, j + n + n, 0 );
		}
	for ( int i = 1; i <= n; ++i )
		for ( int j = 1; j <= q; ++j ){
			int t; scanf( "%d", &t );
			if ( t ) Add( i + n, j + n + n + p, 1 ), Add( j + n + n + p, i + n, 0 );
		}
	int t;
	while( BFS() )
		while( ( t = DFS( S, 0x7f7f7f7f ) ) > 0 ) ans += t;
	printf( "%d\n", ans );
	return 0;
}


这个题目特别点在于要求删点
那么就拆点:
x拆为(x,x')容量为1
原图边x->y 为无向边,所以变为x'->y和y'->x容量均为无穷大
割点x相当于割掉x->x'这条边
因为要经过x连通的边没有了x->x'这条边都联通不了
因为至少满足两个点不连通整个图就不联通了
最后枚举源点和汇点即可

点击查看代码
const int N = 100, M = 5e4+7, INF = 0x3f3f3f3f;
int s1,t1,n,m;
int head[N<<1],ver[M],nex[M],edge[M],tot;
int a[N * N],b[N * N],deep[N<<1];

inline void add(int x,int y,int z){//正边反边
    ver[++tot] = y;edge[tot] = z;
    nex[tot] = head[x];head[x] = tot;
    ver[++tot] = x;edge[tot] = 0;
    nex[tot] = head[y];head[y] = tot;
}

inline bool bfs(){
    memset(deep,0,sizeof deep);
    queue<int>q;
    q.push(s1);
    deep[s1] = 1;//分层
    while(q.size()){
        int x = q.front();
        q.pop();
        for(int i = head[x];i;i = nex[i]){
            int y = ver[i],z = edge[i];//剩余容量>0才属于残量网络
            if(z > 0 && !deep[y]){//不只是更新deep数组,是在残量网络上更新deep数组
                q.push(y);
                deep[y] = deep[x] + 1;
                if(y == t1)return true;
            }
        }
    }
    return false;
}

inline int dinic(int x,int flow){
    if(x == t1)return flow;
    int res = flow;
    for(int i = head[x];i && res;i = nex[i]){
        int y = ver[i],z = edge[i];
        if(z > 0 && (deep[y] == deep[x] + 1)){
            int k = dinic(y,min(res,z));
            if(!k)deep[y] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            res -= k;
        }
    }
    return flow - res;
}

int main(){
    while(cin>>n>>m){
        for(int i = 0;i < m;++i){
            char str[20];
            scanf("%s",str);
            a[i] = b[i] = 0;
            int j;
            for(j = 1;str[j] != ',';j++)
                a[i] = a[i] * 10 + (str[j] - '0');
            for(j++;str[j] != ')';j++)
                b[i] = b[i] * 10 + (str[j] - '0');
        }
        int ans = INF;
        for (s1 = 0; s1 < n; s1++)
		for (t1 = 0; t1 < n; t1++)
        if(s1 != t1){
            memset(head,0,sizeof head);
            tot = 1;
            int maxflow = 0;
            for(int i = 0;i < n;++i){
                if(i == s1 || i == t1)//i是入点,i+n是出点
                     add(i,i + n,INF);//防止被割断
                else add(i,i + n,1);
            }
            for(int i = 0;i < m;++i){
                add(a[i] + n,b[i],INF);//不能割
                add(b[i] + n,a[i],INF);
            }
            while(bfs()){
                int num;
                while((num = dinic(s1,INF)))
                    maxflow += num;
            }
            ans = min(ans,maxflow);
        }
        if(n <= 1 || ans == INF)ans = n;
        cout<<ans<<endl;
    }
    return 0;
}



很明显的一道割点题,最后跑一遍最大流就好

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int sss=0,www=1;
	char chh=getchar();
	while(chh<'0'||chh>'9'){
		if(chh=='-') www=-1;
		chh=getchar();
	}
	while(chh>='0'&&chh<='9'){
		sss=sss*10+chh-'0';
		chh=getchar();
	}
	return sss*www;
}
int n,m,q,s,t;
bool iscut[6005];
int depth[6005];
int head[6005],cnt=1;
struct dj{
	int to,w,next;
}edg[1000005];
void add(int u,int v,int w){
	edg[++cnt].next=head[u];edg[cnt].to=v;edg[cnt].w=w;head[u]=cnt;
	edg[++cnt].next=head[v];edg[cnt].to=u;edg[cnt].w=0;head[v]=cnt;
}
bool bfs(){
	for(int i=s;i<=t;i++)depth[i]=-1;
	depth[s]=1;
	queue<int> q; q.push(s);
	while(!q.empty()){
		int x=q.front(); q.pop();
		for(register int i=head[x];i;i=edg[i].next){
			int u=edg[i].to;
			if(edg[i].w&&depth[u]==-1){
				depth[u]=depth[x]+1;
				q.push(u);
			}
		}
	}
	return depth[t]!=-1;
}
int dfs(int now,int flow){
	if(now==t) return flow;
	for(register int i=head[now];i;i=edg[i].next){
		int u=edg[i].to;
		if(edg[i].w&&depth[u]==depth[now]+1){
			int tmp=dfs(u,min(edg[i].w,flow));
			if(!tmp)continue;
			edg[i].w-=tmp; edg[i^1].w+=tmp;
			return tmp;
		}
	}
	return 0;
}
int Dinic(){
	int ans=0;
	while(bfs()) ans+=dfs(s,1e9);
	return ans;
}
int main(){
	n=read(),m=read(),q=read();
	s=1,t=2*n+1;
	add(1,n+1,1e9); //1节点不能被割掉 
	int u,v;
	for(register int i=1;i<=m;i++){
		//图中所给的边 
		u=read(),v=read();
		add(u+n,v,1e9); 
		add(v+n,u,1e9); 
	}
	for(register int i=1;i<=q;i++){
		u=read(); iscut[u]=true;
		add(u,u+n,1e9);
		add(u+n,t,1e9);
	}
	for(register int i=1;i<=n;i++){
		if(!iscut[i]){//可以被割掉 
			add(i,i+n,1); 
		}
	}
	printf("%d",Dinic());
    return 0;
}

观察又是流动型问题 考虑网络流

最长时间最小 很明显的二分答案

建边 将牛棚拆点

S与每个牛棚i相连,边权为初始牛的数量

T与每个牛棚n+i相连,边权为最大容量

floyed预处理两点的最短路 二分的答案为maxx

如果dis[i,j]最短路<maxx

建边i->j 边权为inf 特别的i==j的时候 也要建边 边权也为inf

再跑一遍dinic即可

check函数判断最大流和总和牛是否相等

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e18
const int maxn=2005;
const int maxm=15005;
ll dis[maxn][maxn],have[maxn],hold[maxn];
int n,m,cnt,S,T,tot,ans;
int head[maxn*2],dp[maxn*2];
struct node{
	int to,next;
	ll w;
}edg[maxn*maxn*10];
void add(int u,int v,ll w){
	edg[++cnt].next=head[u];edg[cnt].w=w;edg[cnt].to=v;head[u]=cnt;
	edg[++cnt].next=head[v];edg[cnt].w=0;edg[cnt].to=u;head[v]=cnt;
}
void floyed();
void init(){
	cnt=1;
	for(int i=S;i<=T;i++)head[i]=0;
}
void rebuild(ll maxx){
	for(int i=1;i<=n;i++){
		add(S,i,have[i]);
		add(i+n,T,hold[i]);
		for(int j=1;j<=n;j++)
		if(i==j||dis[i][j]<=maxx)
		add(i,n+j,inf);
	}
}
queue<int>Q;
bool bfs(){
	while(!Q.empty())Q.pop();
	for(int i=S;i<=T;i++)dp[i]=-1;
	dp[S]=1;Q.push(S);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to;ll w=edg[i].w;
			if(w&&dp[to]==-1){
				dp[to]=dp[u]+1;
				Q.push(to);
			}
		}
	}
	return dp[T]!=-1;
}
ll dfs(int u,ll f){
	if(u==T)return f;
	ll flow;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to;ll w=edg[i].w;
		if(w&&dp[to]==dp[u]+1){
			flow=dfs(to,min(f,w));
			if(!flow)continue;
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
ll dinic(){
	ll res=0;
	while(bfs())res+=dfs(S,inf);
	return res;
}
bool ck(ll aa){
	return aa==tot;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>have[i]>>hold[i],tot+=have[i];
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	dis[i][j]=inf;
	for(ll i=1,ww,uu,vv;i<=m;i++){
		cin>>uu>>vv>>ww;
		dis[uu][vv]=min(dis[uu][vv],ww);
		dis[vv][uu]=min(dis[vv][uu],ww);
	}
	floyed();
	S=0,T=n<<1|1;
	ll mid,l=0,r=inf;
	while(l<=r){
		mid=(l+r)>>1;
		init();
		rebuild(mid);
		if(ck(dinic()))
		r=mid-1,ans=mid;
		else l=mid+1;
	}
	if(ans)cout<<ans<<endl;
	else cout<<-1<<endl; 
     return 0;
}
void floyed(){
	for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
	if(i!=k)
	for(int j=1;j<=n;j++)
	if(j!=i&&j!=k)
	dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}

这个题没啥好说的 就是妥妥的最大流模板题 唯一要注意的是 re可能是数组越界 也有可能分母为0 !!!

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=2005;
const int maxm=20005;
int dp[maxn],head[maxn];
int cnt=1,S,T,N,M,X;
struct node{
	int to,next,w;
}edg[maxm];
void add(int u,int v,int w){
	edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;edg[cnt].w=w;
	edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;edg[cnt].w=0;
}
queue<int>Q;
bool bfs(){
	while(!Q.empty())Q.pop();
	for(int i=S;i<=T;i++)dp[i]=-1;
	dp[S]=1;Q.push(S);
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,w=edg[i].w;
			if(w&&dp[to]==-1){
				dp[to]=dp[u]+1;
				Q.push(to);
			}
		}
	}
	return dp[T]!=-1;
}
int dfs(int u,int f){
	if(u==T)return f;
	int flow=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&dp[to]==dp[u]+1){
		   flow=dfs(to,min(f,w));
		   if(!flow)continue;
		   edg[i].w-=flow;
		   edg[i^1].w+=flow;
		   return flow;	
		}
		 
	}
	return 0;
}
ll dinic(){
	ll res=0;
	while(bfs())res+=dfs(S,inf);
    return res;
}
int main(){
	cin>>N>>M>>X;
	S=1,T=N;
	for(int i=1;i<=M;i++){
		int uu,vv,ww;
		cin>>uu>>vv>>ww;
		add(uu,vv,ww);
	}
	ll ans=dinic();
	if(!ans){
		cout<<"Orz Ni Jinan Saint Cow!"<<endl;
		return 0;
	}
	ll sum=X/ans;
	if(X%ans)sum++;
	cout<<ans<<" "<<sum<<endl;
     return 0;
}

假设先取所有的格子 现在我们目标就是减去最小的 使得剩下的和最大

现在将(i+j)%2分为奇偶两种集合 和(i,j)相邻的奇偶性一定和(i,j)不同

我们将S与偶数相连 边权为点权 T与奇数相连 边权为点权

再将偶数与相邻奇数相连 边权为inf 因为这条边时不能割的

最后答案就是ans-dinic

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define inf 2e9
#define ll long long
const int maxn=100005;
int m,n,S,T,cnt=1,ans;
int head[maxn],dp[maxn],val[maxn];
int d[5][2];
struct node{
	int to,next,w;
}edg[maxn*10];
void add(int u,int v,int w){
	edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;edg[cnt].w=w;
	edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;edg[cnt].w=0;
}
queue<int>Q;
bool bfs(){
	while(!Q.empty())Q.pop();
	for(int i=S;i<=T;i++)dp[i]=-1;
	dp[S]=1;Q.push(S);
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,w=edg[i].w;
			if(w&&dp[to]==-1){
				dp[to]=dp[u]+1;
				Q.push(to);
			}
		}
	}
	return dp[T]!=-1;
}
int dfs(int u,int f){
	if(u==T)return f;
	int flow=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&dp[to]==dp[u]+1){
			flow=dfs(to,min(f,w));
			if(!flow)continue;
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
int dinic(){
	int res=0;
	while(bfs())res+=dfs(S,inf);
	return res;
}
bool ck(int x,int y){
	if(x<1||x>m)return false;
	if(y<1||y>n)return false;
	return true;
} 
int main(){
	cin>>m>>n;
	for(int vv,i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
	cin>>vv,val[(i-1)*n+j]=vv,ans+=vv;
	S=0,T=n*m+1;
	for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
	if((i+j)&1)add(S,(i-1)*n+j,val[(i-1)*n+j]);
	else add((i-1)*n+j,T,val[(i-1)*n+j]);
	d[1][0]=-1,d[1][1]=0;
	d[2][0]=0,d[2][1]=-1;
	d[3][0]=1,d[3][1]=0;
	d[4][0]=0,d[4][1]=1;
	for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
	if((i+j)&1)
		for(int k=1;k<=4;k++){
			int xx=i+d[k][0],yy=j+d[k][1];
			if(!ck(xx,yy))continue;
			add((i-1)*n+j,(xx-1)*n+yy,inf);
		}
	cout<<ans-dinic()<<endl; 
     return 0;
}

因为是无向图 所以要建立双向边 再跑一遍最大流 很容易tle要加各种优化

点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <cctype>
using namespace std;
inline void read(int &x) {
    x = 0; char c = getchar();
    while(!isdigit(c)) c = getchar();
    while(isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = getchar();
}
#define MAXN 1003
struct node{
    int fr, to, va, nxt;
}edge[MAXN * MAXN * 6];
int head[MAXN * MAXN], cnt;
inline void add_edge(int u, int v, int w) {
    edge[cnt].fr = u, edge[cnt].to = v, edge[cnt].va = w;
    edge[cnt].nxt = head[u], head[u] = cnt++;
    edge[cnt].fr = v, edge[cnt].to = u, edge[cnt].va = w;
    edge[cnt].nxt = head[v], head[v] = cnt++; //反向边初始化
}
int st, ed, rk[MAXN * MAXN];
int BFS() {
    queue<int> q;
    memset(rk, 0, sizeof rk);
    rk[st] = 1;
    q.push(st);
    while(!q.empty()) {
        int tmp = q.front();
        //cout<<tmp<<endl;
        q.pop();
        for(int i = head[tmp]; i != -1; i = edge[i].nxt) {
            int o = edge[i].to;
            if(rk[o] || edge[i].va <= 0) continue;
            rk[o] = rk[tmp] + 1;
            q.push(o);
        }
    }
    return rk[ed];
}
int dfs(int u, int flow) {
    if(u == ed) return flow;
    for(int i = head[u]; i != -1 && add < flow; i = edge[i].nxt) {
        int v = edge[i].to;
        if(rk[v] != rk[u] + 1 || !edge[i].va) continue;
        int tmpadd = dfs(v, min(edge[i].va, flow));
        if(!tmpadd) {  //重要!就是这里!
            rk[v] = -1;
            continue;
        }
        edge[i].va -= tmpadd, edge[i ^ 1].va += tmpadd;
        return tmpadd;
    }
    return 0;
}
int ans;
void dinic() {
    while(BFS()) ans += dfs(st, 0x3fffff); 
}
int n, m;
inline int gethash(int i, int j) {
    return (i - 1) * m + j;
}
int main() {
    memset(head, -1, sizeof head);
    read(n), read(m);
    int tmp;
    st = 1, ed = gethash(n, m);
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j < m; ++j)
            read(tmp), add_edge(gethash(i, j), gethash(i, j + 1), tmp);
    }
    for(int i = 1; i < n; ++i) {
        for(int j = 1; j <= m; ++j) 
            read(tmp), add_edge(gethash(i, j), gethash(i + 1, j), tmp);
    }
    for(int i = 1; i < n; ++i) {
        for(int j = 1; j < m; ++j) 
            read(tmp), add_edge(gethash(i, j), gethash(i + 1, j + 1), tmp);
    }
    dinic();
    cout<<ans<<endl;
    return 0;
} 

费用流的板子题目 和dinic差别在于 bfs换成了spfa dfs过程变成了从T回溯的过程

先回顾一下spfa:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e5+5;
int cnt=1,n,m,S,T,maxflow,mincost;
struct node{
	int to,next,flow,dis;
}edg[maxn];
bool vis[maxn];
int dis[maxn],last[maxn],flow[maxn],pre[maxn],head[maxn];
void add(int u,int v,int flow,int dis){
	edg[++cnt].next=head[u];
	edg[cnt].to=v;
	head[u]=cnt;
	edg[cnt].flow=flow;
	edg[cnt].dis=dis;
}
queue<int>Q;
bool spfa(){
	memset(dis,0x7f,sizeof(dis));
	memset(flow,0x7f,sizeof(flow));
	memset(vis,0,sizeof(vis));
	Q.push(S);
	vis[S]=1;dis[S]=0;pre[T]=-1;
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to;
			if(edg[i].flow&&dis[to]>dis[u]+edg[i].dis){
				dis[to]=dis[u]+edg[i].dis;
				pre[to]=u;
				last[to]=i;
				flow[to]=min(flow[u],edg[i].flow);
				if(!vis[to]){
					vis[to]=1;
					Q.push(to);
				}
			}
		}
	}
	return pre[T]!=-1;
} 
void dd(){
	while(spfa()){
		int now=T;
		maxflow+=flow[T];
		mincost+=flow[T]*dis[T];
		while(now!=S){
			edg[last[now]].flow-=flow[T];
			edg[last[now]^1].flow+=flow[T];
			now=pre[now];
		}
	}
}
int main(){
	cin>>n>>m>>S>>T;
	for(int i=1;i<=m;i++){
		int xx,yy,zz,ff;
		scanf("%d%d%d%d",&xx,&yy,&zz,&ff);
		add(xx,yy,zz,ff);
		add(yy,xx,0,-ff);
	}
	dd();
	cout<<maxflow<<" "<<mincost<<endl;
     return 0;
}

很好很经典的一道题

我开始一直没想明白为什么S要和旧毛巾连ri的边 第一天的旧毛巾是哪里来的 第一天不是只能买新毛巾嘛?

观察图才发现 点1+n(第一天的新毛巾来源只有买,没有其他的来源了) S向旧毛巾连ri的边只是保证每天用完肯定是会有ri的旧毛巾的

这个建图确实不好想 但是真的好巧妙

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=2005;
int n,S,T;
int head[maxn<<1],cnt=1;
int d[maxn<<1],vis[maxn<<1],last[maxn<<1],pre[maxn<<1],flow[maxn<<1],r[maxn];
ll mincost;
struct node{
	int to,next;
	int f,cost;
}edg[maxn*maxn];
void add(int u,int v,int f,int w){
	edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;edg[cnt].cost=w;edg[cnt].f=f;
	edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;edg[cnt].cost=-w;edg[cnt].f=0;
} 
queue<int>Q;
bool spfa(){
	memset(d,0x7f,sizeof(d));
	memset(flow,0x7f,sizeof(flow));
	memset(vis,0,sizeof(vis));
	vis[S]=1;d[S]=0;Q.push(S);pre[T]=-1;
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();vis[u]=0;
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,f=edg[i].f,cost=edg[i].cost;
		    if(f&&d[to]>d[u]+cost){
		    	d[to]=d[u]+cost;
		    	pre[to]=u;
		    	last[to]=i;
		    	flow[to]=min(flow[u],f);
		    	if(!vis[to]){
		    		vis[to]=1;
		    		Q.push(to);
				}
			}	
		}
	}
	return pre[T]!=-1;
}
void calc(){
	while(spfa()){
		int now=T;
		mincost+=flow[T]*d[T];
		while(now!=S){
			edg[last[now]].f-=flow[T];
			edg[last[now]^1].f+=flow[T];
			now=pre[now];
		}
	}
}
int main(){
	cin>>n;
	S=0,T=(n<<1)+1;
	for(int i=1;i<=n;i++)cin>>r[i];
	int pp,mm,ff,nn,ss;
	cin>>pp>>mm>>ff>>nn>>ss;
	for(int i=1;i<=n;i++){
		add(S,i,r[i],0);
		add(i+n,T,r[i],0);
		add(S,n+i,inf,pp);
		if(i<n)add(i,i+1,inf,0);
		if(i+mm<=n)add(i,i+n+mm,inf,ff);
		if(i+nn<=n)add(i,i+n+nn,inf,ss);
	}
	calc();
	cout<<mincost<<endl;
     return 0;
}

这个题题很明显的求最小费用最大流和最大费用最大流

求最大费用最大流只需要将边权都取为相反数就好,为什么呢?

以前边权是求最小正值 现在边权是求小负值 就是负数的绝对值最大 就是最长路

因为要求两次 所以进行一次后就直接将边复原再跑一次就好了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=2005;
int head[maxn],cnt=1;
struct node{
	int to,next,w,f;
}edg[maxn*maxn*10];
void add(int u,int v,int f,int w){
	edg[++cnt].w=w;edg[cnt].f=f;edg[cnt].next=head[u];head[u]=cnt;edg[cnt].to=v;
	edg[++cnt].w=-w;edg[cnt].f=0;edg[cnt].next=head[v];head[v]=cnt;edg[cnt].to=u;
}
int vis[maxn],flow[maxn],d[maxn],pre[maxn],last[maxn],dd[maxn];
int n,m,S,T;
queue<int>Q;
bool spfa(){
	memset(vis,0,sizeof(vis));
	memset(flow,0x7f,sizeof(flow));
	memset(d,0x7f,sizeof(d));
	pre[T]=-1;vis[S]=1;Q.push(S);d[S]=0;
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();vis[u]=0;
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,f=edg[i].f,w=edg[i].w;
			if(f&&d[to]>d[u]+w){
				d[to]=d[u]+w;
				pre[to]=u;
				last[to]=i;
				flow[to]=min(flow[u],f);
				if(!vis[to]){
					Q.push(to);
					vis[to]=1;
				}
			}
		}
	}
	return pre[T]!=-1;
}
int calc(){
	int cost=0;
	while(spfa()){
		int now=T;
		cost+=flow[T]*d[T];
		while(now!=S){
			edg[last[now]].f-=flow[T];
			edg[last[now]^1].f+=flow[T];
			now=pre[now];
		}
	}
	return cost;
}
int main(){
	cin>>m>>n;
	S=0,T=n*m+1;
	for(int ai,i=1;i<=m;i++)
	cin>>ai,add(S,i,ai,0);
	for(int bi,i=1;i<=n;i++)
	cin>>bi,add(m+i,T,bi,0);
	for(int xx,i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
	cin>>xx,add(i,m+j,inf,xx);
    cout<<calc()<<endl;
    for(int i=2;i<cnt;i+=2){
    	edg[i].f+=edg[i^1].f;
    	edg[i^1].f=0;
    	edg[i].w=-edg[i].w;
    	edg[i^1].w=-edg[i^1].w;
	}
	cout<<-calc()<<endl;
	return 0;
}

点击查看代码
#include<bits/stdc++.h>
#define inf 1000000007
#define N 2000005
#define M 505
using namespace std;
struct Edge{
    int u,v,next,f;
}G[N];
int head[N],tot=0,a[M],dp[M],n,len,s,t,ans;
void addedge(int u,int v,int f){
    G[tot].u=u;G[tot].v=v;G[tot].f=f;G[tot].next=head[u];head[u]=tot++;
    G[tot].u=v;G[tot].v=u;G[tot].f=0;G[tot].next=head[v];head[v]=tot++;
}
int level[100*M];
bool bfs(int s,int t){
    memset(level,0,sizeof(level));
    queue<int>q;q.push(s);level[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        if(u==t)return 1;
        for(int i=head[u];i!=-1;i=G[i].next){
            int v=G[i].v,f=G[i].f;
            if(level[v]==0&&f)q.push(v),level[v]=level[u]+1;
        }
    }
    return 0;
}
int dfs(int u,int maxf,int t){
    if (u==t)return maxf;
    int rat=0;
    for (int i=head[u];i!=-1&&rat<maxf;i=G[i].next){
        int v=G[i].v;int f=G[i].f;
        if (level[v]==level[u]+1&&f){
            int Min=min(maxf-rat,f);
            f=dfs(v,Min,t);
            G[i].f-=f;G[i^1].f+=f;rat+=f;
        }
    }
    if (!rat)level[u]=N;
    return rat;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]),dp[i]=1;
    for(int i=1;i<=n;i++)
    for(int j=1;j<i;j++)
    if(a[j]<=a[i])dp[i]=max(dp[i],dp[j]+1);
    for(int i=1;i<=n;i++)len=max(len,dp[i]);
    printf("%d\n",len);
    if(len==1){
    	cout<<n<<endl<<n<<endl;
    	return 0;
	}
    s=0;t=5000;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)if(dp[i]==1)addedge(s,i,1);
    for(int i=1;i<=n;i++)if(dp[i]==len)addedge(i+n,t,1);
    for(int i=1;i<=n;i++)addedge(i,i+n,1);
    for(int i=1;i<=n;i++)
    for(int j=1;j<i;j++)
    if(a[j]<=a[i]&&dp[j]+1==dp[i])addedge(j+n,i,1);
    while(bfs(s,t))ans+=dfs(s,inf,t);printf("%d\n",ans);
    addedge(1,1+n,inf);addedge(s,1,inf);
    if(dp[n]==len)addedge(n,n*2,inf),addedge(n*2,t,inf);
    while(bfs(s,t))ans+=dfs(s,inf,t);
    printf("%d\n",ans);
    return 0;
}

这个题目恶心就在于中间建边的过程

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int N = 751 * 15 + 10;
const int M = (N + 750 + 20 * 751 + 10) * 2;
const int INF = 1e9; 

int n, m, k, S, T;
struct Edge
{
	int to, nxt, flow;
}line[M];
int fist[N], idx;
int cur[N], d[N];
struct Ship
{
	int h, r, id[30];
}ships[30];
int fa[30];

int find(int x)
{
	if(fa[x] != x) fa[x] = find(fa[x]);
	return fa[x];
}

int get(int i, int day)
{
	return day * (n + 2) + i;	
}

void add(int x, int y, int z)
{
	line[idx] = {y, fist[x], z};
	fist[x] = idx ++;
	line[idx] = {x, fist[y], 0};
	fist[y] = idx ++;
}

bool bfs()
{
	queue<int> q;
	memset(d, -1, sizeof d);
	q.push(S), d[S] = 0, cur[S] = fist[S];
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int i = fist[u]; i != -1; i = line[i].nxt)
		{
			int v = line[i].to;
			if(d[v] == -1 && line[i].flow)
			{
				d[v] = d[u] + 1;
				cur[v] = fist[v];
				if(v == T) return 1;
				q.push(v);
			}
		}
	}	
	return 0;
} 

int find(int u, int limit)
{
	if(u == T) return limit;
	int flow = 0;
	for(int i = cur[u]; i != -1 && flow < limit; i = line[i].nxt)
	{
		cur[u] = i;
		int v = line[i].to;
		if(d[v] == d[u] + 1 && line[i].flow)
		{
			int t = find(v, min(line[i].flow, limit - flow));
			if(!t) d[v] = -1;
			line[i].flow -= t;
			line[i ^ 1].flow += t;
			flow += t;
		}
	}
	return flow;
}

int dinic()
{
	int res = 0, flow;
	while(bfs()) while(flow = find(S, INF)) res += flow;
	return res;	
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	S = N - 2, T = N - 1;
	memset(fist, -1, sizeof fist);
	for(int i = 0; i < 30; ++ i) fa[i] = i;
	for(int i = 0; i < m; ++ i)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		ships[i] = {a, b};
		for(int j = 0; j < b; ++ j)
		{
			int id;
			scanf("%d", &id);
			if(id == -1) id = n + 1; 
			ships[i].id[j] = id;
			if(j) 
			{
				int x = ships[i].id[j - 1];
				fa[find(x)] = find(id);
			}
		}
	}
	if(find(0) != find(n + 1)) puts("0");
	else
	{
		add(S, get(0, 0), k);
		add(get(n + 1, 0), T, INF);
		int day = 1, res = 0; 
		while(1)
		{
			add(get(n + 1, day), T, INF);
			for(int i = 0; i <= n + 1; ++ i)
				add(get(i, day - 1), get(i, day), INF);
			for(int i = 0; i < m; ++ i)
			{
				int r = ships[i].r;
				int a = ships[i].id[(day - 1) % r];
				int b = ships[i].id[day % r];
				add(get(a, day - 1), get(b, day), ships[i].h);
			} 
			res += dinic();
			if(res >= k) break;
			++ day;
		}
		printf("%d\n", day);
	}
	return 0;
}

最小路径覆盖数=顶点数-最大匹配

为什么?

我们首先将原图用n条路径覆盖,每条边只经过每个节点。

现在尽量合并更多的路径(即将两个路径通过一条边首尾相连)。

可以知道,每合并两条路径,图中的路径覆盖数就会减少1。

所以我们只需要利用网络流合并相关的路径即可

本题麻烦就在 输出路径 只要我们找起点 之后在残留网络里面走就好

问题变为维护起点 可以在dfs遍历的过程中记录 也可以最后并查集记录

我这里用的并查集 合并两个的时候 一定是fa[find(to)]=find(u) 顺序不能变 因为一定要保证起点为fa[i]=i

判断起点就直接fa[i]=i即可

还有数组能开大点就开大点

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define inf 1e9
const int maxn=3550;
const int maxm=(maxn<<2)+6e5;
int S,T,n,m;
int dp[maxn],vis[maxn],fa[maxn];
int head[maxn],cnt=1;
int find(int x){
	if(fa[x]!=x)return fa[x]=find(fa[x]);
	return x;
}
struct node{
	int to,next,f;
}edg[maxn];
void add(int u,int v,int f){
	edg[++cnt].next=head[u];head[u]=cnt;edg[cnt].f=f;edg[cnt].to=v;
	edg[++cnt].next=head[v];head[v]=cnt;edg[cnt].f=0;edg[cnt].to=u;
}
queue<int>Q;
bool bfs(){
	while(!Q.empty())Q.pop();
	memset(dp,-1,sizeof(dp));
	dp[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,f=edg[i].f;
			if(f&&dp[to]==-1){
				dp[to]=dp[u]+1;
				Q.push(to);
			}
		}
		
	}
	return dp[T]!=-1;
}
int dfs(int u,int flow){
	if(u==T)return flow;
	int tot=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,f=edg[i].f;
		if(dp[to]==dp[u]+1&&f){
			int t=dfs(to,min(f,flow-tot));
			if(!t)continue;
			edg[i].f-=t;
			edg[i^1].f+=t;
			tot+=t; 
		}
	}
	return tot;
}
void output(int x){
	cout<<x<<" ";
	for(int i=head[x];i;i=edg[i].next)
		if(edg[i].to>n&&edg[i].to<T&&edg[i].f==0){
			output(edg[i].to-n);
		}
}
int dinic(){
	int res=0;
	while(bfs())
		res+=dfs(S,inf);
	for(int i=S;i<=T;i++)
	fa[i]=i;
	for(int i=1;i<=n;i++)
	for(int j=head[i];j;j=edg[j].next){
		if(edg[j].f==0&&edg[j].to<T&&edg[j].to>n)
		fa[find(edg[j].to-n)]=find(i);
	}
	for(int i=1;i<=n;i++)
	if(fa[i]==i)
	output(i),cout<<endl; 
    return res;
}
int main(){
	cin>>n>>m;
	S=0;T=2*n+1;
	for(int i=1;i<=n;i++)
	add(S,i,1),add(i+n,T,1);
	for(int i=1;i<=m;i++){
		int uu,vv;
		cin>>uu>>vv;
		add(uu,vv+n,1);
	}
	cout<<n-dinic();
     return 0;
}

posted @   wzx_believer  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示