CSU 1807 最长上升子序列~

[ Submit][ Status][ Web Board]

Description

Bobo 在 ICPCCamp 学会了解决最长上升子序列问题后得到了一个长度为 n 的数列 p 1,p 2,…,p n.
Bobo 想用 1,2,…,n 来替换其中值为 0 的元素,使得 p 1,p 2,…,p n 互不相同(即 p 1,p 2,…,p n 是 {1,2,…,n} 的排列)。
现在 Bobo 想知道,替换后最长上升子序列的长度恰好为 (n-1) 数列的数量。

Input

输入包含不超过 300 组数据,其中不超过 20 组的 n 超过 100.
每组数据的第一行包含一个整数 n (1≤n≤10 5).
第二行包含 n 个整数p 1,p 2,…,p n  (0≤p i≤n).
保证p 1,p 2,…,p n中非 0 的元素互不相同。

Output

对于每组数据,输出一个整数表示要求的值。

Sample Input

3
0 0 0
4
0 0 0 0
5
1 0 0 4 5

Sample Output

4
9

1

先递推出dp[i]表示长度为i且最长上升子序列的长度恰好为 (n-1) 的序列的个数。

考虑第i个数放的位置,如果放在第i位,那么有dp[i-1]种,如果放在i-1位,有i-1种

放在前面i-2个位置分别有一种,所以dp[i]=dp[i-1]+2i-3;

对于一个长度为n的合法序列来说,必然是从有序的1,2,3,4,5..n

这个数列中向前或向后移动某一个数字得到的

所以对于给出的序列,分类讨论

1.如果有某个数字偏离值超过1,那么显然此时答案是只有1种或没有,判断一下即可。

2.如果有某两个数字偏离分别是+1和-1,那么同上,判断一下即可。

3.如果有偏离,判断是-1的还是+1的,对于这种情况,

找出最左和最右的偏离,并且中间有的数也必须是偏离的,

然后答案是左边可用的空间和右边可用的空间的乘积。

4.没有任何位置有偏移,那么找出全部连续的可用空间,把对应的dp值加起来即可。

#include<set>
#include<map>
#include<ctime>
#include<cmath>
#include<stack>
#include<queue>
#include<bitset>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define rep(i,j,k) for (int i = j; i <= k; i++)
#define per(i,j,k) for (int i = j; i >= k; i--)
#define loop(i,j,k) for (int i = j;i != -1; i = k[i])
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ff first
#define ss second
#define mp(i,j) make_pair(i,j)
#define pb push_back
#define pii pair<int,int>
#define in(x) scanf("%d", &x);
using namespace std;
typedef long long LL;
const int low(int x) { return x&-x; }
const double eps = 1e-8;
const int INF = 0x7FFFFFFF;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n, a[N];
LL dp[N];

void init()
{
	dp[0] = dp[1] = 0; rep(i, 2, N - 1) dp[i] = dp[i - 1] + 2 * i - 3;
}

int check(int x)
{
	int now = 1;
	rep(i, 1, n)
	{
		if (now == a[x]) ++now;
		if (i == x) continue;
		if (a[i] && a[i] != now) return 0;
		++now;
	}
	return 1;
}

int check(int x, int y)
{
	if (x != y - 1) return 0;
	rep(i, 1, n)
	{
		if (i == x || i == y) continue;
		if (a[i] && a[i] != i) return 0;
	}
	return 1;
}

LL get(int x, int y)
{
	int l = n + 1, r = 0;
	rep(i, 1, n) if (a[i] && a[i] != i) l = min(l, i), r = max(r, i);
	rep(i, l, r) if (a[i] && a[i] == i) return 0;
	int L = l, R = n - r + 1;
	per(i, l, 1) if (a[i] == i) { L = l - i; break; }
	rep(i, r, n) if (a[i] == i) { R = i - r; break; }
	return 1LL * (L - x) * (R - y);
}

LL get()
{
	LL res = 0, now = 0;
	rep(i, 1, n) if (a[i]) res += dp[a[i] - now - 1], now = a[i];
	return res + dp[n - now];
}

int main()
{
	init();
	while (scanf("%d", &n) != EOF)
	{
		int flag = 0, l = 0, r = 0;
		rep(i, 1, n)
		{
			scanf("%d", &a[i]);
			if (!a[i]) continue;
			if (abs(a[i] - i) > 1) flag = i;
			if (a[i] - i == 1) l = i;
			if (i - a[i] == 1) r = i;
		}
		if (flag) { printf("%d\n", check(flag)); continue; }
		if (l&&r) { printf("%d\n", check(l, r)); continue; }
		if (l || r) { printf("%lld\n", get(!l, !r)); continue; }
		printf("%lld\n", get());
	}
	return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值