AtCoder Regular Contest 150 F Constant Sum Subsequence
定义 \(\mathrm{nxt}(i,x)\) 为最小的 \(j\) 满足 \(a_j = x\) 且 \(j > i\),\(\mathrm{pre}(i,x)\) 为最大的 \(j\) 满足 \(a_j = x\) 且 \(j < i\)。
有了上面的定义后,考虑 dp。设 \(f_s\) 表示最小的 \(i\),满足所有和为 \(s\) 的正整数序列都是 \(a_1,a_2,...,a_i\) 的子序列。则答案为 \(f_S\)(此处的 \(S\) 为原题面中的 \(S\)),初值 \(f_0 = 0\)。
考虑如何转移。枚举一个数 \(x\),那么所有和为 \(s-x\) 的序列都可以在末尾接上一个 \(x\) 得到,所以 \(f_s \ge \mathrm{nxt}(f_{s-x},x)\)。可得 \(f_s = \max\limits_{x=1}^s \mathrm{nxt}(f_{s-x},x)\)。如果使用刷表法,则 \(f_s\) 已知,\(f_{s+x} \gets \max(f_{s+x},\mathrm{nxt}(f_s,x))\)。这样朴素转移是 \(O(S^2 \log n + n)\) 的。
考虑分治优化。设当前区间为 \([l,r]\),令 \(mid = \left\lfloor\frac{l+r}{2}\right\rfloor\)。先计算 \([l,mid]\),再考虑 \([l,mid]\) 对 \([mid+1,r]\) 的贡献。还是先枚举一个 \(x\),由 \(f\) 的转移式可得 \(f\) 有单调性,则 \(\forall i \in [mid+1,r],f_i > f_{mid}\)。则我们只需要考虑可能对右区间有贡献的 \(s\),即 \(s\) 满足 \(\mathrm{nxt}(f_s,x) = \mathrm{nxt}(f_{mid},x)\)。这些 \(s\) 形成了一段区间,而这个区间的左端点就是最小的 \(i\) 满足 \(f_i \ge \mathrm{pre}(f_{mid},x)\)。于是对这段区间内的 \(s\),令 \(f_{s+x} \gets \max(f_{s+x},\mathrm{nxt}(f_{mid},x))\)。则我们需要一个区间取 \(\max\),单点查询的数据结构。可以用标记永久化的线段树实现。
总时间复杂度 \(O(n + S \log S(\log S + \log n))\)。
code
/*
p_b_p_b txdy
AThousandSuns txdy
Wu_Ren txdy
Appleblue17 txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<ll, ll> pii;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
inline ll read() {
char c = getchar();
ll x = 0;
for (; !isdigit(c); c = getchar()) ;
for (; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
return x;
}
const int maxn = 1500100;
const int maxm = 200100;
ll n, m, a[maxn], f[maxm];
vector<ll> app[maxm];
namespace SGT {
ll tree[maxm << 2];
void update(int rt, int l, int r, int ql, int qr, ll x) {
if (ql <= l && r <= qr) {
tree[rt] = max(tree[rt], x);
return;
}
int mid = (l + r) >> 1;
if (ql <= mid) {
update(rt << 1, l, mid, ql, qr, x);
}
if (qr > mid) {
update(rt << 1 | 1, mid + 1, r, ql, qr, x);
}
}
ll query(int rt, int l, int r, int x) {
if (l == r) {
return tree[rt];
}
int mid = (l + r) >> 1;
if (x <= mid) {
return max(tree[rt], query(rt << 1, l, mid, x));
} else {
return max(tree[rt], query(rt << 1 | 1, mid + 1, r, x));
}
}
}
inline ll getnxt(ll x, ll y) {
ll t = (x - 1) % n + 1, k = x - t;
auto it = upper_bound(app[y].begin(), app[y].end(), t);
if (it == app[y].end()) {
return app[y].front() + k + n;
} else {
return *it + k;
}
}
inline ll getpre(ll x, ll y) {
ll t = (x - 1) % n + 1, k = x - t;
auto it = upper_bound(app[y].begin(), app[y].end(), t);
if (it == app[y].begin()) {
return app[y].back() + k - n;
} else {
return *(--it) + k;
}
}
void solve(int l, int r) {
if (l == r) {
return;
}
int mid = (l + r) >> 1;
solve(l, mid);
for (int i = 1; i <= r - l; ++i) {
int R = min(r - i, mid);
int L = lower_bound(f + l, f + mid + 1, getpre(f[R], i)) - f;
L = max(L, mid + 1 - i);
SGT::update(1, 0, m, L + i, R + i, getnxt(f[R], i));
}
for (int i = mid + 1; i <= r; ++i) {
f[i] = SGT::query(1, 0, m, i);
}
solve(mid + 1, r);
}
void solve() {
n = read();
m = read();
for (int i = 1; i <= n; ++i) {
a[i] = read();
app[a[i]].pb(i);
}
solve(0, m);
printf("%lld\n", f[m]);
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}