洛谷 P2687 [USACO4.3]逢低吸纳Buy Low, Buy Lower/ACWing 314 低买 题解

洛谷 P2687 [USACO4.3]逢低吸纳Buy Low, Buy Lower/ACWing 314 低买 题解

2019-12-26 xiaoh

题意

给定一个有n个数的序列a(1<=n<=5000,且a中每个数在int范围内),求最长严格下降子序列(我们不妨称其为LDS,Largest Descreasing Subsequence)的长度及数量。注:当两个LDS元素完全相同时,就算元素从不同的位置取出,也算作同一个答案

题解

首先,数据很小,n只有5000,很明显长度直接套LIS的板子就行。接下来要考虑如何计算数量,并避免重复计算相同的序列。接下来,我们考虑如何在DP时算出满足要求的LDS的数目。将DP方程f[i]=Max{f[j]+1},j∈[i+1,n]且a[i]>a[j]的情况分为三类:
1、f[j]+1大于f[i],说明存在更长的序列,则f[i]=f[j]+1,而以i为开头的LDS的长度f[i]的数量tot[i]就等于tot[j];
2、f[j]+1等于f[i],说明以i为开头的LDS后的第二个数可以有多种情况,那么就都要计入tot中,即tot[i]+=tot[j];
3、f[j]小于f[i],此时,什么也做不了,就过去吧QAQ
这样我们就初步捋出了大致的算法。但仍然存在一个问题:即两个LDS的元素完全相同时,就算从不同的位置取出,也只能算一个。那么我们考虑对于一对i和j,若a[i]=a[j],且f[i]=f[j],那么很明显这两者中只需要算一个,因为数相同,长度也相同,说明两个序列一定可以重合,因此在处理完i之后j再跑一边找出符合的j,将其tot[j]设为0即可。时间复杂度O(n^2),刚好能过

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n;
int a[5010];//原数组
struct node{//num为LDS的长度,tot为总数
	int num;
	long long tot;
}f[5010];
int ans1=0;//LDS长度
long long ans2=0;//LDS数量
map<int,bool> mp;
int main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=n;i>=1;i--)
	{
		f[i].num=1;
		f[i].tot=1;
		for(int j=i+1;j<=n;j++)//计算LDS数量和长度
		if(a[i]>a[j])
		{
			if(f[j].num+1>f[i].num) f[i].num=f[j].num+1,f[i].tot=f[j].tot;
			else if(f[j].num+1==f[i].num) f[i].tot+=f[j].tot;
		}
		for(int j=i+1;j<=n;j++)//找出重复部分并删除
		if(a[i]==a[j]&&f[i].num==f[j].num) f[j].num=f[j].tot=0;
	}
	for(int i=1;i<=n;i++)
	if(f[i].num>ans1) ans1=f[i].num,ans2=f[i].tot;
	else if(f[i].num==ans1) ans2+=f[i].tot;
	printf("%d %lld\n",ans1,ans2);
	return 0;
}

后记

其实本人看到这道题的第一反应是:雾草!这道题似乎可以用map来维护重复部分诶!于是乎,O(nlgnlgn)成功的爆炸了……后来看了题解才发现原来正解那么愚蠢QAQ
理论上到这里就应该结束了,是的,在ACWing里,以上代码就能轻松的拿到AC的好成绩,但是在luogu中,你却惊喜的发现,第九个点死活也过不去,于是乎,笔者下载了一下第九个点的数据,各位看一下输出:
200 1606938044258990275541962092341162602522202993782792835301376
~~奉劝正在数位数的各位,别数了,61位。~~显然,__int128甚至long long也解决不了这个问题,但是我们又不想写高精,那该怎么办呢?突然间,我们想到了long double,这个东西好像比long long存的还多诶。所以我们将输出的种类改为用long double存(有些题解说/1e17存,实际上是不行的,因为精度丢失,直接存就能过),然后呢……AC!

Code(洛谷版)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int ret=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return w*ret;
}
int n;
int a[5010];
struct node{
    int num;
    long double tot;
}f[5010];
int ans1=0;
long double ans2=0;
map<int,bool> mp;
int main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=n;i>=1;i--)
    {
        f[i].num=1;
        f[i].tot=1;
        for(int j=i+1;j<=n;j++)
        if(a[i]>a[j])
        {
            if(f[j].num+1>f[i].num) f[i].num=f[j].num+1,f[i].tot=f[j].tot;
            else if(f[j].num+1==f[i].num) f[i].tot+=f[j].tot;
        }
        for(int j=i+1;j<=n;j++)
        if(a[i]==a[j]&&f[i].num==f[j].num) f[j].num=f[j].tot=0;
    }
    for(int i=1;i<=n;i++)
    if(f[i].num>ans1) ans1=f[i].num,ans2=f[i].tot;
    else if(f[i].num==ans1) ans2+=f[i].tot;
    printf("%d %.0Lf\n",ans1,ans2);
    return 0;
}
证据在此↓↓↓