582B. Once Again…
Tags:二分LIS 离散化 贪心 矩阵快速幂 STL
题目描述
给定一个循环节数组 a a a,和循环次数 T T T,求该数组循环 T T T 次后的数组的最长非递减子序列的长度。
输入
1 1 1 组。第一行两个数, n n n 和 T T T,分别代表循环节长度和循环次数
第二行 n n n 个数,代表循环节。
范围:
1
≤
n
≤
100
,
1
≤
T
≤
1
0
7
1 ≤ n ≤ 100,1 ≤ T ≤ 10^7
1 ≤ n ≤ 100,1 ≤ T ≤ 107
输出
输出循环后的大数组的最长非递减子序列的长度
输入样例
4 3
3 1 4 2
输出样例
5
分析
思路分析
T T T 非常的大,所以最终的 L I S LIS LIS 肯定有某种循环规律。不妨先观察一个小例子(比如 1 , 3 , 2 , 3 1,3,2,3 1,3,2,3 重复 4 4 4 次):
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
1
1
1
3
3
3
2
2
2
3
3
3
人眼 d p dp dp 一下, L I S LIS LIS 是 1 2 3 3 3 3 3 3 3 1\ 2\ 3\ 3\ 3\ 3\ 3\ 3\ 3 1 2 3 3 3 3 3 3 3。
为什么 3 3 3 出现那么多次呢?似乎是因为循环节里面 3 3 3 最多。当循环次数非常多的时候,选 3 3 3 是非常明智的。
那什么叫循环次数非常多呢?循环次数大于多少的时候可以认为再循环就统统选出现次数最多的那个数了呢?
不妨借用一下 离散化 的思维。把循环节离散化显然不影响答案(比如 10 2 5 10\ 2\ 5 10 2 5 -> 3 1 2 3\ 1\ 2 3 1 2)。
记 k k k 是离散化之后循环节编号的最大值(其实就是把循环节去重之后的剩余长度啦)
可以想到,如果循环次数多到再多一个循环节之后会导致这个新加入的循环节的每个数已经 “插不进” 之前的 L I S LIS LIS 中的时候,就是足够多了。考虑到离散化之后数的排名(编号)是连续的( [ 1 , k ] [1,k] [1,k] 中的每个值都会在循环节中出现),因此最多循环 k k k 次,再来一个循环节就怎么也 “插不进” 了,就只能选一些重复的数加入 L I S LIS LIS 中。
所以循环次数 ≥ k \ge k ≥k 之后,就 贪心 地去选出现次数最多的数即可。在循环 k k k 次之前呢暴力求就完事了。
而如果 T T T 本来就没有 k k k 那么多,那就更暴力,直接求整个 L I S LIS LIS 即可。(此时 n × T < n × k ≤ n × n n\times T< n\times k\le n\times n n×T<n×k≤n×n)
二分LIS
二分 L I S LIS LIS(非递减的)会用 u p p e r _ b o u n d upper\_bound upper_bound 动态维护当前的 L I S LIS LIS。这玩意儿要怎么感性理解呢?
个人觉得可以理解为:当一个数新加入进来之后,如果并不比末尾的数大(不能使 L I S LIS LIS 延长),那就康康能不能 “置换” 一下一个已有的、比自己大的、且置换后不影响单调性的那么一个数。这样相当于会提升整个 L I S LIS LIS 的 “潜力”,使其在后续过程更有可能去延长。
时间复杂度:
- 离散化(其实也不用真的离散化,我们只要求出那个 k k k 即可), O ( n ) O(n) O(n) 的。
- 如果 T ≤ k T\le k T≤k,直接暴力求 L I S LIS LIS,此时 n × T < n × k ≤ n × n n\times T< n\times k\le n\times n n×T<n×k≤n×n,因此最多是 O ( n 2 ) O(n^2) O(n2) 的。
- 如果 T > k T>k T>k,先求 n × k n\times k n×k 这段的 L I S LIS LIS( O ( n 2 log n ) O(n^2\log n) O(n2logn) 的)再求循环节最大出现次数( O ( n ) O(n) O(n) 的)
- 总时间复杂度: O ( n 2 log n ) O(n^2\log n) O(n2logn) 的
*另一种解法:
把 L I S LIS LIS 的 d p dp dp 过程(循环求 m a x max max)理解为矩阵的一种 “新乘法”,进而用矩阵快速幂加速,做 T T T 次幂,再遍历求最大值即可。
此种做法慢一点, O ( n 3 log T ) O(n^3\log T) O(n3logT) 的
AC代码(第一种解法)
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define GC getchar()
#define PC putchar
#define _Q_(x) {register char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;
#define _H_(x) for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define sc(x) _Q_(x)_H_(x)
#define se(x) _Q_(x)else if(_c==EOF)exit(0);_H_(x)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
template<class T>
void PRT(const T a){if(a<0){PC(45),PRT(-a);return;}if(a>=10)PRT(a/10);PC(a%10+48);}
template<class T>
void UPRT(const T a){if(a>=10)UPRT(a/10);PC(a%10+48);}
constexpr int MN(10003), MUB(300);
int a[MN];
void multi(int &n, const int T)
{
int i, k, *p;
for(k=1, p=a; k<T; ++k)
for (p+=n, i=0; i<n; ++i)
p[i] = a[i];
n*=T;
}
int LIS(const int n)
{
int sta[MN], top = 0;
sta[top++] = *a;
for (int i=1; i<n; ++i)
{
if (a[i] >= sta[top-1])
sta[top++] = a[i];
else
*std::upper_bound(sta, sta+top, a[i]) = a[i];
}
return top;
}
int main()
{
get(n,T)
int i, k, *p;
for(i=0; i<n; ++i)
sc(a[i])
if (T > n)
{
int hs[MUB+3] = {0};
for (int i=0; i<n; ++i)
++hs[a[i]];
int max_cnt = 0;
for (int i=1; i<=MUB; ++i)
if (hs[i] > max_cnt)
max_cnt = hs[i];
int ans = (T-n)*max_cnt;
multi(n, n);
ans += LIS(n);
UPRT(ans);
}
else
{
multi(n ,T);
UPRT(LIS(n));
}
return 0;
}
顺便贴个 u p p e r _ b o u n d upper\_bound upper_bound 的实现:
// UB: 查找第一个严格大于key的数。找不到的话,L就会==R(返回值==输入的R)
template <typename E>
E *upper_bound(E *L, E *R, const E key)
{
int len = R - L, half;
E *mid;
while (len > 0)
if (*(mid = L + (half = len >> 1)) > key)
len = half;
else
L = mid + 1, len = len - half - 1;
return L;
}