题目大意:给出一个数列,有n个数,往后循环T次,问最长非下降子序列
分析:由于T的范围过大,无法用普通的DP求解,需要稍微处理下。
首先,我们可以发现如果只遍历第一个序列,然后推后续的想法是错误的。因为处理中间那些段时,我们的取值不全都是1。所以我们要多处理至少100段。
然后,我们能够确定初始数列中出现次数最多的项,必定会在最长子序列中一直出现。
接着,就可以用普通的DP来求解这100多段。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 111111;
int a[maxn], b[333];
int dp[maxn];
int n, T;
int main() {
while(~scanf("%d%d", &n, &T)) {
memset(b, 0, sizeof(b));
memset(dp, 0, sizeof(dp));
int k = -1;
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
b[a[i]]++;
k = max(k, b[a[i]]);
}
T--;
int p = n;
while(T && p < 10000) {
for(int i = 0; i < n; i++)
a[p++] = a[i];
T--;
}
int ans = 0;
for(int i = 0; i < p; i++) {
dp[i] = 1;
for(int j = 0; j < i; j++)
if(a[j] <= a[i])
dp[i] = max(dp[i], dp[j]+1);
ans = max(ans, dp[i]);
}
printf("%d\n", ans+k*T);
}
}
-----------------------------------------------------------------我是华丽丽的分割线-----------------------------------------------------------------
之前对这个题理解的不够透彻,这次又重新看了下CF的题解,收获颇多。
根据题目的hint,在每个数只出现一次的情况下,我们会发现,当选完n个数后,每次只能选一个数,也就是说,现在只要关注什么时候选完这n个数,最糟糕的情况就是倒序排列,如:4,3,2,1,这种情况下,就需要4次,来选完所有数。所以,设计算法的时候,我们可以先计算前n次循环时的LIS,然后加上T-n。
当每个数不只出现一次时,很容易发现,LIS的长度,就是最大出现次数×T。
Time:64ms.
如果,用二分的方法来求解代码中的LIS,可以降到15ms
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, T;
int a[maxn*maxn];
int dp[maxn*maxn];
int cnt[333];
int ans;
void LIS(int k) {
for(int i = 0; i < n*k; i++) {
dp[i] = 1;
for(int j = 0; j < i; j++)
if(a[j] <= a[i]) {
dp[i] = max(dp[i], dp[j]+1);
}
ans = max(ans, dp[i]);
}
}
int main() {
scanf("%d%d", &n, &T);a
memset(cnt, 0, sizeof(cnt));
for(int i = 0; i < n; i++) {
scanf("%d", &a[i]);
cnt[a[i]]++;
for(int j = 1; j < n; j++)
a[i+j*n] = a[i];
}
memset(dp, 0, sizeof(dp));
if(T <= n) LIS(T);
else {
LIS(n);
int p = 0;
for(int i = 0; i < n; i++)
p = max(p, cnt[a[i]]);
ans += p*(T-n);
}
printf("%d\n", ans);
return 0;
}