题目描述
给定正整数序列 ,以下递增子序列均为非严格递增。
- 计算其最长递增子序列的长度s ss。
- 计算从给定的序列中最多可取出多少个长度为s ss的递增子序列。
- 如果允许在取出的序列中多次使用x 1 x_1x1和x n x_nxn,则从给定序列中最多可取出多少个长度为s ss的递增子序列。
输入格式
文件第1 11行有1 11个正整数 ,表示给定序列的长度。接下来的1 11行有n nn个正整数x 1 x_1x1~x n x_nxn 。
输出格式
第1 11行是最长递增子序列的长度 。第2 22行是可取出的长度为s ss的递增子序列个数。第3 33行是允许在取出的序列中多次使用x 1 x_1x1和x 2 x_2x2时可取出的长度为s ss的递增子序列个数。
样例输入
4
3 6 2 5
样例输出
2
2
3
分析:
在建模前,需要预处理得到d p [ i ] dp[i]dp[i]:表示以x i x_ixi结尾可以得到的最长递增子序列长度,同时可得第一问的答案——最长递增子序列的长度s ss。
对每一个点进行拆点,如点u uu拆分为u → u ′ u\rarr u'u→u′(入点为u uu,出点为u ′ u'u′),容量为1,表示仅可选一次;
对于d p [ i ] = 1 dp[i]=1dp[i]=1的点,说明可以作为最长递增子序列的起点,连边s → i s\rarr is→i,容量为∞ \infin∞;
对于d p [ i ] = s dp[i]=sdp[i]=s的点,说明可以作为最长递增子序列的终点,连边i ′ → t i'\rarr ti′→t,容量为∞ \infin∞;
而对于点之间,若d p [ i ] + 1 = d p [ j ] dp[i]+1=dp[j]dp[i]+1=dp[j],说明x j x_jxj可以接在x i x_ixi之后,连边i ′ → j i'\rarr ji′→j,容量为1。
跑最大流,即为第二问答案。
对于第三问,只需将点1和点n的拆点容量改为∞ \infin∞即可,但要注意特判s = 1 s=1s=1的情况。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int maxn = 1e6 + 10;
int n, s = 0, t = maxn - 1;
int a[maxn];
int head[maxn], cnt;
struct edge {
int w; //边的流量(残量)
int to; //该边通向的结点v
int next; //点u邻接表的下一条边
} e[maxn];
void add_edge(int u, int v, int w) //添加一条u->v,最大容量为w的边
{
//建立正向边
e[cnt].w = w;
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
cnt++;
//建立反向边
e[cnt].w = 0; //有些图是需要建立双向边(例如求最小割时),则反向边的初始残量不为0
e[cnt].to = u;
e[cnt].next = head[v];
head[v] = cnt;
cnt++;
}
int dis[maxn]; // dis数组记录层次
bool bfs() //利用BFS建立分成图,从而可以多次DFS增广
{
memset(dis, -1, sizeof(dis)); //初始化dis数组
queue<int> q;
q.push(s);
dis[s] = 0; //源点层次为0
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].to;
if (e[i].w > 0 && dis[v] == -1) //可达&&未分层
{
dis[v] = dis[u] + 1; //分层
if (v == t) //若到达汇点,则分层结束,返回true
return true;
q.push(v);
}
}
}
return false; //运行到此处,说明汇点已不可达,返回false
}
int cur[maxn]; //弧优化:cur数组用于记录上一次DFS增广时u已经增广到第几条边,从而优化时间
int dfs(int u, int flow) // flow代表流入u点的最大流量
{
if (u == t)
return flow; //到达汇点,直接返回flow
for (int &i = cur[u]; i != -1; i = e[i].next) { //注意i前面用&引用,这样就可以直接改变cur[u]
int v = e[i].to;
if (dis[v] == dis[u] + 1 && e[i].w > 0) // v为u的下一层&&可达
{
int k = dfs(v, min(flow, e[i].w));
if (k > 0) {
e[i].w -= k; //正向边-=k
e[i ^ 1].w += k; //反向边+=k
return k;
}
}
}
return 0; //无法继续增广,返回0
}
int dinic() {
int ans = 0; //记录总流量
while (bfs()) //分层
{
for (int i = 0; i < maxn; i++) //初始化cur数组,即将head数组赋给cur数组
cur[i] = head[i];
while (int k = dfs(s, INF)) //增广
ans += k;
}
return ans;
}
int dp[1010], max_s;
void init() {
memset(head, -1, sizeof(head));
cnt = 0;
}
int main() {
init();
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++) {
if (a[j] <= a[i])
dp[i] = max(dp[i], dp[j] + 1);
}
max_s = max(max_s, dp[i]);
}
printf("%d\n", max_s);
for (int i = 1; i <= n; i++) {
add_edge(i, n + i, 1);
if (dp[i] == 1)
add_edge(s, i, INF);
if (dp[i] == max_s)
add_edge(n + i, t, INF);
for (int j = 1; j < i; j++) {
if (a[j] <= a[i] && dp[j] + 1 == dp[i])
add_edge(n + j, i, 1);
}
}
printf("%d\n", dinic());
init();
for (int i = 1; i <= n; i++) {
if (i == 1 || i == n)
add_edge(i, n + i, INF);
else
add_edge(i, n + i, 1);
if (dp[i] == 1)
add_edge(s, i, INF);
if (dp[i] == max_s)
add_edge(n + i, t, INF);
for (int j = 1; j < i; j++) {
if (a[j] <= a[i] && dp[j] + 1 == dp[i])
add_edge(n + j, i, 1);
}
}
if (max_s == 1)
printf("%d\n", n); //特判
else
printf("%d\n", dinic());
return 0;
}