1807: 最长上升子序列~
Time Limit: 5 Sec Memory Limit: 128 Mb Submitted: 276 Solved: 52Description
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
Hint
Source
湖南省第十二届大学生计算机程序设计竞赛
#include<bitset>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, a[N];
long long dp[N];
void init()
{
dp[0] = dp[1] = 0;
for(int i=2; i<=N-1; i++)
{
dp[i] = dp[i - 1] + i-1+i-1;
//dp[i]表示在长度为i的时候,最长上升子序列的长度等于i-1的次数,同时也可以这么理解:数列的长度为n,
//数列按从小到大的顺序排列,要求最长上升子序列的长度为n-1的次数那我们每次只移动一个数字,使得只有一个数错位
//即考虑第i个数放置的位置,1.当第i个数放在第i的位置的时候,dp[i]=dp[i-1];2.当第i个数放在第i-1的位置上的时候
//第i的位置就有i-1种方法(前面i-1个数都可以放在第i位上,就只有i上的数字是错位的)3.当第i个数放在其他位置上的时候,有i-2种方法
//当i放在前面i-2的任何位置的时候,除了i放的那个位置,其余位置的数都必须从小到大排序,这样才能保证只有一个数错位
//第i个后面的数不用管,因为会按顺序计算下去;
}
}
int check(int x)//若偏差大于1
{
int now = 1;
for(int i=1;i<=n;i++)
{
if (now == a[x]) ++now;//如果now==a[x],意味着偏差开始或者结束
if (i == x) continue;//这个时候说明x这个位置上的数要比他后面的数要大,若now再加1的话就会满足下面条件,使得输出值错误
if (a[i] && a[i] != now) return 0;//当a[i]!=0并且a[i]!=now,那么说明该数列不能成为最长上升子序列的值为n-1;
++now;
}
return 1;
}
int check(int x, int y)//前后两个数之间对换了位置,即前进又后退的情况
{
if (x != y - 1) return 0;
for(int i=1;i<=n;i++)
{
if (i == x || i == y) continue;
if (a[i] && a[i] != i) return 0;//12435768这种情况需要这样判断
}
return 1;
}
long long get(int x, int y)//只前进或者只后退的情况
{
int l = n + 1, r = 0;
for(int i=1; i<=n; i++)
{
if (a[i] && a[i] != i)//记录错位的数的位置
{
l = min(l, i);//记录最开始错位的位置
r = max(r, i);//记录最后错位的位置
}
}
for(int i=l;i<=r; i++)
{
if (a[i] && a[i] == i)//如果在错位的这一段数中有没有错位的,就说明最长上升子序列不可能为n-1,或者不满足p1,p2,…,pn中非 0 的元素互不相同。
{
return 0;
}
}
int L = l, R = n - r + 1;
for(int i=l; i>=1; i--)
{
if (a[i] == i)
{
L = l - i;//计算前面有几个0,也就是说记录有几个可以活动的位置
printf("%d#\n",L);
break;
}
}
for(int i=r; i<=n; i++)
{
if (a[i] == i)
{
R = i - r;//计算后面有几个0,也就是说记录有几个可以活动的位置
printf("%d*\n",R);
break;
}
}
return 1LL * (L - x) * (R - y);//* 1LL是为了在计算时,把int类型的变量转化为long long,然后再赋值给long long类型的变量
//前面有n个0,那么可以变换的方式就有n+1种,后面的是n个0就可以变换n种,因为最长上升子序列的长度必须为n-1。
}
long long get()//没有错位的情况,全靠0来变换
{
long long res = 0, now = 0;
for(int i=1; i<=n; i++)
{
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;
for(int i=1;i<=n;i++)
{
scanf("%d", &a[i]);
if (!a[i]) continue;//a[i]=0;
if ( abs(a[i] - i)> 1) flag = i;//abs是绝对值函数,答案只有一种或者没有
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;
}