洛谷P2766 最长不下降子序列问题 题解
题目链接:P2766 最长不下降子序列问题
题意:
给定正整数序列 \(x_1 \ldots, x_n\)。
- 计算其最长不下降子序列的长度 \(s\)。
- 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 \(s\) 的不下降子序列。
- 如果允许在取出的序列中多次使用 \(x_1\) 和 \(x_n\)(其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 \(s\) 的不下降子序列。
令 \(a_1, a_2, \ldots, a_s\) 为构造 \(S\) 时所使用的下标,\(b_1, b_2, \ldots, b_s\) 为构造 \(T\) 时所使用的下标。且 \(\forall i \in [1,s-1]\),都有 \(a_i \lt a_{i+1}\),\(b_i \lt b_{i+1}\)。则 \(S\) 和 \(T\) 不同,当且仅当 \(\exists i \in [1,s]\),使得 \(a_i \neq b_i\)。
输入格式:
第一行有一个正整数 \(n\),表示给定序列的长度。接下来的一行有 \(n\) 个正整数\(x_1, ..., x_n\)。
输出格式:
- 第 \(1\) 行是最长不下降子序列的长度 \(s\)。
- 第 \(2\) 行是可取出的长度为 \(s\) 的不下降子序列个数。
- 第 \(3\) 行是允许在取出的序列中多次使用 \(x_1\) 和 \(x_n\) 时可取出的长度为 \(s\) 的不同的不下降子序列个数。
数据范围:
\(1 \le n\le 500\)
第一问就是萌萌dp,我觉得 cxy 都能做。
考虑第二问怎么做。
每个点只能用一次,考虑拆点,把 \(i\) 拆成 \(i_{x},i_{y}\) ,\(i_x\) 向 \(i_y\) 连一条流量为 \(1\) 的边。
因为要是最长不下降子序列,所以选点 \(j\) 的下一个点 \(i\) 时,要严格满足 \(f_j = f_i - 1\) 。
因此我们将这样的 \(j_y\) 向 \(i_x\) 连边,流量为 \(1\) (因为对于这样的 \((j,i)\) 只支持一次匹配)
然后 \(s\) 向 \(f_i=1\) 的 \(i_x\) 连边,流量为 \(1\) 。\(f_i=s\) 的 \(i_y\) 向 \(t\) 连边,流量为 \(1\) 。
跑一遍最大流就是答案。
第三问其实就是稍微改了一改:
- \(j=1\) 时,\((j,i)\) 可以匹配多次,对应边 \((1,i)\) 。
- \(j=n \land f_n = s\) 时,\(n \to t\) 可以有多条匹配,对应边 \((n,t)\) 。
- 还有 \(j_x \to j_y~(j=1\lor(j=n\land f_n=s))\) 也可以匹配多条。
我们直接在残量网络上建新边,流量为 \(+\infty\) ,再跑一遍最大流即可,注意这是在第二问基础上的增量。
注意这里有一个很特殊的情况,需要特判,即输入 1 1
的时候,第三问我们会建出来这样的图
这个时候跑出来的最大流会变成 \(+\infty\) ,所以需要特判一下,答案是 \(1\) 。
理论时间复杂度 \(\mathcal{o}(n^4)\) ,因为边数其实远小于 \(\mathcal{O}(n^2)\) 。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(505 * 2 + 15))
#define M ((int)(N * N * 2 + 3 * N * 2))
int n,m,s,t,tot,pos=1,a[N],dp[N],dep[N],now[N],head[N];
// A : 1 ~ n, A' : n+1 ~ n+n
struct Edge { int u,v,w,next; } e[M];
void addEdge(int u,int v,int w)
{ e[++pos] = {u,v,w,head[u]}; head[u] = pos; }
void add(int u,int v,int w) { addEdge(u,v,w); addEdge(v,u,0); }
queue<int> q;
bool bfs()
{
for(int i=1; i<=tot; i++) dep[i] = INF;
dep[s] = 1; q.push(s); now[s] = head[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(e[i].w > 0 && dep[v] == INF)
{
dep[v] = dep[u] + 1;
now[v] = head[v]; q.push(v);
}
}
}
return dep[t] != INF;
}
int dfs(int u,int in)
{
if(u == t) return in;
int out = 0;
for(int i=now[u]; i && in; i = e[i].next)
{
int v = e[i].v; now[u] = i;
if(e[i].w > 0 && dep[v] == dep[u] + 1)
{
int res = dfs(v, min(in, e[i].w));
e[i].w -= res; e[i ^ 1].w += res;
in -= res; out += res;
}
}
if(!out) dep[u] = INF;
return out;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n; s = n + n + 1; tot = t = s + 1;
if(n == 1) { return cout << "1\n1\n1\n", 0; }
for(int i=1; i<=n; i++) cin >> a[i];
for(int i=1; i<=n && (dp[i] = 1); i++)
{
for(int j=1; j<i; j++)
if(a[j] <= a[i]) up(dp[i], dp[j] + 1);
up(m, dp[i]);
}
cout << m << '\n';
for(int i=1; i<=n; i++) add(i, n + i, 1); // A -> A'
for(int i=1; i<=n; i++) if(dp[i] == 1) add(s, i, 1); // s -> A
for(int i=1; i<=n; i++) if(dp[i] == m) add(n + i, t, 1); // A' -> t
for(int i=1; i<=n; i++) for(int j=1; j<i; j++)
if(a[j] <= a[i] && dp[i] - 1 == dp[j]) add(n + j, i, 1); // x -> y
int res = 0;
while(bfs()) res += dfs(s,INF);
cout << res << '\n';
add(1, n + 1, INF); add(s, 1, INF);
if(dp[n] == m) { add(n + n, t, INF); add(n, n + n, INF); }
while(bfs()) res += dfs(s,INF);
cout << res << '\n';
return 0;
}
参考文献:
[1] https://www.luogu.com.cn/blog/PopulusEuphratica/luogup2766-zui-zhang-fou-xia-xiang-zi-xu-lie-wen-ti