T1、Global warming
题目链接
给定整数 \(n \ (n \leq 2 \times 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] \bigcap \Z)\),其中 \(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
#include
#include
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;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline 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\n", 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\):
答案等于 \(\tbinom{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} \rfloor } {y} \rfloor = \lfloor \frac{S}{xy} \rfloor\),所以这样是对的。
学过数论分块的话一定知道,这样的 \(k\) (即 \(\lfloor \frac{n - 1}{i} \rfloor\)) 有一个上界 \(2 \sqrt{n - 1}\)。
\(prf\):
当 \(i \leq \sqrt{N}\) 时,\(\lfloor \frac{N}{i} \rfloor\) 不会超过 \(\sqrt{N}\) 种;
当 \(i > \sqrt{N}\) 时,\(\lfloor \frac{N}{i} \rfloor < \sqrt{N}\),不会超过 \(\sqrt{N}\) 种。
\(Q.E.D.\)
时间复杂度 \(O(r c \sqrt{n})\),空间复杂度 \(O((r + c) \sqrt{n})\)。
\(Source\):
#include
#include
#include
#include
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;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline 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\n", (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 \ne 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
#include
#include
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;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline 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;
}