一. 题面:点这里
二. 思路:
首先需要感谢 良心WA题人 巨佬提供的复杂度证明思路,好人一生平安。
首先考虑答案的性质,发现答案具备单调性,所以考虑二分答案 \(m\) 。然后对于朴素的 check 你可以做到每一次都是 \(O(n)\) 的。然后枚举每一个记忆的起始数字,总复杂度 \(O(n^2\log n)\)。
考虑优化,显然枚举每一个记忆起始数字这个部分是每一次的答案贡献,我们很难优化,那么考虑 check 的部分,思考具体的过程。每一次 check 我们都是扫一遍,遇到最近的 \(x+1\) 就作为最后答案序列的一部分,这个贪心的正确性是显然的。然后你发现这就形成了一个跳跃的过程,那么我们考虑是否可以预处理维护。然后就发现这个其实是可以线性维护预处理的(细节看代码)。但是如果连续段很稠密,你发现复杂度还是会退化成朴素做法,但是因为形成了跳跃的过程,有经验的选手就会发现这显然是可以倍增的,所以搞一个倍增数组 \(f_{i,j}\) ,意义不再阐述,而 \(f_{i,0}\) 就是我们刚才预处理的东西。那么你发现我们现在就可以在 \(O(\log n)\) 的复杂度下去处理上述贪心了。所以一次 check 的复杂度就变成了 \(O(k\log n)\) 。具体的,你可以枚举每个起始值 \(x\) 的位置,然后暴力往后跳。你发现枚举起始值 \(x\) 的过程是均摊线性的。所以总时间复杂度由 枚举+二分+check 三部分组成,最终总时间复杂度为:\(O(n\log^2n)\) 。
到目前为止,我们已经可以拿到 82pts 了,但是还可以更加优化。注意到一个性质,若起始值为 \(x\) 的答案记作 \(ans\) ,那么起始值为 \(x+1\) 的答案至少为 \(\max\{0,ans-1\}\) ,所以我们考虑维护一个双指针,左端点为 \(x\) ,右端点为 \(x+ans-1\) 。然后再去利用上述的 check 去做,这样的话就省去了二分的过程,效率会更优化,但是我们希望可以量化这个复杂度(再次感谢巨佬 良心WA题人)。和第一次的优化相仿的,我们不妨采取均摊复杂度分析。考虑双指针维护的某一个区间 \([l,r]\) ,我们记他们分别在原序列中的 \(cnt\) 有一个比较显然的性质就是区间 \([l,r]\) 的出现次数一定不会超过 \(\min\{cnt_l,cnt_r\}\) 然后你发现双指针的本质就是我们按照区间 \([l,r]\) 的出现次数扫那么也是均摊线性的,所以本质上我们就去掉了二分答案中的那一层 \(\log\) 。
在最终实现上还有对代码运行速度的优化,所以建议读者阅读本人的代码以作参考。
三. Code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
inline int read() {int x=0,f=1;char ch=getchar();while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}return x * f;
}void write(int x) {if (x > 9) {write(x / 10);}putchar(x % 10 + 48);
}const int N = 2e6 + 10,LOGN = 24;
std::vector<int> pos[N];
int n,k,V,f[N][LOGN],tmp[N],a[N],Log[N];int retn_pos(int id,int v)
{if(id > n && v > V) return n + 1;return *std::upper_bound(pos[v].begin(),pos[v].end(),id);
}int lowbit(int x){return x & (-x);}
bool check(int num,int m)
{--m;int ps = 0;int tp[21];ps = retn_pos(ps,num);int cnt = 0,j;while(m){j = lowbit(m);m -= j;j = Log[j];ps = f[ps][j];tp[++cnt] = j;if(ps == n + 1) return false;}if(ps == n + 1) return false;for(int i = 1;i <= k - 1;++i){ps = retn_pos(ps,num);for(int j = 1;j <= cnt;++j){ps = f[ps][tp[j]];if(ps == n + 1) return false;}if(ps == n + 1) return false;}return true;
}int main()
{n = read(),k = read(),V = read();for(int i = 2;i <= n;++i) Log[i] = Log[i >> 1] + 1;for(int i = 1;i <= n;++i){a[i] = read();pos[a[i]].push_back(i);}for(int i = 1;i <= V;++i) pos[i].push_back(n + 1);for(int i = n;i >= 1;--i){if(!tmp[a[i] + 1]) f[i][0] = n + 1;else f[i][0] = tmp[a[i] + 1];tmp[a[i]] = i;}f[n + 1][0] = n + 1;for(int j = 1;j <= 20;++j)for(int i = 1;i <= n + 1;++i)f[i][j] = f[f[i][j - 1]][j - 1];int ans = 0;for(int i = 1;i <= V;++i){ans = std::max(0,ans - 1);while(check(i,ans + 1)) ++ans;write(ans);putchar(32);}return 0;
}