T1、Global warming
给定整数 (n (n leq 2 imes 10 ^ 5)) 和 (x (x leq 10 ^ 9)),以及一个长度为 (n) 的序列 (a (a_i leq 10 ^ 9));
你可以选择一个区间 ([l,r]),然后令 (a_i = a_i + d (i in [l,r] igcap )),其中 (d) 满足 (|d|<=x);
要求最大化 (a) 的最长上升子序列的长度,并输出该值。
(Sol):
有一个比较明显的性质:
给 ([l, r]) 加 (d) 不比 给 ([l, n]) 加 (d) 优;
给 ([l, r]) 减 (d) 不比 给 ([1, r]) 加 (d) 优。
而且给一个前缀减 (d) 和给一个后缀加 (d) 本质是一样的。
预处理前后缀的 (lis),从左至右枚举后缀的起点,用权值线段树维护前缀 (lis) 长度最大值即可。
由于权值过大,离散化后树状数组常数会小一点,但是我懒线段树也很优秀。
时间复杂度 (O(n log_2 n + n log_2 a_i))。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
inline int max(int _, int __) { return _ > __ ? _ : __; }
const int N = 2e5 + 5, M = 2e9;
int n, x, a[N], res;
int f[N], len;
int pre[N], suf[N];
struct segment_tree {
int t[N * 71], c[N * 71][2], rt, tot;
inline void clear() {
tot = 0;
}
inline int new_node() {
++tot;
t[tot] = c[tot][0] = c[tot][1] = 0;
return tot;
}
void modify(int pos, int k, int tl, int tr, int &p) {
if (p > tot)
p = 0;
if (!p)
p = new_node();
chk_max(t[p], k);
if (tl == tr)
return chk_max(t[p], k);
int mid = ((long long)tl + tr) >> 1;
if (mid >= pos)
modify(pos, k, tl, mid, c[p][0]);
else
modify(pos, k, mid + 1, tr, c[p][1]);
}
int query_max(int l, int r, int tl, int tr, int &p) {
if (p > tot)
p = 0;
if (!p)
return 0;
if (l <= tl && tr <= r)
return t[p];
int mid = ((long long)tl + tr) >> 1;
if (mid < l)
return query_max(l, r, mid + 1, tr, c[p][1]);
if (mid >= r)
return query_max(l, r, tl, mid, c[p][0]);
return max(query_max(l, r, tl, mid, c[p][0]),
query_max(l, r, mid + 1, tr, c[p][1]));
}
} T;
void work() {
f[len = pre[1] = 1] = a[1];
for (int i = 2, p; i <= n; ++i) {
p = std::lower_bound(f + 1, f + 1 + len, a[i]) - f;
if (p == len + 1) {
f[++len] = a[i];
} else {
f[p] = a[i];
}
pre[i] = p;
}
res = pre[n];
T.modify(a[n], 1, -M, M, T.rt);
len = suf[n] = 1, f[n] = a[n];
for (int i = n - 1, p; i; --i) {
p = std::upper_bound(f + n - len + 1, f + 1 + n, a[i]) - f - 1;
if (p == n - len) {
f[n - len] = a[i];
++len;
} else {
f[p] = a[i];
}
suf[i] = n - p + 1;
chk_max(res, pre[i] + T.query_max(a[i] - x + 1, M, -M, M, T.rt));
T.modify(a[i], suf[i], -M, M, T.rt);
}
}
int main() {
//freopen("in", "r", stdin);
freopen("glo.in", "r", stdin);
freopen("glo.out", "w", stdout);
n = in(), x = in();
for (int i = 1; i <= n; ++i)
a[i] = in();
work();
printf("%d
", res);
return 0;
}
T2、Mobitel
给定一个 (r (r leq 300)) 行 (c (c leq 300)) 列的矩阵,每个格子里都有一个正整数。
问如果从左上角走到右下角,且每次只能向右或向下走到相邻格子,那么使得路径上所有数的乘积不小于 (n (n leq 10 ^ 6)) 的路径有多少条?
由于答案可能很大,所以请输出答案对 (10^9+7) 取模的结果。
(Time Limits: 6000 ms) (Memory Limits: 64 MB)
(Sol):
答案等于 ( binom{r - 1 + c - 1}{c - 1}) - 乘积 (leq n - 1) 的路径数。
显然有一个暴力 (dp):(f_{i, j, k}) 表示到 ((i, j)) 路径乘积为 (k) 的方案数,转移是显然的。
考虑优化状态数,记 (f_{i, j, k}) 表示到 ((i, j)) 还有 (k) 可以用来被格子上的数除;
(lfloor frac{ lfloor frac{S}{x}
floor } {y}
floor = lfloor frac{S}{xy}
floor),所以这样是对的。
学过数论分块的话一定知道,这样的 (k) (即 (lfloor frac{n - 1}{i} floor)) 有一个上界 (2 sqrt{n - 1})。
(prf):
当 (i leq sqrt{N}) 时,(lfloor frac{N}{i}
floor) 不会超过 (sqrt{N}) 种;
当 (i > sqrt{N}) 时,(lfloor frac{N}{i}
floor < sqrt{N}),不会超过 (sqrt{N}) 种。
(Q.E.D.)
时间复杂度 (O(r c sqrt{n})),空间复杂度 (O((r + c) sqrt{n}))。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 305, mod = 1e9 + 7;
int R, C, n, mp[N][N], f[2][N + N][2005];
int nn, num[2005], id[1000005];
inline void add(int &_, int __) {
_ += __, _ = _ < mod ? _ : _ - mod;
}
int qpow(int base, int b, int ret = 1) {
for (; b; b >>= 1, base = 1ll * base * base % mod)
if (b & 1)
ret = 1ll * ret * base % mod;
return ret;
}
int combination(const int n, const int m) {
if (n < m)
return 0;
int ret = 1;
for (int i = 2; i <= m; ++i)
ret = 1ll * ret * i % mod;
ret = qpow(ret, mod - 2);
for (int i = n - m + 1; i <= n; ++i)
ret = 1ll * ret * i % mod;
return ret;
}
int main() {
//freopen("in", "r", stdin);
freopen("mobitel.in", "r", stdin);
freopen("mobitel.out", "w", stdout);
R = in(), C = in(), n = in();
for (int i = 1; i <= R; ++i)
for (int j = 1; j <= C; ++j)
mp[i][j] = in();
for (int i = 1; i < n; i = (n - 1) / ((n - 1) / i) + 1) {
num[++nn] = (n - 1) / i;
id[(n - 1) / i] = nn;
}
int cur = 0;
f[1][1][id[(n - 1) / mp[1][1]]] = 1;
for (int i = 2; i <= R + C - 1; ++i, cur ^= 1) {
memset(f[cur], 0, sizeof(f[cur]));
for (int j = std::max(1, i - C + 1); j <= R && i + 1 - j; ++j) {
for (int k = 1; k <= nn; ++k) {
add(f[cur][j][id[num[k] / mp[j][i + 1 - j]]], f[cur ^ 1][j][k]);
add(f[cur][j][id[num[k] / mp[j][i + 1 - j]]], f[cur ^ 1][j - 1][k]);
}
}
}
cur ^= 1;
int res = 0;
for (int i = 1; i <= nn; ++i)
add(res, f[cur][R][i]);
printf("%d
", (combination(R + C - 2, C - 1) - res + mod) % mod);
return 0;
}
T3、Lottery
定义两个序列对应位置上不同的值的个数不超过 (k),则可称为 (k) 相似。
现在有一个长度为 (n (n leq 10 ^ 4)) 的序列 (a),它有 (n−l+1) 个长度为 (l (l leq 10 ^ 4)) 的子串(第 (i) 个子串为 ([i, i + l - 1]))。
(q (q leq 100)) 组询问,第 (j) 组询问给出一个 (k_j (k_j leq l)),求每个子串与多少个其它的子串可称为 (k_j) 相似。
(Memory Limits: 32MB)
(Sol):
知道 ([x, x + l - 1], [y, y + l - 1] (x
e y)) 的相似度,可以 (O(2)) (逃) 得出 ([x + 1, x + l], [y + 1, y + l]) 的相似度。
记 (f_{i, j}) 表示与第 (i) 个子串 (j) 相似的子串数量,空间复杂度 (O(n^2)) ,显然不行;
注意到询问数不超过 (100),可以预处理,将询问排序,空间复杂度降为 (O(nq)),可以通过。
时间复杂度 (O(l log_2 q + n ^ 2 + n q))。
(Source):
#include <cstdio>
#include <cstring>
#include <algorithm>
int in() {
int x = 0; char c = getchar(); bool f = 0;
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template<typename T>inline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
template<typename T>inline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }
const int N = 1e4 + 5;
struct query {
int id, k;
} b[105];
int n, L, q, a[N], id[N];
int mp[N][105], res[N];
inline bool cmp_id(const query &i, const query &j) {
return i.id < j.id;
}
inline bool cmp_k(const query &i, const query &j) {
return i.k < j.k;
}
void work() {
std::sort(b + 1, b + 1 + q, cmp_k);
for (int i = 1; i <= L; ++i)
id[i] = std::lower_bound(b + 1, b + 1 + q, (query){0, i}, cmp_k) - b;
for (int i = 1, now; i <= n - L; ++i) {
now = 0;
for (int j = 1; j <= L; ++j)
now += (a[j] != a[j + i]);
++mp[1][id[now]];
++mp[1 + i][id[now]];
for (int x = 2, y; x + i <= n - L + 1; ++x) {
y = x + i;
now -= (a[x - 1] != a[y - 1]);
now += (a[x + L - 1] != a[y + L - 1]);
++mp[x][id[now]];
++mp[y][id[now]];
}
}
for (int i = 1; i <= n - L + 1; ++i)
for (int j = 1; j <= q; ++j)
mp[i][j] += mp[i][j - 1];
std::sort(b + 1, b + 1 + q, cmp_id);
for (int i = 1; i <= q; ++i) {
for (int j = 1; j <= n - L + 1; ++j)
printf("%d ", mp[j][id[b[i].k]]);
puts("");
}
}
void input() {
n = in(), L = in();
for (int i = 1; i <= n; ++i)
a[i] = in();
q = in();
for (int i = 1; i <= q; ++i)
b[i] = (query){i, in()};
}
int main() {
//freopen("in", "r", stdin);
freopen("lottery.in", "r", stdin);
freopen("lottery.out", "w", stdout);
input();
work();
return 0;
}