来源:http://poj.org/problem?id=1952
题意:求最长单调递减子序列的长度以及有多少种途径到达该最长单调递减子序列。
思路:求最长单调递减子序列是比较容易的了,关键是求路径的条数。而且还要去重。如5 5 2 2 1,其实是只有一条5 2 1。该题难点就在于去重,去重方法如下:
我们在求单调递减子序列的时候,可以用一个二重for循环求,在循环中dp[i]表示的是到第i个数时的单调递减子序列的最长长度,我们用如下的for循环求递减子序列的长度:
for(int j = 0;j < i;++j){
if(num[i] < num[j]){
if(dp[j] > mmax)
mmax = dp[j];
}
}
我们设数组cnt[i]表示有几种途径到达第i个数字时的最长长度。则cnt[i]的取值决定于cnt[j](num[j] > num[i] 且 dp[j] == dp[i] - 1 且 j < i),由于要去重,所以需要倒着循环,这是个比较难想的地方。我就在这里卡了一天。。。。可以用下面的代码实现:
CLR(flag,false);
for(int j = i-1;j >= 0 ;--j){
if(num[i] < num[j] ){
if(dp[j] == mmax && !flag[num[j]]){
flag[num[j]] = true;
cnt[i] += cnt[j];
}
}
}
其中的flag数组就是去重的功能,即如果是两个相同的数都能到达num[i],则计算一个就可以,且从后计算。
附几组数据:
5
20 18 25 18 17
输出:3 2
4
20 15 10 10
输出:3 1
9
100 96 30 200 196 30 300 296 30
输出:3 3
5
5 5 2 2 1
输出:3 1
这几种情况如果都考虑了的话,基本就对了。
代码:
#include <iostream>
#include <string.h>
#include<cstdio>
using namespace std;
#define CLR(arr,val) memset(arr,val,sizeof(arr))
const int N = 5010;
int dp[N],num[N],sum[N];
bool flag[10*N];
long long cnt[N];
int main(){
//freopen("1.txt","r",stdin);
int n;
while(scanf("%d",&n) != EOF){
for(int i = 0;i < n;++i)
scanf("%d",&num[i]);
CLR(dp,0);
CLR(cnt,0);
CLR(flag,false);
dp[0] = 1;cnt[0]=1;
int mmax = 0;
for(int i = 1;i < n;++i){
mmax = 0;
for(int j = 0;j < i;++j){
if(num[i] < num[j]){
if(dp[j] > mmax)
mmax = dp[j];
}
}
CLR(flag,false);
for(int j = i-1;j >= 0 ;--j){
if(num[i] < num[j] ){
if(dp[j] == mmax && !flag[num[j]]){
flag[num[j]] = true;
cnt[i] += cnt[j];
}
}
}
if(cnt[i] == 0)cnt[i] = 1;
dp[i] = mmax+1;
}
mmax = 0;
for(int i = 0;i < n;++i){
if(mmax < dp[i])
mmax = dp[i];
}
long long ans = 0;
CLR(flag,false);
for(int i = n-1;i >= 0;--i){
if(dp[i] == mmax) {
if(!flag[num[i]]){
ans += cnt[i];
flag[num[i]] = true;
}
}
}
printf("%d %lld\n",mmax,ans);
}
return 0;
}
话说这道题卡了一天多啊,,
10417832 | ac_Bool | 1952 | Wrong Answer | C++ | 739B | 2012-07-10 19:30:37 |
10429494 | ac_Bool | 1952 | Accepted | 296K | 125MS | C++ | 1215B | 2012-07-12 08:53:09 |