down

Description
给出一个数列,求出这个序列的最长下降子序列的长度及方案数,子序列中的权值完全相同视为同一个序列
Input
第一行一个整数n,接下来一行n个整数表示序列的权值

Output
一行两个整数分别表示最长的长度及方案数

Sample Input
样例输入1:

12
68 69 54 64 68 64 70 67 78 62 98 87

样例输入2:

3
3 2 2

样例输出1:

4 2

样例输出2:

2 1
HINT
数据范围:

对于20%的数据n <= 10
对于50%的数据n <= 200
对于100%的数据n<= 5000,权值<= 100000,答案不会超出int64(pascal),long long(c++)范围


要求出最长下降子序列长度及方案数是非常容易的. 关键在于去重.
对于去重, 可以从头到尾再DP一次.
第一次DP得到f[i], 即以i为结尾的子串的个数.
第二次DP维护cnt[i], 表示以i为结尾的长度为f[i]的不同子串的个数.
具体维护方法是, j从前往后扫, 至i的位置. 假若有 f[j] == f[i] - 1 且 满足a[j] > a[i], 则将j统计入i的前一位中, 即 cnt[i] += cnt[j]. 对于f[i] == 1的位置, cnt[i]直接赋值为1, 表示有且仅有一种情况, 即为它本身. 至于去重, 假如有 a[j] == a[i] 且 j < i, 则将cnt[j]直接赋值为0, 表示以j为结尾的情况在i中都已经进行累加, 以避免后面重复计算.

这题当时不会写, 只拿了暴力分20, 真的很不应该.

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxN = (int)5e5;
int a[maxN];
int f[maxN];
long long cnt[maxN];
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("down.in", "r", stdin);
    freopen("down.out", "w", stdout);
    #endif
    int n;
    cin >> n;
    for(int i = 0; i < n; i ++)
        cin >> a[i];
    memset(f, 0, sizeof(f));
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < i; j ++)
            if(a[j] > a[i])
                f[i] = max(f[j] + 1, f[i]);
        if(! f[i])
            f[i] = 1;
    }
    for(int i = 0; i < n; i ++)
    {
        if(f[i] == 1)
            cnt[i] = 1;
        for(int j = 0; j < i; j ++)
            if(a[j] > a[i] && f[j] == f[i] - 1)
                cnt[i] += cnt[j];
        for(int j = 0; j < i; j ++)
            if(f[j] == f[i] && a[j] == a[i])
                cnt[j] = 0;
    }
    long long maxLen = 0, ans = 0;
    for(int i = 0; i < n; i ++)
    {
        if(f[i] > maxLen)
            maxLen = f[i], ans = cnt[i];
        else if (f[i] == maxLen)
            ans += cnt[i];
    }
    cout << maxLen << ' ' << ans << endl;
}
posted @ 2016-11-15 13:48  Zeonfai  阅读(154)  评论(0编辑  收藏  举报