「网络流24题」最长不下降子序列问题
传送门:>Here<
题意:
给定正整数序列$x_1,...,x_n$
(1)计算其最长不下降子序列的长度s。
(2)计算从给定的序列中最多可取出多少个长度为s的不下降子序列。
(3)如果允许在取出的序列中多次使用$x_1$和$x_n$,则从给定序列中最多可取出多少个长度为$s$的不下降子序列。
思路分析
题意首先就很坑:注意第二问中的取出二字,意味着一个数字最多只能存在于一个LIS中。所以才会有第三问的假设
第一问
第一问很简单,直接暴力$O(n^2)$就好了
后面的两问需要借助于网络流。
第二问
直接解释原理不清晰也不易懂,先详细阐述如何建模过程:
首先原序列做LIS,其中$f[i]$表示以$a[i]$为结尾的LIS的最大长度。对序列的每一个数进行拆点,也就是分为$<X_i, Y_i>$。我们要让S到T的每一条路径都是一个LIS,因此若$f[i]=1$,则$X_i$的点连接源点(容量为1,后同),$f[i]=maxs$则$Y_i$连接汇点。其中对于每一个数字,连接有向边$(X_i, Y_i)$。并且在做LIS的过程中,若发现一对数字满足$a[j]<=a[i] 且 f[j]+1=f[i]$,则连有向边$(Y_j, X_i)$。最后做最大流即可
我们发现这道题又和二分图有一点类似。经过刚才这样的连边之后,任一S到T的路径都是一个LIS,并且容量为1保证了每个点不会被用两次(与二分图原理相同),因此最大流即为方案数
至于与二分图不同的地方我们发现,我们每个点本身连了边,最后LIS的边还是反过来加的。为什么要这么做呢?这是这道题最大的难点。
首先分析这种方法的正确性:经过现在这样的建图,Y部分称为了边的起点,而X成为了终点。设有一条边是$(Y_a, X_b)$,则其到达$X_b$以后会顺着自己的边到达$Y_b$,而后如果还存在一条边$(Y_b, X_c)$,它就会继续来到$Y_c$,以此类推最终到达T。其中$(X_i, Y_i)$起到了过渡作用,并且保证了每个点只走一次
然后来讲这种方法的必要性:为什么正着连边不行呢?原因在于我们源点与汇点的选择。想象一个序列$5,4,3,2,1$,按照目前这种方法自然会跑出最大流为5(每个点直接沿着自己的边走了)。然而如果正着连边自己反着连,那么将会跑出0——我们并没有给它一个跑出去的机会
第三问
第三问其实就是在第二问的基础上稍作了一些改动。$x_1和x_n$可以用无限次意味着可以去掉之前给这两个点只能用一次的束缚,因此只需要把源点到1,n到汇点的边的容量改为INF即可。注意由于现在起点是右侧点了,所以$(X_1,Y_1)$和$(X_n,Y_n)$的容量务必也要改成INF
Code
细节题。Dinic不要打错了不然完蛋
另外,再DFS的时候由于我们最初给S设的可增广量为INF,而容量也是INF,这就不太对了,因为源点还有可能连更多的边,INF就不够了。因此第三问的时候容量改为INF/10保险一点
/*By DennyQi*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; const int MAXN = 10010; const int MAXM = 10010; const int INF = 1061109567; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x << 3) +(x << 1) + c - '0', c = getchar(); return x * w; } int K,N,M,S,T,x,p,maxs; int first[MAXM*2],nxt[MAXM*2],to[MAXM*2],cap[MAXM*2],flow[MAXM*2],num_edge=-1; int level[MAXN],cur[MAXN]; queue <int> q; int a[MAXN],f[MAXN]; inline void add(int u, int v, int c, int f){ to[++num_edge] = v; cap[num_edge] = c; flow[num_edge] = f; nxt[num_edge] = first[u]; first[u] = num_edge; } inline bool BFS(){ memset(level, 0, sizeof(level)); while(!q.empty()) q.pop(); q.push(S); level[S] = 1; int u,v; while(!q.empty()){ u = q.front(); q.pop(); for(int i = first[u]; i!=-1; i = nxt[i]){ v = to[i]; if(!level[v] && cap[i]-flow[i]>0){ level[v] = level[u] + 1; q.push(v); } } } return level[T] != 0; } int DFS(int u, int a){ if(u == T || a == 0) return a; int ans = 0, _f,v; for(int& i = cur[u]; i != -1; i = nxt[i]){ v = to[i]; if(level[u]+1==level[v] && cap[i]-flow[i]>0){ _f = DFS(v, Min(a,cap[i]-flow[i])); ans += _f, a -= _f; flow[i] += _f, flow[i^1] -= _f; if(a == 0) break; } } return ans; } inline void Dinic(){ int ans = 0; while(BFS()){ for(int i = S; i <= T; ++i) cur[i] = first[i]; ans += DFS(S, INF*2); } printf("%d\n", ans); } int main(){ N=r; memset(first, -1, sizeof(first)); S = 1, T = 2*N+2; for(int i = 1; i <= N; ++i){ a[i]=r; add(i<<1, i<<1|1, 1, 0); add(i<<1|1, i<<1, 0, 0); } maxs = 0; for(int i = 1; i <= N; ++i){ for(int j = 1; j < i; ++j){ if(a[j] <= a[i]){ f[i] = Max(f[i], f[j]); } } ++f[i]; maxs = Max(maxs, f[i]); for(int j = 1; j < i; ++j){ if(f[j]+1 == f[i] && a[j] <= a[i]){ add(j<<1|1, i<<1, 1, 0); add(i<<1, j<<1|1, 0, 0); } } } printf("%d\n", maxs); for(int i = 1; i <= N; ++i){ if(f[i] == 1){ add(S, i<<1, 1, 0); add(i<<1, S, 0, 0); } if(f[i] == maxs){ add(i<<1|1, T, 1, 0); add(T, 1<<1|1, 0, 0); } } Dinic(); memset(flow, 0, sizeof(flow)); memset(first, -1, sizeof(first)); memset(nxt, 0, sizeof(nxt)); memset(f, 0, sizeof(f)); S = 1, T = 2*N+2; num_edge = -1; for(int i = 1; i <= N; ++i){ if(i == 1 || i == N){ add(i<<1, i<<1|1, INF/10, 0); add(i<<1|1, i<<1, 0, 0); continue; } add(i<<1, i<<1|1, 1, 0); add(i<<1|1, i<<1, 0, 0); } maxs = 0; for(int i = 1; i <= N; ++i){ for(int j = 1; j < i; ++j){ if(a[j] <= a[i]){ f[i] = Max(f[i], f[j]); } } ++f[i]; maxs = Max(maxs, f[i]); for(int j = 1; j < i; ++j){ if(f[j]+1 == f[i] && a[j] <= a[i]){ add(j<<1|1, i<<1, 1, 0); add(i<<1, j<<1|1, 0, 0); } } } for(int i = 1; i <= N; ++i){ if(f[i] == 1){ if(i == 1){ add(S, i<<1, INF/10, 0); add(i<<1, S, 0, 0); }else{ add(S, i<<1, 1, 0); add(i<<1, S, 0, 0); } } if(f[i] == maxs){ if(i == N){ add(i<<1|1, T, INF/10, 0); add(T, 1<<1|1, 0, 0); }else{ add(i<<1|1, T, 1, 0); add(T, 1<<1|1, 0, 0); }; } } Dinic(); return 0; }