la 3231 fair share 二分 最大流 ISAP Dinic
You are given N processors and M jobs to be processed. Two processors are specified to each job. To process the job, the job should be allocated to and executed on one of the two processors for one unit of time. If K jobs are allocated to a processor, then it takes K units of time for the processor to complete the jobs. To complete all the jobs as early as possible, you should allocate the M jobs to the N processors as fair as possible. Precisely speaking, you should minimize the maximum number of jobs allocated to each processor over all processors. The quantity, minimum number of jobs, is called fair share.
For example, you are given 5 processors and 6 jobs. Each job can be allocated to one of the two processors as
shown in the table below. Job 1 can be allocated to processors 1 or 2, and job 2 can be allocated to processors
2 or 3, etc. If you allocate job 1 to processor 1, job 2 to processor 2, job 3 to processor 3, job 4 to processor 4,
job 5 to processor 5, and job 6 to processor 1, then you have at most two jobs allocated to each processor.
Since there are more jobs than processors in this example, some processors necessarily have at least two jobs,
and thus the fair share is two.
Given N processors, M jobs, and the sets of two processors to which the jobs can be allocated, you are to write a program that finds the fair share. Processors are numbered from 1 to N and jobs are numbered from 1 to M . It is assumed that the sets of two processors to which the jobs can be allocated are distinct over all jobs.
That is, if a job J1 can be allocated to processors P1 or P2, and a job J2 which is different from J1 can be allocated to processors P3 or P4, then {P1, P2}{P3, P4}.
Input
The input consists of T test cases. The number of test cases T is given in the first line of the input file. Each test case begins with a line containing an integer N, 1N1, 000, that represents the number of processors in the test case. It is followed by a line containing an integer M, 1M10, 000, that represents the number of jobs. In the following M lines, K-th line contains two distinct integers representing processors to which job K can be allocated, 1KM. The integers given in a line are separated by a space. After that, the remaining test cases are listed in the same manner as the above.
Output
Print exactly one line for each test case. The line should contain the fair share for that test case.
The following shows sample input and output for three test cases.
Sample Input
3 5 6 1 2 2 3 3 4 4 5 5 1 1 3 3 2 3 2 1 2 6 6 1 2 3 4 4 6 6 5 5 3 6 3
Sample Output
2 1 2
看的白书,引入虚拟源点0、汇点n+m+1,从源点连容量为1的弧到每个任务,从每个任务连容量为1的弧到可运行的处理器,从每个处理器连容量为二分值到汇点。
当练习Dinic 和 ISAP算法,于是写了两个:
Dinic:先bfs建立层次图,再dfs多路增广,如此交替进行,直至没有可行残流路从s到t,据说O(n^2m)
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<vector> #include<cstdlib> #include<algorithm> #include<queue> using namespace std; #define LL long long #define ULL unsigned long long #define UINT unsigned int #define MAX_INT 0x7fffffff #define MAX_LL 0x7fffffffffffffff #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) #define INF 100000000 #define MAXM 80000 #define MAXN 15000 struct edge{ int u, v, cap, flow, nxt; }e[MAXM]; int n, m, cc; int h[MAXN], cur[MAXN]; int s, t; void add(int u, int v, int cap, int flow){ e[cc]=(edge){u, v, cap, flow, h[u]}; h[u]=cc++; } bool done[MAXN]; int d[MAXN]; bool bfs(){ int i, k=t+1, u; memset(done, 0, sizeof(done)); // for(i=0; i<k; i++) d[i]=INF; queue<int> q; q.push(s); done[s]=true; d[s]=0; // cout<<'a'<<endl; while(!q.empty()){ u=q.front(); q.pop(); // cout<<u<<endl; for(i=h[u]; i!=-1; i=e[i].nxt){ int cap=e[i].cap, flow=e[i].flow, v=e[i].v; // cout<<done[v]<<' '<<cap<<' '<<flow<<endl; if(!done[v] && cap>flow){ // cout<<v<<' '; d[v]=d[u]+1; done[v]=true; q.push(v); } } // cout<<endl; } return done[t]; } int dfs(int u, int a){ if(u==t || a==0) return a; int flow=0, f; for(int &i=cur[u]; i!=-1; i=e[i].nxt){ //multi path augmenting, 不用优化cur会TLE int v=e[i].v; if(d[u]+1 == d[v] && (f=dfs(v, MIN(a, e[i].cap-e[i].flow)))>0){ e[i].flow+=f; e[i^1].flow-=f; flow+=f; a-=f; if(a==0) break; } } return flow; } int tc; bool Dinic(int x){ for(int i=tc; i<cc; i+=2) e[i].cap=x; for(int i=0; i<cc; i++) e[i].flow=0; int flow=0; while(bfs()){ for(int i=0; i<t+1; i++) cur[i]=h[i]; flow+=dfs(s, INF); //cout<<flow<<endl; } return flow==m; } int main(){ // freopen("C:\\Users\\Administrator\\Desktop\\in.txt","r",stdin); int T; scanf(" %d", &T); while(T--){ scanf(" %d %d", &n, &m); int i, j, u, v; memset(h, -1, sizeof(h)); cc=0; for(i=1; i<=m; i++){ scanf(" %d %d", &u, &v); add(i, u+m, 1, 0); add(u+m, i, 0, 0); //jobs to processors add(i, v+m, 1, 0); add(v+m, i, 0, 0); } for(i=1; i<=m; i++){ add(0, i, 1, 0); add(i, 0, 0, 0); //source to jobs } tc=cc; s=0, t=n+m+1; for(i=m+1; i<=n+m; i++){ add(i, t, 0, 0); add(t, i, 0, 0); //processors to sink } int mid, l=1, r=m, ans=0; while(r>=l){ mid=(l+r)>>1; if(Dinic(mid)) r=mid-1, ans=mid; else l=mid+1; } // if(Dinic(3)) cout<<3<<endl; // cout<<"***********************"<<endl; // if(Dinic(2)) cout<<2<<endl; printf("%d\n", ans); } return 0; }
ISAP:可先逆向bfs处理出每个点到汇点的最短路,即到汇点经过其它点数,也可直接初始化d为0,但多样例情况下(比如该题)耗时增多;然后对于当前节点u,若有满足
d[u] = d[v]+1的弧(u, v),则前进u=v;否则,更新当前节点d值后(可能更新至n),后退一步。
关于gap优化:因为每次增广一条路后,层次图上点离汇点距离只有增大的可能, 所以每次更新后d[u]数量少1,一个d[u]=MIN{d[v],(u,v)}+1数量增1,于是若前一个距离数量值为0时,显然至少当前节点(新距离比原来的大)无法到达汇点,即层次图不连通,已得解。
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<vector> #include<cstdlib> #include<algorithm> #include<queue> using namespace std; #define LL long long #define ULL unsigned long long #define UINT unsigned int #define MAX_INT 0x7fffffff #define MAX_LL 0x7fffffffffffffff #define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) #define INF 100000000 #define MAXM 80000 #define MAXN 15000 struct edge{ int u, v, cap, flow, nxt; }e[MAXM]; int n, m, cc; int h[MAXN], cur[MAXN]; int s, t, d[MAXN]; int tc, num[MAXN]; int p[MAXN]; void add(int u, int v, int cap, int flow){ e[cc]=(edge){u, v, cap, flow, h[u]}; h[u]=cc++; } bool done[MAXN]; void bfs(){ //reverse bfs queue<int> q; memset(done, 0, sizeof(done)); done[t]=true; d[t]=0; q.push(t); while(!q.empty()){ int u=q.front(); q.pop(); for(int i=h[u]; i!=-1; i=e[i].nxt){ int v=e[i^1].u, flow=e[i^1].flow, cap=e[i^1].cap; //attention 'u' if(cap>flow && !done[v]){ done[v]=true; d[v]=d[u]+1; q.push(v); } } // cout<<u<<endl; } } int Augment(){ //similar to Edmonds-Karp int u=t, a=INF; while(u!=s){ a=MIN(a, e[p[u]].cap-e[p[u]].flow); u=e[p[u]].u; } for(u=t; u!=s; u=e[p[u]].u){ e[p[u]].flow+=a; e[p[u]^1].flow-=a; } return a; } bool isap(int x){ int i; for(i=tc; i<cc; i+=2) e[i].cap=x; for(i=0; i<cc; i++) e[i].flow=0; int flow=0; bfs(); memset(num, 0, sizeof(num)); for(i=0; i<t+1; i++) num[d[i]]++; int u=s; for(i=0; i<t+1; i++) cur[i]=h[i]; while(d[s]<t+1){ if(u==t){ flow+=Augment(); u=s; } bool ok=false; for(int i=cur[u]; i!=-1; i=e[i].nxt){ //advance int v=e[i].v, flow=e[i].flow, cap=e[i].cap; if(cap>flow && d[u]==d[v]+1){ ok=true; p[v]=i; cur[u]=i; u=v; break; } } if(!ok){ //retreat int tmp=t; for(int i=h[u]; i!=-1; i=e[i].nxt) if(e[i].cap>e[i].flow) tmp=MIN(tmp, d[e[i].v]); if(--num[d[u]]==0) break; //gap improvement num[d[u]=tmp+1]++; cur[u]=h[u]; if(u!=s) u=e[p[u]].u; } } return flow==m; } int main(){ // freopen("C:\\Users\\Administrator\\Desktop\\in.txt","r",stdin); int T; scanf(" %d", &T); while(T--){ scanf(" %d %d", &n, &m); int i, j, u, v; memset(h, -1, sizeof(h)); cc=0; for(i=1; i<=m; i++){ scanf(" %d %d", &u, &v); add(i, u+m, 1, 0); add(u+m, i, 0, 0); //jobs to processors add(i, v+m, 1, 0); add(v+m, i, 0, 0); } for(i=1; i<=m; i++){ add(0, i, 1, 0); add(i, 0, 0, 0); //source to jobs } tc=cc; s=0, t=n+m+1; for(i=m+1; i<=n+m; i++){ add(i, t, 0, 0); add(t, i, 0, 0); //processors to sink } int mid, l=1, r=m, ans=0; while(r>=l){ mid=(l+r)>>1; if(isap(mid)) r=mid-1, ans=mid; else l=mid+1; } printf("%d\n", ans); } return 0; }