[ZJOI2011]最小割 & [CQOI2016]不同的最小割 分治求最小割

题面:

[ZJOI2011]最小割

[CQOI2016]不同的最小割

题解:

其实这两道是同一道题。。。。

最小割是用的dinic,不同的最小割是用的isap

其实都是分治求最小割

简单讲讲思路吧

就是首先全部的点都在一个集合里,然后随意定两个点为s和t,这里默认是第一个和最后一个。

然后找到最小割,最小割将整张图分为了s集和t集,于是我们再用这个最小割更新跨集合点对之间的最小割。

 这个很好理解,因为当前找到的最小割将s集和t集分开了,显然对于任意一组跨集合的点对而言,当前最小割都是一个可能的最小割。

然后我们再递归处理s集和t集(重复以上步骤)。

每次找到最小割后就更新跨集合点对。

本质上是分治吧。

之前看有些地方提到了最小割树,这里放个链接(这是我找到的写的最全的一篇了)

Gomory-Hu tree 最小割树

下面放代码吧,个人觉得看代码会更好理解,尤其是对分治不熟悉的人(比如我)

不同的最小割(isap):

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 900
  5 #define ac 20000
  6 #define inf 2139062143
  7 #define D printf("line in %d\n", __LINE__);
  8 char READ[7000100], *o = READ;
  9 int n, m, s, addflow, t, answer, tt;
 10 int last[AC], c[AC], have[AC], a[AC], ans[AC][AC], good[AC];
 11 int Head[AC], date[ac], Next[ac], haveflow[ac], tot = 1;
 12 int q[AC], head, tail;
 13 int ss[400000], cnt;
 14 bool z[AC];
 15 
 16 inline int read()
 17 {
 18     int x = 0; char c = getchar();
 19     while(c > '9' || c < '0') c = getchar();
 20     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
 21     return x;
 22 }
 23 
 24 inline void add(int f, int w, int S)
 25 {
 26     date[++tot] = w, Next[tot] = Head[f], Head[f] = tot, haveflow[tot] = S;
 27     date[++tot] = f, Next[tot] = Head[w], Head[w] = tot, haveflow[tot] = S;
 28     //printf("%d ---> %d %d\n", f, w, S);
 29 }
 30 
 31 inline void upmin(int &a, int b)
 32 {
 33     if(b < a) a = b;
 34 }
 35 
 36 void pre()
 37 {
 38     int u, v, e;
 39     n = read(), m = read();
 40     for(R i = 1; i <= n; i++) a[i] = i;
 41     for(R i = 1; i <= m; i++)
 42     {
 43         u = read(), v = read(), e = read();
 44         add(u, v, e);    
 45     }
 46     memset(ans, 127, sizeof(ans));
 47 }
 48 
 49 bool bfs()
 50 {
 51     int x, now;
 52     memset(have, 0, sizeof(have));
 53     memset(c, 0, sizeof(c));
 54     have[1] = 1, c[t] = 1, x = t;
 55     head = tail = 0;
 56     q[++tail] = t;
 57     while(head < tail)
 58     {
 59         x = q[++head]; 
 60         for(R i = Head[x]; i ; i = Next[i])
 61         {
 62             now = date[i];
 63             if(haveflow[i] && !c[now])
 64             {
 65                 c[now] = c[x] + 1;
 66                 ++have[c[now]];//error...忘记统计了
 67                 q[++tail] = now;
 68             }
 69         }
 70     }
 71     memcpy(good, Head, sizeof(Head));
 72     return c[s];
 73 }
 74 
 75 inline void aru()
 76 {
 77     int x = t;
 78     while(x != s)
 79     {
 80         haveflow[last[x]] -= addflow;
 81         haveflow[last[x] ^ 1] += addflow;
 82         x = date[last[x] ^ 1];
 83     }
 84     tt += addflow;
 85 }
 86 
 87 void isap()
 88 {
 89     int x = s, now; bool done;
 90     tt = 0, addflow = inf; 
 91     while(c[s] != 875)
 92     {
 93         if(x == t) aru(), addflow = inf, x = s;//忘记设置全局了,,,那在这里手动改一下吧
 94         done = false;
 95         for(R i = good[x]; i ; i = Next[i])
 96         {
 97             now = date[i];
 98             if(haveflow[i] && c[now] == c[x] - 1)
 99             {
100                 upmin(addflow, haveflow[i]);
101                 done = true;
102                 last[now] = i;
103                 good[x] = i;
104                 x = now;
105                 break;
106             }
107         }
108         if(!done)
109         {
110             int go = 874;
111             for(R i=Head[x]; i ; i = Next[i])
112             {
113                 now = date[i];
114                 if(haveflow[i] && c[now]) upmin(go, c[now]);
115             }
116             good[x] = Head[x];
117             if(!(--have[c[x]])) break;
118             ++have[c[x] = go + 1];
119             if(x != s) x = date[last[x] ^ 1];
120         }
121     }
122 }
123 
124 void restore()//还原
125 {
126     for(R i = 2; i <= tot; i += 2)//对于无向图而言,这还是非常妙的
127         haveflow[i] = haveflow[i ^ 1] = (haveflow[i] + haveflow[i ^ 1]) / 2;
128 }
129 
130 void dfs(int x)
131 {
132     int now;
133     z[x] = true;//....
134     for(R i = Head[x]; i ; i = Next[i])
135     {
136         now = date[i];
137         if(haveflow[i] && !z[now]) dfs(now);
138     }
139 }
140 
141 void solve(int l, int r)
142 {
143     if(l == r) return ;
144     s = a[l], t = a[r];
145     restore();
146     //printf("%d %d\n", l, r);
147     bfs();//重新定层次
148     isap();
149     memset(z, 0, sizeof(z));
150     dfs(s);
151     for(R i=1;i<=n;i++)//更新最小割
152         if(z[i])
153             for(R j=1;j<=n;j++)
154                 if(!z[j])
155                     ans[i][j] = ans[j][i] = min(ans[i][j], tt);
156     int ll = l - 1, rr = r + 1;
157     for(R i = l; i <= r; i++)
158         if(z[a[i]]) q[++ll] = a[i];
159         else q[--rr] = a[i];//不知道取什么名字了,先借这个用一下吧
160     for(R i = l; i <= r; i++) a[i] = q[i];
161     solve(l, ll), solve(rr, r);
162 }
163 
164 void work()
165 {
166     for(R i=1;i<=n;i++)
167         for(R j=1;j<i;j++)
168             ss[++cnt] = ans[i][j];
169     sort(ss + 1, ss + cnt + 1);
170     for(R i=1;i<=cnt;i++)
171         if(ss[i] != ss[i + 1]) ++answer;
172     printf("%d\n", answer); 
173 }
174 
175 int main()
176 {
177 //    freopen("in.in","r",stdin);
178     pre();
179     solve(1, n);
180     work();
181 //    fclose(stdin);
182     return 0;
183 }
View Code

最小割(dinic):

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define LL long long
  5 #define inf 2139062143
  6 #define getchar() *o++
  7 #define AC 155
  8 #define ac 101000
  9 #define D printf("line in %d\n", __LINE__);
 10 
 11 char READ[7001000], *o = READ;
 12 int s, t, T, Q, n, m;
 13 int last[AC], ans[AC][AC], a[AC], tmp[AC], c[AC];
 14 int date[ac], Next[ac], haveflow[ac], Head[AC], tot;
 15 int q[AC], head, tail;
 16 bool z[AC];
 17 
 18 inline int read()
 19 {
 20     int x = 0;char c = getchar();bool z = false;
 21     while(c > '9' || c < '0') c = getchar();
 22     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
 23     if(!z) return x;
 24     else return -x;
 25 }
 26 
 27 inline void add(int f, int w, int S)
 28 {//因为是双向边,所以反向边就可以省掉了
 29     date[++tot] = w, Next[tot] = Head[f], Head[f] = tot, haveflow[tot] = S;
 30     date[++tot] = f, Next[tot] = Head[w], Head[w] = tot, haveflow[tot] = S;
 31 //    printf("%d ---> %d %d\n", f, w, S);
 32 }
 33 
 34 bool bfs()
 35 {
 36     int now, x;
 37     head  = tail = 0;
 38     memset(c, 0, sizeof(c));
 39     c[s] = 1, q[++tail] = s;
 40     while(head < tail)
 41     {
 42         x = q[++head];
 43         for(R i = Head[x]; i ; i = Next[i])
 44         {
 45             now = date[i];
 46             if(haveflow[i] && !c[now])
 47             {
 48                 c[now] = c[x] + 1;
 49                 q[++tail] = now;
 50             }
 51         }
 52     }
 53     return c[t];
 54 }
 55 
 56 int dfs(int x, int flow)
 57 {
 58     if(x == t) return flow;
 59     int addflow, used = 0, now;
 60     for(R i=Head[x]; i ; i = Next[i])
 61     {
 62         now = date[i];
 63         if(c[now] == c[x] + 1)
 64         {//流到下一个点去的流量在haveflow和剩余流量中取最小值
 65             addflow = dfs(now, min(haveflow[i], flow - used));
 66             haveflow[i] -= addflow;
 67             haveflow[i^1] += addflow;
 68             used += addflow;
 69             if(used == flow) return flow;
 70         }
 71     }
 72     if(!used) c[x] = 0;
 73     return used;
 74 }
 75 
 76 int dinic()
 77 {
 78     int rnt = 0;
 79     while(bfs()) rnt += dfs(s, inf);
 80     return rnt;
 81 }
 82 
 83 void DFS(int x)//标记能到的点,便于区分
 84 {
 85     int now;
 86     z[x]= 1;
 87     for(R i = Head[x]; i ; i = Next[i])
 88     {
 89         now = date[i];
 90         if(haveflow[i] && !z[now]) DFS(now); 
 91     }
 92 }
 93 
 94 void restore()//挺妙的还原流量的方法,因为这两条边即是双向边,又可以当反向边
 95 {//因此总流量肯定是不会变的,所以加起来取个平均值就相当于还原了
 96     for(R i = 2; i <= tot; i += 2)
 97         haveflow[i] = haveflow[i+1] = (haveflow[i] + haveflow[i+1]) / 2;
 98 }
 99 
100 void solve(int l, int r)
101 {
102     if(l == r) return ;
103     restore();
104     s = a[l], t = a[r];//随便选两个作为s和t
105     int tt = dinic();//跑最小割
106     memset(z, 0, sizeof(z));
107     DFS(s);//查看哪些点是能到的(即没有被割断)
108     for(R i=1;i<=n;i++)//这里是在每次找到一个最小割的时候,就对跨集合点对的割进行更新,
109         if(z[i])//因此每次是需要枚举所有点对的。不然的话因为i是枚举的s集里面的东西,可能会导致某些点对没有被统计到
110             for(R j=1;j<=n;j++)//获取跨集合点对的割的大小
111                 if(!z[j]) ans[i][j] = ans[j][i] = min(ans[i][j], tt);
112     int ll = l - 1, rr = r + 1;
113     for(R i = l; i <= r; i++)
114         if(z[a[i]])//如果这个点可以属于s集
115             tmp[++ll] = a[i];//加入左边(从左边开始塞)
116         else tmp[--rr] = a[i];//不然就放在右边
117     for(R i=l;i<=r;i++) a[i] = tmp[i];//类似于归并排序的,,,放回数组内
118     solve(l, ll), solve(rr, r);
119 }
120 
121 void pre()
122 {
123     int u, v, k;
124     tot  = 1;
125     memset(ans, 127, sizeof(ans));
126     memset(Head, 0, sizeof(Head));
127     n = read(), m = read();
128     for(R i=1;i<=n;i++) a[i] = i;//先把点按顺序放进来
129     for(R i=1;i<=m;i++)
130     {
131         u = read(), v = read(), k = read();
132         add(u, v, k);
133     }
134     solve(1, n);//找出所有最小割
135 }
136 
137 void work()
138 {
139     int k, cnt;
140     Q = read();
141     while(Q--)
142     {
143         k = read(), cnt = 0;
144         for(R i=1;i<=n;i++)
145             for(R j=1;j<i;j++)//不能一个点对重复统计了
146                 if(ans[i][j] <= k) ++cnt;
147         printf("%d\n", cnt);
148     }
149     printf("\n");
150 }
151 
152 int main()
153 {
154 //    freopen("in.in","r",stdin);
155     fread(READ, 1, 7000000, stdin);
156     T = read();
157     while(T--)
158     {
159         pre();
160         work();
161     }
162 //    fclose(stdin);
163     return 0;
164 }
View Code

猜你喜欢

转载自www.cnblogs.com/ww3113306/p/9236049.html