网络流 24 题
P2756 飞行员配对方案问题#
题目很明确的告诉我们需要将一个歪果仁和一个英国人搭配起来,问你最多的搭配是多少,我们设一个超级源点和超级汇点,把所有的外国人都连到超级源点上,然后把所有的英国人都连到超级汇点上,然后把题目给的能配对的两人连边,跑 dinic 即可,最后从编号
code:
#include<bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;
int n,m,val[N],to[N*2],nxt[N*2],head[N*2];//val存放边的最大流
int cnt=1,ans,q[N*2],l,r,vis[N*2],frm[N*2];//q用于模拟队列
inline void add(int u,int v,int w)//建边
{
val[++cnt]=w;//存最大流量
frm[cnt]=u;//存起点
to[cnt]=v;//存终点
nxt[cnt]=head[u];//存头节点
head[u]=cnt;//更新
}
int bfs()
{
memset(vis,0,sizeof(vis));//清空vis
q[r=l=1]=0;//起点入列
vis[0]=1;//标记深度为1
while(l<=r)//只要队列不空
{
int u=q[l++];//取出队头元素
for(int i=head[u];i;i=nxt[i])//枚举每一条与u相连的边
{
int v=to[i];//取出终点
if(val[i]&&!vis[v])//如果当前边最大流量不为0并且没有搜过此点
{
vis[v]=vis[u]+1;//标记深度
q[++r]=v;//入列
}
}
}
return vis[n+1];//返回汇点的深度
}
int dfs(int u,int in)//深搜找最大流
{
if(u==n+1)//如果到达了汇点
return in;//直接返回流入的流量的值
int out=0;//out存放流出的流量的值
for(int i=head[u];i&∈i=nxt[i])//遍历每一个与u相连的边,保证流入的流量大小不为0
{
int v=to[i];//取出终点
if(val[i]&&vis[v]==vis[u]+1)//如果当前边最大流量不为0并且是向汇点流去
{
int res=dfs(v,min(val[i],in));//计算当前边流的流量
val[i]-=res;//正向边减去
val[i^1]+=res;//反向边加上
in-=res;//流入的流量减去
out+=res;//流出的流量加上
}
}
if(out==0)//如果当前流出的流量大小为0
vis[u]=0;//标记u,下次不搜了
return out;//返回out的值
}
signed main()
{
cin>>m>>n;
int u,v;
for(int i=1;i<=m;i++)
add(0,i,1),//预处理把1-m连到0点上
add(i,0,0);
for(int i=m+1;i<=n;i++)
add(i,n+1,1),//预处理把m+1-n连到n+1点上
add(n+1,i,0);
while(cin>>u>>v)
{
if(u==-1&&v==-1)break;
add(u,v,1);//存边
add(v,u,0);
}
while(bfs())
ans+=dfs(0,1e18);//求最大流
cout<<ans<<endl;
for(int i=n*2+2;i<=cnt;i++)//输出答案
{
if(val[i]==0&&frm[i]<=m)
cout<<frm[i]<<' '<<to[i]<<endl;//输出方案数
}
return 0;//好习惯
}
P3254 圆桌问题#
这个题目比较好想,就是建一个超级源点和一个超级汇点,源点与每一个单位的人数建边,在这里把总人数都给加起来存到
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,s,t,ans,sum,head[N],cnt=1;
struct sb{int u,v,w,next;}e[N<<2];
int dis[N],r[N],c[N];
inline void add(int u,int v,int w)
{
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
inline int bfs()
{
memset(dis,0,sizeof dis);
queue<int>q;
dis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dis[v]&&e[i].w)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[t];
}
inline int dfs(int x,int in)
{
if(x==t)return in;
int out=0;
for(int i=head[x];i&∈i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[x]+1&&e[i].w)
{
int res=dfs(v,min(in,e[i].w));
out+=res;
in-=res;
e[i].w-=res;
e[i^1].w+=res;
}
}
if(out==0)dis[x]=0;
return out;
}
signed main()
{
cin>>m>>n;
s=0;t=n+m+1;
for(int i=1;i<=m;i++)
{
cin>>r[i];
sum+=r[i];
add(0,i,r[i]);
add(i,0,0);
}
for(int i=1;i<=n;i++)
{
cin>>c[i];
add(i+m,t,c[i]);
add(t,i+m,0);
}
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
add(i,j+m,1),add(j+m,i,0);
while(bfs())
ans+=dfs(s,INF);
if(ans==sum)
{
cout<<"1"<<endl;
for(int u=1;u<=m;u++)
{
for(int i=head[u];i;i=e[i].next)
if(e[i].v>m&&e[i].v<=m+n&&!e[i].w)
cout<<e[i].v-m<<" ";
cout<<endl;
}
return 0;
}
cout<<"0"<<endl;
return 0;
}
P4011 孤岛营救问题#
这题为啥是网络流啊。
这题需要用到的东西就是状压+bfs。
我们看到钥匙的数量很少,所以我们可以用数组的一维来存放当前点的钥匙持有情况,然后 bfs 即可。
code:
#include<bits/stdc++.h>
using namespace std;
int vis[11][11][(1<<11)];//标记在ij点的钥匙状态
int mp[11][11][11][11];//标记两个格子是否有门
int key[11][11][11];//当前点的钥匙
int num[11][11];//标记当前点的钥匙数目
int dx[]={0,1,-1,0,0},dy[]={0,0,0,1,-1};
int n,m,p,k,s;
struct sb{int x,y,dis,cos;};
queue<sb>q;
inline int bfs()
{
int now=0;
for(int i=1;i<=num[1][1];i++)
now|=(1<<(key[1][1][i]-1));
vis[1][1][now]=1;
q.push(sb{1,1,0,now});
while(!q.empty())
{
sb qwq=q.front();
q.pop();
if(qwq.x==n&&qwq.y==m)
return qwq.dis;
for(int i=1;i<=4;i++)
{
int xx=qwq.x+dx[i];
int yy=qwq.y+dy[i];
if(xx<1||yy<1||xx>n||yy>m)continue;
if(mp[qwq.x][qwq.y][xx][yy]==-1)continue;
int t=mp[qwq.x][qwq.y][xx][yy];
if(t!=0)
if((qwq.cos&(1<<(t-1)))==0)continue;
int cc=qwq.cos;
for(int j=1;j<=num[xx][yy];j++)
cc|=(1<<key[xx][yy][j]-1);
if(vis[xx][yy][cc])continue;
vis[xx][yy][cc]=1;
q.push(sb{xx,yy,qwq.dis+1,cc});
}
}
return -1;
}
int main()
{
cin>>n>>m>>p>>k;
for(int i=1;i<=k;i++)
{
int a1,b1,a2,b2,c;
cin>>a1>>b1>>a2>>b2>>c;
if(c==0)
{
mp[a1][b1][a2][b2]=-1;
mp[a2][b2][a1][b1]=-1;
}
else
{
mp[a1][b1][a2][b2]=c;
mp[a2][b2][a1][b1]=c;
}
}
cin>>s;
for(int i=1;i<=s;i++)
{
int x,y,p;
cin>>x>>y>>p;
num[x][y]++;
key[x][y][num[x][y]]=p;
}
int ans=bfs();
cout<<ans<<endl;
return 0;
}
P2774 方格取数问题#
我们可以发现,互斥的两个点横纵坐标和奇偶性不同,这样可以分为两类点,一类是横纵坐标和是奇数,一类是横纵坐标和是偶数。
我们假设一开始全都要,然后扔掉一些点让答案合法,问题就转化为了求最小割。
我们用超级源点和其中一类以点权为流量链接,另一类同样连到超级汇点上。
将两两互斥的边之间连一条 INF 的边,保证最小割不会割掉这条边使图不连通。
根据最小割等于最大流,我们就可以用点权的总和减去最大流求出答案了。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1001000
using namespace std;
struct sb{int u,v,w,next;}e[N];
int n,m,s,t,dis[N],mp[510][510],head[N],cnt=1,ans,sum;
int dx[]={0,0,0,1,-1},dy[]={0,1,-1,0,0};
inline void add(int u,int v,int w)
{
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
e[++cnt].u=v;
e[cnt].v=u;
e[cnt].w=0;
e[cnt].next=head[v];
head[v]=cnt;
}
int bfs()
{
memset(dis,0,sizeof dis);
queue<int>q;
q.push(s);
dis[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dis[v]&&e[i].w)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[t];
}
inline int dfs(int x,int in)
{
if(x==t)return in;
int out=0;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[x]+1&&e[i].w)
{
int res=dfs(v,min(in,e[i].w));
in-=res;
out+=res;
e[i].w-=res;
e[i^1].w+=res;
if(!in)break;
}
}
if(!out)dis[x]=0;
return out;
}
signed main()
{
cin>>n>>m;
s=n*m+1;t=s+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>mp[i][j];
sum+=mp[i][j];
if((i+j)%2==0)
{
add(s,(i-1)*m+j,mp[i][j]);
for(int k=1;k<=4;k++)
{
int xx=i+dx[k];
int yy=j+dy[k];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m)
add((i-1)*m+j,(xx-1)*m+yy,INF);
}
}
else add((i-1)*m+j,t,mp[i][j]);
}
}
while(bfs())
// cout<<ans<<endl,
ans+=dfs(s,INF);
cout<<(sum-ans)<<endl;
return 0;
}
P4016 负载平衡问题#
题目的意思很明确,我们也可以根据标签根据题意判断出这题需要用到最小费用最大流。
首先我们把所有的仓库以当前货物数量为流量给连到超级源点上,把当前所有仓库的点都与他右边的仓库连流量为 INF (保证两个仓库之间可以随意分货物)的边,最后计算出平均值
code:
#include<bits/stdc++.h>
#define int long long
#define INF 2147483647
#define N 100010
using namespace std;
int n,a[N],cnt=1,head[N],cost[N];
int s,t,mflow,mcost,dis[N],vis[N],sum;
struct sb1{int fa,v;}b[N];
struct sb{int u,v,w,val,next;}e[N];
inline void add(int u,int v,int w1,int w2)
{
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].val=w1;
e[cnt].w=w2;
e[cnt].next=head[u];
head[u]=cnt;
}
inline bool bfs()
{
queue<int>q;
memset(b,0,sizeof b);
memset(vis,0,sizeof vis);
for(int i=0;i<=t;i++)
dis[i]=INF;
dis[s]=0;
q.push(s);
vis[s]=1;
while(!q.empty())
{
int u=q.front();
vis[u]=0;
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].w;
if(e[i].val>0&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
b[v].fa=u,b[v].v=i;
if(vis[v]==0)
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[t]!=INF;
}
inline void slove()
{
while(bfs())
{
int minn=INF;
for(int i=t;i!=s;i=b[i].fa)
minn=min(minn,e[b[i].v].val);
for(int i=t;i!=s;i=b[i].fa)
{
e[b[i].v].val-=minn;
e[b[i].v^1].val+=minn;
}
mflow+=minn;
mcost+=minn*dis[t];
// cout<<mflow<<endl;
}
return ;
}
signed main()
{
cin>>n;
s=0,t=n+1;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
add(s,i,a[i],0);
add(i,s,0,0);
}
sum/=n;
for(int i=1;i<=n;i++)
{
int kk;
if(i==n)kk=1;
else kk=i+1;
add(i,kk,INF,1);
add(kk,i,0,-1);
add(kk,i,INF,1);
add(i,kk,0,-1);
}
for(int i=1;i<=n;i++)
{
add(i,t,sum,0);
add(t,i,0,0);
}
slove();
cout<<mcost<<endl;
return 0;
}
P4014 分配问题#
好像是最大流,但是最小费用最大流。
我们把每一个人员和源点连起来,流量为
然后把每一个人员和每一个的工件都给链接起来,流量为
欸你这只求了最小总效益啊
把边的花费改成负的重新建一次图再跑一遍不就好了吗。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 100100
using namespace std;
struct sb{int next,to,flow,dis;}e[N];
int n,m,s,t,x,y,z,k,head[N],cnt=1,mp[110][110];
int xb[N],flow[N],dis[N],pre[N],vis[N];
queue<int>q;
inline void add(int x,int y,int z,int cost)
{
e[++cnt].next=head[x];
e[cnt].to=y;
e[cnt].flow=z;
e[cnt].dis=cost;
head[x]=cnt;
}
inline int bfs(int s,int t)
{
memset(flow,INF,sizeof flow);
memset(dis,INF,sizeof dis);
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!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].flow>0&&dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
pre[v]=u;
xb[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
return dis[t]!=INF;
}
int dfs()
{
int ans=0;
while(bfs(s,t))
{
int k=t;
ans+=flow[t]*dis[t];
while(k!=s)
{
e[xb[k]].flow-=flow[t];
e[xb[k]^1].flow+=flow[t];
k=pre[k];
}
}
return ans;
}
signed main()
{
cin>>n;
s=0,t=n*2+1;
memset(head,-1,sizeof head);
for(int i=1;i<=n;i++)
{
add(0,i,1,0);
add(i,0,0,0);
}
for(int i=n+1;i<=2*n;i++)
{
add(i,t,1,0);
add(t,i,0,0);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>mp[i][j];
add(i,n+j,1,mp[i][j]);
add(n+j,i,0,-mp[i][j]);
}
cout<<dfs()<<endl;
memset(e,0,sizeof e);
memset(xb,0,sizeof xb);
memset(pre,0,sizeof pre);
memset(head,-1,sizeof head);
cnt=1;
for(int i=1;i<=n;i++)
{
add(0,i,1,0);
add(i,0,0,0);
}
for(int i=n+1;i<=2*n;i++)
{
add(i,t,1,0);
add(t,i,0,0);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
add(i,n+j,1,-mp[i][j]);
add(n+j,i,0,mp[i][j]);
}
cout<<(-dfs())<<endl;
return 0;
}
P4015 运输问题#
这个题目和上面的差不多,不再多说。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,mp[110][110],dis[N],head[N],cnt=1;
int s,t,vis[N],A[N],B[N],flow[N],pre[N],xb[N];
struct sb{int v,w,val,next;}e[N];
inline void add(int u,int v,int z,int val)//w是流量,val是花费
{
e[++cnt].v=v;
e[cnt].val=val;
e[cnt].w=z;
e[cnt].next=head[u];
head[u]=cnt;
}
inline int bfs()
{
queue<int>q;
memset(vis,0,sizeof vis);
memset(dis,INF,sizeof dis);
memset(flow,INF,sizeof flow);
dis[s]=0;
q.push(s);
vis[s]=1;
while(!q.empty())
{
int u=q.front();
vis[u]=0;
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].val;
if(e[i].w&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
pre[v]=u,xb[v]=i;
flow[v]=min(flow[u],e[i].w);
if(vis[v]==0)
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[t]!=INF;
}
inline int dfs()
{
int ans=0;
while(bfs())
{
// cout<<"cao"<<endl;
ans+=dis[t]*flow[t];
int k=t;
while(k!=0)
{
e[xb[k]].w-=flow[t];
e[xb[k]^1].w+=flow[t];
k=pre[k];
}
// cout<<ans<<endl;
}
return ans;
}
inline void build(int x)
{
for(int i=1;i<=n;i++)
add(s,i,A[i],0),
add(i,s,0,0);
for(int i=1;i<=m;i++)
add(i+n,t,B[i],0),
add(t,i+n,0,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
add(i,j+n,INF,mp[i][j]*x),
add(j+n,i,0,-mp[i][j]*x);
}
signed main()
{
cin>>n>>m;
s=0,t=n+m+1;
for(int i=1;i<=n;i++)
cin>>A[i];
for(int i=1;i<=m;i++)
cin>>B[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>mp[i][j];
build(1);
cout<<dfs()<<endl;
memset(head,0,sizeof head);
cnt=1;
build(-1);
cout<<(-dfs())<<endl;
return 0;
}
P2764 最小路径覆盖问题#
首先我们需要知道一个定理:最小覆盖=点数-最大流。
这个很好证明。因为当点之间没有边时,路径数=点数-
然后就是改如何建图了。
我们知道如果要是直接把这些点连到源点起来然后再都连到汇点的话,跑最大流中间点与点之间的边是不会走到的,所以我们想到了拆点,把一个点拆成两个,这样每一个点都会被标记上谁到谁,保证中间的点与点之间的边是走过的,也就是说,如果要是有从某个点到某个点的关系的话,指向某一个点的所有路径流量都是
最后输出路径的时候用两个数组存一下就好了。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,S,T,head[N],cnt=1,dis[N];
int s[N],t[N],pf[N],vis[N];
struct sb{int u,v,w,next;}e[N<<2];
inline void add(int u,int v,int w)
{
e[++cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].u=v;e[cnt].v=u;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
// cout<<"cao"<<endl;
memset(dis,0,sizeof dis);
queue<int>q;
q.push(S);
dis[S]=1;
while(!q.empty())
{
// cout<<"cao"<<endl;
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].next)
{
// cout<<"cao"<<endl;
int v=e[i].v;
if(e[i].w&&!dis[v])
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[T];
}
inline int dfs(int x,int in)
{
// cout<<"FUCK"<<endl;
if(x==T)return in;
int out=0;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].v;
if(out==in)return in;
if(dis[v]==dis[x]+1&&e[i].w)
{
int res=dfs(v,min(e[i].w,in-out));
e[i].w-=res;
e[i^1].w+=res;
out+=res;
if(res!=0&&x!=S&&v!=T)
{
s[x]=v-n;
t[v-n]=x;
}
}
}
if(!out)dis[x]=0;
return out;
}
void dfs(int x)
{
if(x==0)return ;
if(t[x]!=x)dfs(t[x]);
vis[x]=1;
cout<<x<<" ";
return ;
}
inline int dinic()
{
int ans=0;
while(bfs())
// cout<<ans<<endl,
ans+=dfs(S,INF);
return ans;
}
signed main()
{
// freopen("P2764_2.in","r",stdin);
// freopen("551.out","w",stdout);
cin>>n>>m;
S=0;T=2*n+1;
for(int i=1;i<=n;i++)
s[i]=t[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
add(x,y+n,1);
}
for(int i=1;i<=n;i++)
{
add(S,i,1);
add(i+n,T,1);
}
int ans=dinic();
for(int i=n;i;i--)
{
if(s[i]==i&&vis[i]==0)
{
dfs(i);
cout<<endl;
}
}
cout<<(n-ans)<<endl;
return 0;
}
P3355 骑士共存问题#
和方格取数的题是差不多的,建图方式也是。
同样跟据横纵坐标和奇偶性的不同来建图,互斥的点流量设为 INF。
注意一开始的有障碍的点不能放骑士,建图的时候过滤掉,然后输出答案的时候减去
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1001000
using namespace std;
int n,m,s,t,dis[N],mp[210][210],cnt=1,head[N];
int dx[]={0,1,1,-1,-1,2,2,-2,-2};
int dy[]={0,2,-2,2,-2,1,-1,1,-1};
struct sb{int v,w,next;}e[N];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
// cout<<"cao"<<endl;
memset(dis,0,sizeof dis);
queue<int>q;
q.push(s);
dis[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dis[v]&&e[i].w)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[t];
}
inline int dfs(int x,int in)
{
if(x==t)return in;
int out=0;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[x]+1&&e[i].w)
{
int res=dfs(v,min(e[i].w,in));
out+=res;
in-=res;
e[i].w-=res;
e[i^1].w+=res;
if(in==0)break;
}
}
if(out==0)dis[x]=0;
return out;
}
int main()
{
// freopen("P3355_3.in","r",stdin);
cin>>n>>m;
s=n*n+1;t=n*n+2;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
mp[x][y]=1;
// mp[y][x]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(mp[i][j])continue;
if((i+j)%2==0)
{
add(s,(i-1)*n+j,1);
for(int k=1;k<=8;k++)
{
int xx=i+dx[k];
int yy=j+dy[k];
if(mp[i][j]||xx<1||xx>n||yy<1||yy>n)continue;
add((i-1)*n+j,(xx-1)*n+yy,INF);
// add((xx-1)*n+yy,(i-1)*n+j,INF);
}
}
else add((i-1)*n+j,t,1);
}
}
int sum=0;
while(bfs())
// cout<<sum<<endl,
sum+=dfs(s,INF);
cout<<(n*n-sum-m)<<endl;
return 0;
}
P1251 餐巾计划问题#
这道题目我们把每一天拆成晚上和早上两个点。
首先我们可以对于每一个晚上的点,从源点连一条边,容量为当天产生的脏餐巾,费用是零(因为脏餐巾的产生不需要花费)。
然后我们从每一天的早上向汇点连一条边,如果要是流满了就代表当前白天的餐巾已经够用了。
从每一天晚上向下一天晚上连一条边,流量为 INF ,表示脏餐巾可以留到第二天,INF的原因是可以将任意数量的脏餐巾给留到下一天。
从每一天晚上向第
从每一天晚上向第
从源点向每一天早上连一条容量为INF,花费为
然后就是跑一遍最小费用最大流的模板即可。
code:
#include<bits/stdc++.h>
#define INF 999999999
//#define int long long
#define N 10010
using namespace std;
int n,m,s,t,dis[N],head[N],cnt=1;
int flow[N],vis[N],ww,w1,w2,t1,t2;
long long mcost;
struct sb1{int fa,v;}b[N<<2];
struct sb{int v,w,val,next;}e[N<<2];
inline void add(int u,int v,int w,int val)//w是流量,val是花费
{
e[++cnt].v=v;e[cnt].val=val;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].val=-val;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
// cout<<"cao"<<endl;
for(int i=1;i<=2*n+2;i++)
dis[i]=INF,vis[i]=0;
queue<int>q;
q.push(s);
flow[s]=INF;
dis[s]=0;
vis[s]=1;
while(!q.empty())
{
// cout<<q.front()<<endl;
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].val;
// cout<<v<<" "<<w<<endl;
if(e[i].w>0&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
b[v].fa=u;b[v].v=i;
flow[v]=min(flow[u],e[i].w);
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[t]<INF;
}
inline void slove()
{
while(bfs())
{
// cout<<dis[t]<<endl;
int k=t;
while(s!=k)
{
e[b[k].v].w-=flow[t];
e[b[k].v^1].w+=flow[t];
k=b[k].fa;
}
// mflow+=flow[t];
mcost+=flow[t]*dis[t];
}
}
signed main()
{
scanf("%d",&n);
s=0;t=2*n+1;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
add(s,i,x,0);
add(i+n,t,x,0);
}
scanf("%d%d%d%d%d",&ww,&t1,&w1,&t2,&w2);
for(int i=1;i<=n;i++)
{
if(i+1<=n)add(i,i+1,INF,0);
if(i+t1<=n)add(i,i+n+t1,INF,w1);
if(i+t2<=n)add(i,i+n+t2,INF,w2);
add(s,i+n,INF,ww);
}
slove();
cout<<mcost<<endl;
return 0;
}
P2761 软件补丁问题#
和前面的那个题目一样是状压bfs。
我们发现 n 的数据范围很小,所以我们就可以利用二进制状压一下。
首先我们需要把
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define int long long
#define N (1<<20)+1
#define M 110
using namespace std;
int n,m,b1[M],b2[M],f1[M],f2[M];
int vis[N],t[M];
struct sb{
int now,dis;
bool operator < (const sb &a)const{return this->dis > a.dis;}
}now;
inline void bfs()
{
priority_queue<sb>q;
// priority_queue<sb,vector<sb>, greater<sb> > q;
q.push(now);
while(!q.empty())
{
sb qwq=q.top();
q.pop();
if(qwq.now==0)
{
cout<<qwq.dis<<endl;
return ;
}
if(vis[qwq.now])continue;
vis[qwq.now]=1;
for(int i=1;i<=m;i++)
{
if((qwq.now|b1[i])!=qwq.now||(qwq.now&(~b2[i]))!=qwq.now)continue;
sb k=qwq;
k.now&=(~f1[i]);
k.now|=f2[i];
k.dis+=t[i];
q.push(k);
}
}
cout<<"0"<<endl;
return ;
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>t[i];
char s;
for(int j=1;j<=n;j++)
{
cin>>s;
if(s=='+')b1[i]|=(1<<(j-1));
else if(s=='-')b2[i]|=(1<<(j-1));
}
for(int j=1;j<=n;j++)
{
cin>>s;
if(s=='-')f1[i]|=(1<<(j-1));
else if(s=='+')f2[i]|=(1<<(j-1));
}
}
for(int i=1;i<=n;i++)
now.now|=(1<<(i-1));
now.dis=0;
// cout<<now.now<<endl;
bfs();
return 0;
}
P4009 汽车加油行驶问题#
怎么啥都能用网络流
首先我们看到题目中有两个很棘手的问题那就是如何处理油箱内的油
然后直接最小费用最大流输出答案就可了。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define int long long
#define N 1001000
using namespace std;
int n,k,A,B,C,dis[N],vis[N],cnt=1,s,t,flow[N];
int mp[110][110],mcost,head[N];
struct sb1{int fa,v;}b[N<<2];
struct sb{int v,next,w,val;}e[N<<2];
inline int wz(int x,int y,int z){return n*n*(z-1)+(x-1)*n+y;}
inline void add(int u,int v,int w,int val)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline int bfs()
{
memset(dis,INF,sizeof dis);
queue<int>q;
q.push(s);
vis[s]=1;
flow[s]=INF;
dis[s]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].val;
if(e[i].w>0&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
b[v].fa=u;b[v].v=i;
flow[v]=min(flow[u],e[i].w);
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
return dis[t]<INF;
}
inline void solve()
{
while(bfs())
{
int k=t;
while(k!=s)
{
e[b[k].v].w-=flow[t];
e[b[k].v^1].w+=flow[t];
k=b[k].fa;
}
mcost+=dis[t]*flow[t];
}
}
signed main()
{
cin>>n>>k>>A>>B>>C;
s=0,t=n*n*(k+1)+1;
add(s,wz(1,1,1),1,0);
for(int i=2;i<=k+1;i++)
add(wz(n,n,i),t,1,0);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
int x;
cin>>x;
// cout<<"cao"<<endl;
if(x==1)
{
for(int o=2;o<=k+1;o++)
add(wz(i,j,o),wz(i,j,1),1,A);
if(i<n)add(wz(i,j,1),wz(i+1,j,2),1,0);
if(j<n)add(wz(i,j,1),wz(i,j+1,2),1,0);
if(i>1)add(wz(i,j,1),wz(i-1,j,2),1,B);
if(j>1)add(wz(i,j,1),wz(i,j-1,2),1,B);
}
else
{
for(int o=1;o<=k;o++)
{
if(i<n)add(wz(i,j,o),wz(i+1,j,o+1),1,0);
if(j<n)add(wz(i,j,o),wz(i,j+1,o+1),1,0);
if(i>1)add(wz(i,j,o),wz(i-1,j,o+1),1,B);
if(j>1)add(wz(i,j,o),wz(i,j-1,o+1),1,B);
}
add(wz(i,j,k+1),wz(i,j,1),1,A+C);
}
}
}
solve();
cout<<mcost<<endl;
return 0;
}
P2765 魔术球问题#
什么 jb 题目真 jb 恶心。
首先我们知道我们可以把每一个点拆成两个分别与源点汇点相连,因为要是一个与源点和汇点相连的话跑最大流没有意义。
其次,我们知道两个编号和为完全平方数的可以连边,但是要统一一下,比如从第一部分的当前点连向第二部分的其他点。
建完图之后就会发现转化成了求最小路径覆盖问题。
但是题目正好是反着的,所以我们边跑最大流边加球,具体注释在代码里。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
//#define int long long
#define endl '\n'
#define N 1001000
#define M 5000
using namespace std;
int n,dis[N],cnt=1,head[N],sum;
int ans,num,s,t,vis[N],re[N],pre[N];
struct sb{int v,w,next;}e[N<<2];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].w=0;e[cnt].next=head[v];head[v]=cnt;
}
bool bfs()
{
memset(dis,0,sizeof dis);
dis[s]=1;
queue<int>q;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
// cout<<u<<endl;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dis[v]&&e[i].w)
dis[v]=dis[u]+1,q.push(v);
}
}
return dis[t];
}
int dfs(int x,int in)
{
if(x==t)return in;
int out=0;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]==dis[x]+1&&e[i].w)
{
int res=dfs(v,min(e[i].w,in));
e[i].w-=res;
e[i^1].w+=res;
in-=res;
out+=res;
if(v!=t)pre[x>>1]=v>>1;//如果要是当前点不是终点就存一下下一个点
if(!in)break;
}
}
if(out==0)dis[x]=0;
return out;
}
inline int dinic()
{
int cao=0;
while(bfs())
// cout<<cao<<endl,
cao+=dfs(s,1e9);
return cao;
}
signed main()
{
cin>>n;
s=M*2+1,t=s+1;
while(ans<=n)//找到最少覆盖路径数为n的时候退出
{
num++;//当前球的数量加一
add(s,num<<1,1);//×2的目的是把一个点拆成两个,这是源点向第一部分的点连边
add((num<<1)|1,t,1);//第二部分的点向汇点连边
int GG=sqrt(num)+1; //从每一个数的根号向下取整开始枚举
for(int i=GG;i*i<2*num;i++)//枚举每一个能与之构成完全平方数的i
// if(i*i==2*num)如果不是<2*num的话,可能会出现自己两部分的点相连或者后面有两个重复的情况
// cout<<num<<": "<<i*i<<endl,
// cout<<num<<":"<<((i*i-num))<<endl,
add((i*i-num)<<1,(num<<1)|1,1);//第一部分的i方-num与第二部分的num点建边
int kk=dinic();
if(!kk)re[++ans]=num;//如果当前没有增加流量就说明当前的球需要新开一个柱子
// cout<<kk<<" "<<ans<<" "<<num<<endl;
}
cout<<(--num)<<endl;
for(int i=1;i<=n;i++)
{
if(vis[re[i]])continue;//如果当前答案的值已经访问过就跳过
int x=re[i];
vis[x]=1;
while(x!=0)
{
printf("%d ",x);
x=pre[x];
vis[x]=1;
}
cout<<endl;
}
return 0;
}
P4013 数字梯形问题#
我们发现边不会相交只会重复。
第一问:边和点都不能重合,那就拆点建图,然后所有的点的边最大流量都是1
第二问:边不能重合,点可以,点可以重合,我们可以不用拆点了,然后我们所有的最后一层的点到源点的边流量都是INF
第三问:边和点都可以重合,我们可以在点与点之间的边流量都设为INF,这样边也能重合了
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 5010
using namespace std;
int n,m,s,t,mcost,mflow,ff[N],p[110][110],mp[110][110];
int cnt=1,head[N],dis[N],flow[N],pre[N],vis[N],id;
struct sb{int v,next,w,val;}e[N];//w是流量,val是花费
inline void add(int u,int v,int w,int val)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline void qk()
{
cnt=1;
memset(head,0,sizeof head);
memset(pre,0,sizeof pre);
memset(ff,0,sizeof ff);
memset(e,0,sizeof e);
mcost=0;mflow=0;
}
inline int SPFA()
{
queue<int>q;
memset(dis,INF,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
vis[s]=1;
q.push(s);
flow[s]=INF;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].val;
if(e[i].w&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
flow[v]=min(flow[u],e[i].w);
ff[v]=u;
pre[v]=i;
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
return dis[t]!=INF;
}
inline void EK()
{
while(SPFA())
{
int k=t;
while(k!=s)
{
int i=pre[k];
e[i].w-=flow[t];
e[i^1].w+=flow[t];
k=ff[k];
}
mflow+=flow[t];
mcost+=flow[t]*dis[t];
}
}
signed main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m+i-1;j++)
{
cin>>mp[i][j];//存点权
p[i][j]=++id;//存编号
}
}
qk();
s=0;t=id*2+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m+i-1;j++)
{
add(p[i][j],p[i][j]+id,1,-mp[i][j]);//每一个点与自己拆出来的点相连
if(p[i+1][j+1])add(p[i][j]+id,p[i+1][j+1],1,0);//每一个点与自己的右下方的点相连因为不能重复所以流量是1
if(p[i+1][j])add(p[i][j]+id,p[i+1][j],1,0);//每一个点与自己的左下方的点相连
}
}
for(int i=1;i<=m;i++)
add(s,p[1][i],1,0);//起点与源点相连
for(int i=1;i<=m+n-1;i++)
add(p[n][i]+id,t,1,0);//终点与汇点相连
EK();
cout<<(-mcost)<<endl;
qk();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m+i-1;j++)
{//点可以重复就不拆点了
if(p[i+1][j+1])add(p[i][j],p[i+1][j+1],1,-mp[i][j]);//边不能重复所以流量还是1
if(p[i+1][j])add(p[i][j],p[i+1][j],1,-mp[i][j]);
}
}
for(int i=1;i<=m;i++)
add(s,p[1][i],1,0);
for(int i=1;i<=m+n-1;i++)
add(p[n][i],t,INF,-mp[n][i]);//因为点可以重复所以最大流量是INF
EK();
cout<<(-mcost)<<endl;
qk();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m+i-1;j++)
{
if(p[i+1][j+1])add(p[i][j],p[i+1][j+1],INF,-mp[i][j]);//边也可以重复了
if(p[i+1][j])add(p[i][j],p[i+1][j],INF,-mp[i][j]);//流量设为INF
}
}
for(int i=1;i<=m;i++)
add(s,p[1][i],1,0);
for(int i=1;i<=m+n-1;i++)
add(p[n][i],t,INF,-mp[n][i]);
EK();
cout<<(-mcost)<<endl;
return 0;
}
P4012 深海机器人问题#
给你一个网格图,每一条网格边上都有边权,给你
考虑最大费用最大流。
我们需要弄清楚的一点就是题目里说了机器人只能向北或者向东走,也就是说只会从左下角往右上角走,我们把他给反过来,让他从左上角往右下角走,也就是只能向下或者向右。
我们可以看到输入中是
然后我们来处理每一个起点和每一个终点。
我们可以发现每一个起点和终点都有机器人个数限制,比如第
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 100010
using namespace std;
int cnt=1,head[N],n,m,s,t,a,b,mcost;
int dis[N],vis[N],flow[N],pre[N],ff[N];
struct sb{int v,w,val,next;}e[N];
inline int bh(int x,int y){return x*(m+1)+y+1;}//求出编号
inline void add(int u,int v,int w,int val)//W是流量,val是花费
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline int SPFA()
{
queue<int>q;
memset(dis,INF,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
vis[s]=1;
flow[s]=INF;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].val;
if(e[i].w&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
pre[v]=i;
ff[v]=u;
flow[v]=min(flow[u],e[i].w);
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[t]!=INF;
}
inline void EK()
{
while(SPFA())
{
int k=t;
while(k!=s)
{
e[pre[k]].w-=flow[t];
e[pre[k]^1].w+=flow[t];
k=ff[k];
}
mcost+=dis[t]*flow[t];
}
}
signed main()
{
cin>>a>>b>>n>>m;
s=0;t=N-1;//起点和终点编号
for(int i=0;i<=n;i++)//枚举所有横着的边
{
for(int j=0;j<m;j++)//一行m条
{
int x;
cin>>x;
add(bh(i,j),bh(i,j+1),1,-x);//将当前点和下一个点链接起来,花费是-x因为要求的是最大花费最大流,因为标本只能采一次所以流量是1
add(bh(i,j),bh(i,j+1),INF,0);//这个是后面的边,标本没了花费为0,可以无限重复走,所以流量是INF
}
}
for(int i=0;i<=m;i++)//同理枚举所有竖着的边
{
for(int j=0;j<n;j++)
{
int x;
cin>>x;
add(bh(j,i),bh(j+1,i),1,-x);
add(bh(j,i),bh(j+1,i),INF,0);
}
}
for(int i=1;i<=a;i++)//这个是枚举每一个起点,
{
int x,y,z;
cin>>z>>x>>y;
add(s,bh(x,y),z,0);
}
for(int i=1;i<=b;i++)
{
int x,y,z;
cin>>z>>x>>y;
add(bh(x,y),t,z,0);
}
EK();
cout<<(-mcost)<<endl;
return 0;
}
Q:为什么这一篇这么详细?
A:因为我从我洛谷的题解搬过来的。
P3356 火星探险问题#
这道题目和上面那道的思路是差不多的,到那时要输出路径并且是有障碍的。
首先我们和上面那道题一样,先用一个双重循环赋个编号,然后这个题目是点到点,需要拆点,把第一部分的起点和第二部分的终点分别连向源点和汇点,然后我们开始读入地图信息,如果要是 2 的话就表示当前点是有石头标本的,我们就可以从第一部分向第二部分连一条花费为 1,容量为 1 的边,因为只能拿一次。对于是 1 的点,前面我们存了编号,直接用一维数组来标记当前点是障碍,然后不进行对自己的建边,表示当前点不能走,然后对于剩下的点,都建花费为 0,容量为
然后我们开始对于所有的点向自己的右边和下边的点连边,跑一边费用流,然后开始进行路径的输出,我参考了题解里面的思路,建一个新图然后dfs。
code:
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 1000100
using namespace std;
int n,m,cnt=1,kk,s,t,mcost,head[N],dis[N],vis[N];
int pre[N],ff[N],mp[110][110],v[N],flow[N],G;
struct sb{int v,w,val,next;}e[N];
inline void add(int u,int v,int w,int val)//W是流量,val是花费
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].val=val;e[cnt].next=head[u];head[u]=cnt;
e[++cnt].v=u;e[cnt].w=0;e[cnt].val=-val;e[cnt].next=head[v];head[v]=cnt;
}
inline int SPFA()
{
queue<int>q;
memset(dis,INF,sizeof dis);
memset(vis,0,sizeof vis);
dis[s]=0;
vis[s]=1;
flow[s]=INF;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v,w=e[i].val;
if(e[i].w&&dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
pre[v]=i;
ff[v]=u;
flow[v]=min(flow[u],e[i].w);
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[t]!=INF;
}
inline void EK()
{
while(SPFA())
{
int k=t;
while(k!=s)
{
e[pre[k]].w-=flow[t];
e[pre[k]^1].w+=flow[t];
k=ff[k];
}
mcost+=dis[t]*flow[t];
}
}
inline void print(int id,int x)
{
int temp=x-2*n*m;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].v,w=e[i].w;
if(!w)continue;
int tt=v-2*n*m;
if(temp+1==tt)cout<<id<<" 1"<<endl;
else cout<<id<<" 0"<<endl;
e[i].w--;
print(id,v);
break;
}
}
signed main()
{
cin>>kk>>m>>n;
s=3*n*m+1;t=s+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
mp[i][j]=++G;
add(s,1,kk,0);
add(2*n*m,t,kk,0);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>G;
if(G==2)add(mp[i][j],mp[i][j]+n*m,1,-1);
if(G==1)v[mp[i][j]]=1;
else add(mp[i][j],mp[i][j]+n*m,INF,0);
}
}
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
add(mp[i][j-1]+n*m,mp[i][j],INF,0);
for(int i=2;i<=n;i++)
for(int j=1;j<=m;j++)
add(mp[i-1][j]+n*m,mp[i][j],INF,0);
EK();
for(int i=n*m+1;i<n*m*2;i++)
{
if(v[i-n*m])continue;
for(int j=head[i];j;j=e[j].next)
{
int w=e[j].w,v=e[j].v;
if(v+n*m==i)continue;
if(w==INF)continue;
if(v!=t)add(i+n*m,v+2*n*m,INF-w,0);
}
}
for(int i=1;i<=kk;i++)
print(i,n*m*2+1);
return 0;
}
我要去学二分图了,剩下的以后来复习网络流的时候补完。
作者: 北烛青澜
出处:https://www.cnblogs.com/Multitree/p/17145335.html
本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!