codeforces847J Students Initiation 网络流
题意:
有n个人,m对关系,要求每对关系中,有且仅有一个人给另外一个人送礼物,并且使送出礼物最多的人送的礼物尽可能少。并输出送礼物的方案。
思路:这道题麻烦的是网络流模型的转换(废话)。
最关键的因素是每对关系中只有一个人能给另外一个人送礼物,也就是说是不能相互送礼物的,虽然这句话是废话,但是正因为如此,如果我们考虑直接按照人建点,最后的方案会有问题。(流量相同,但方案错误)。
#pragma GCC optimize (2) #pragma G++ optimize (2) #pragma comment(linker, "/STACK:102400000,102400000") #include<bits/stdc++.h> #include<cstdio> #include<vector> #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,b,a) for(int i=b;i>=a;i--) #define clr(a,b) memset(a,b,sizeof(a)) #define pb push_back #define pii pair<int,int > using namespace std; typedef long long ll; const int maxn=200010; const int inf=0x3f3f3f3f; ll rd() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const ll INFLL = 0x3f3f3f3f3f3f3f3f; const int INF = 0x3f3f3f3f; struct Edge { int to, flow, nxt; Edge() {} Edge(int to, int nxt, int flow):to(to),nxt(nxt), flow(flow) {} } edge[maxn << 2]; int head[maxn], dep[maxn]; int S, T; int N, n, m, tot,cnt; vector<pair<int,int> >va; void Init(int n) { N = n; for (int i = 0; i <= N; ++i) head[i] = -1; tot = 0; } void addv(int u, int v, int w, int rw = 0) { edge[tot] = Edge(v, head[u], w); head[u] = tot++; edge[tot] = Edge(u, head[v], rw); head[v] = tot++; } bool BFS() { for (int i = 0; i <= N; ++i) dep[i] = -1; queue<int>q; q.push(S); dep[S] = 1; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = head[u]; ~i; i = edge[i].nxt) { if (edge[i].flow && dep[edge[i].to] == -1) { dep[edge[i].to] = dep[u] + 1; q.push(edge[i].to); } } } return dep[T] < 0 ? 0 : 1; } int DFS(int u, int f) { if (u == T || f == 0) return f; int w, used = 0; for (int i = head[u]; ~i; i = edge[i].nxt) { if (edge[i].flow && dep[edge[i].to] == dep[u] + 1) { w = DFS(edge[i].to, min(f - used, edge[i].flow)); edge[i].flow -= w; edge[i ^ 1].flow += w; used += w; if (used == f) return f; } } if (!used) dep[u] = -1; return used; } int Dicnic() { int ans = 0; while (BFS()) { ans += DFS(S, INF); } return ans; } int vs[5010][5010]; bool check(int res){ Init(T); rep(i,1,m){ addv(i+n,T,1); } rep(i,1,n){ addv(S,i,res); } rep(i,0,m-1){ int u=va[i].first,v=va[i].second; addv(u,i+1+n,1); addv(v,i+1+n,1); } int flow=Dicnic(); if(flow>=m){ return true; } return false; } int main() { while (~scanf("%d %d", &n, &m)) { va.clear(); S = 0, T = n+m+1; rep(i,1,m){ int u,v; scanf("%d%d",&u,&v); va.push_back({u,v}); } int l=0,r=n+1,mid,ans=-1; while(l<=r){ mid=(l+r)>>1; if(check(mid)){ ans=mid; r=mid-1; }else{ l=mid+1; } } check(ans); printf("%d\n", ans); int flow=Dicnic(); for(int u=1;u<=n;u++){ for(int i=head[u];i!=-1;i=edge[i].nxt){ if(edge[i].flow==0&&edge[i].to>n){ int id=edge[i].to-n; int x= (va[id-1].first==u?va[id-1].second:va[id-1].first); printf("%d %d\n",u,x); } } } } }
所以我们要考虑建立n个点,每个点对应一个人,再建立m个点,每个点对应一对关系,每个关系向汇点连一条容量为1的边,每个人向自己所处的所有关系都连容量为1的边,这样就使得上面说的这个条件成立了。然后我们就建立虚拟源点,二分每个源点向人的流量,每次重新建图即可。
最后输出方案,每个点流出的容量为0的边就是方案。
——愿为泰山而不骄
qq850874665~~