1119考试总结
T1
题目大意:
给定一个长度为 的数列 ,初始时数列中每个元素 都不大于 。你可以在其上进行若干次操作。在一次操作中,你会选出相邻且相等的两个元素,把它们合并成一个元素,新的元素值为 (旧 元 素 值 + 1)。
请你找出,怎样的一系列操作可以让数列中的最大值变得尽可能地大?这个最大值是多少? (n <= 2^{18}, a <= 40)
题目链接
(f[i][j]), 表示(i)这个位置可以合成(j)这个数字, 并且后继为(f[i][j])."后继"就是指合成(j)这一段区间的右端点的下一位.
然后我们可以知道, 如果(f[i][j - 1], f[f[i][j - 1]][j - 1])可以被合成, 那么(f[i][j])也可以被合成, 然后使(f[i][j] = f[f[i][j - 1]][j - 1]).
如上图, 红色的那一段可以合成(j), (f[i][j - 1])存的是红色合成区间右端点的下一个位置, 也就是绿色部分的合成区间的左端点.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 3e5 + 5;
int n, ans;
int f[N][70];
int main() {
n = read();
for(int i = 1;i <= n; i++) f[i][read()] = i + 1;
for(int j = 1;j <= 60; j++)
for(int i = 1;i <= n; i++)
if(f[i][j - 1] && f[f[i][j - 1]][j - 1]) f[i][j] = f[f[i][j - 1]][j - 1], ans = j;
printf("%d", ans);
return 0;
}
T2
题目大意:
定义函数(f(n))为选取两个小于(n)的非负整数 ,使得(a *b)不是(n)的倍数的方案数。
定义函数(g(n) = displaystyle sum_{d mid n} f(d))现给出多组询问,每组询问给出一个正整数(n), 请回答(g(n))的值。 (n <= 1e9, T <= 1e4)
莫比乌斯反演.
首先推导(f(n)):
(f(n) = displaystyle n ^ 2 - sum_{a = 1}^{n}sum_{b = 1}^{n}[n mid ab]) 这一句是简化题意.
(f(n) = displaystyle n ^ 2 - sum_{a = 1}^{n}sum_{b = 1}^{n}[frac{n}{gcd(a, n)} mid frac{ab}{gcd(a, n)}] = n ^ 2 - sum_{a = 1}^{n}sum_{b = 1}^{n}[frac{n}{gcd(a, n)} mid b]), 首先(n)整除(ab), 那么他俩同时除以(gcd(a,n))依然成立, 又发现(frac{n}{gcd(a, n)} frac{a}{gcd(a, n)}) 互质, 那么可以得到(frac{n}{gcd(a, n)} mid b).
(f(n) = displaystyle n ^ 2 - sum_{a = 1}^{n}gcd(a, n)), 这里可以理解为(1)到(n)中有几个数字是(frac{n}{gcd(a,n)})的倍数, 也就是有(displaystyle frac{n}{frac{n}{gcd(a,n)}} = gcd(a,n))个.
然后开始反演:
(f(n) = displaystyle n ^ 2 - sum_{a = 1}^{n}gcd(a, n) = n ^ 2 - sum_{dmid n} d sum_{a = 1}^{n} [gcd(a, n) == d] = n ^ 2 - sum_{dmid n} d sum_{a = 1}^{n / d} [gcd(a, n/d) == 1] = n ^ 2 - sum_{dmid n} d sum_{a = 1}^{n / d} sum_{q mid gcd(a, n/d)} mu(q))
(f(n) = displaystyle n ^ 2 - sum_{dmid n} sum_{qmid frac{n}{d}} d *mu(q) sum_{a = 1}^{n / d} [q mid a] = n ^ 2 - sum_{dmid n} sum_{qmid frac{n}{d}} mu(q) id(frac{n}{d} / q) * frac{d^2q}{n} * frac{n}{dq} = n ^ 2 - sum_{dmid n} phi(n / d) * d)
然后的然后推导(g(n)):
(displaystyle g(n) = sum_{d mid n} f(d) = sum_{d mid n}d^2 - sum_{d mid n}sum_{q mid d}phi(d/q)*q = sum_{d mid n}d^2 - sum_{d mid n} phi(d)*id(d) = sum_{d mid n}d^2 - sum_{d mid n}1(n / d)(phi(d)*id(d)) = sum_{d mid n}d^2 - 1(n)(phi(n)*id(n)))
(g(n) = displaystyle sum_{d mid n}d^2 - id(n)*id(n) = sum_{d mid n}d^2 - sum_{d mid n} id(d) * id(n / d) = sum_{d mid n}d^2 - sum_{d mid n} d * n / d = sum_{d mid n}d^2 - sum_{d mid n} n)
然后(g(n))就可以(O(sqrt n))求啦.
复杂度(O(Tsqrt n)).
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
long long calc(int d, int n) { return 1ll * d * d - n; }
void work(int n) {
long long res = 0; register int i;
for(i = 1;i * i < n; i++) if(!(n % i)) res += calc(i, n), res += calc(n / i, n);
if(i * i == n) res += calc(i, n);
printf("%lld
", res);
}
int main() {
for(int T = read(); T ; T --) work(read());
return 0;
}
T3
题目大意:
你想送给 Makik 一个情人节礼物,但是手中只有一块方格纸。这张方格纸可以看作是一个由(n)行(m)列格子组成的长方形,不幸的是上面甚至还有一些格子((P)个)已经损坏了。为了让这张破破烂烂的方格纸变得像个礼物的样子,你要从中剪出一个边长不小于(l)的方框,并且损坏的格子都不能被包含在这个方框中。这里,一个边长为(s)的方框指的是大小为(s)的正方形最外层的(4*(s - 1))个格子所构成的形状。在动手剪方格纸之前,请你算一算一共有可能剪出多少种不同的方框?
$ n, m <= 4000, P <= 100000$
树状数组 + 离散化 + 前缀和 + 差分.
首先我们可以预处理出一个格子((i,j))向左, 向右, 向上, 向下可以延伸多少格子没有障碍, 分别用(f1[i][j], f2[i][j], f3[i][j], f4[i][j])表示.
然后我们让(f1, f3)合并, (f1[i][j] = min(f1[i][j], f3[i][j])), 让(f1[i][j])表示向左上可以延伸多少格子, (f2, f4)同理, (f2)表示向右下可以延伸多少格子.
为什么要预处理这些呢? 我们知道要取出的合理部分应该是一个正方形, 那么我们可以用一条对角线来确定每一个正方形, 那我们就可以枚举一个正方形的对角线的左上角和右下角的端点, 这样就可以确定一个正方形.
单纯的枚举然后再检验是否有损坏肯定是不行的, 当我门枚举到一个右下端点时, 左上端点的合法位置其实可以用前缀和统计的, 这时候就要用到树状数组来维护这个前缀和.
对于每个右下端点, 我们可以更新的左上端点其实是一段区间, 用差分来修改可以很方便.每当我们枚举到一个右下端点时, 我们将之前记录的左上端点的差分修改到树状数组里(相当于是懒标记), 然后统计前缀和, 最后再把这个右下端点作为左上端点时对应的右下区间存起来, 等到后面差分用.(说的很乱, 具体可以看代码)
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 4005, M = 1e5 + 5;
int n, m, l, p;
int f1[N][N], f2[N][N], f3[N][N], f4[N][N];
long long ans, t[N];
bool vis[N][N];
vector <pair<int, int> > v[N];
void make_pre() {
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++)
if(!vis[i][j]) {
f1[i][j] = f1[i][j - 1] + 1;
f3[i][j] = f3[i - 1][j] + 1;
}
for(int i = n;i >= 1; i--)
for(int j = m;j >= 1; j--)
if(!vis[i][j]) {
f2[i][j] = f2[i][j + 1] + 1;
f4[i][j] = f4[i + 1][j] + 1;
}
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++) {
f1[i][j] = min(f1[i][j], f3[i][j]);
f2[i][j] = min(f2[i][j], f4[i][j]);
}
}
int lowbit(int x) { return x & -x; }
void change(int x, int y) { for(; x < N ; x += lowbit(x)) t[x] += y; return ; }
long long query(int x) { long long res = 0; for(; x ; x -= lowbit(x)) res += t[x]; return res; }
int main() {
n = read(); m = read(); l = read(); p = read();
for(int i = 1, x, y;i <= p; i++) x = read(), y = read(), vis[x][y] = 1;
make_pre();
for(int x = n, y = 1;y <= m; x == 1 ? y ++ : x --) { //枚举每一条对角线
int len = 0; memset(t, 0, sizeof(t));
for(int i = 1;i <= max(n, m); i++) v[i].clear();
for(int i = x, j = y;i <= n && j <= m; i++, j++) len ++; // 把这条对角线拉出来
for(int i = 1;i <= len; i++) {
for(int j = 0;j < (int)v[i].size(); j++) change(v[i][j].first, v[i][j].second); // 懒标记更新
if(f1[i + x - 1][i + y - 1] >= l) ans += query(i - l + 1) - query(i - f1[i + x - 1][i + y - 1]); // 当前点作为右下端点
if(f2[i + x - 1][i + y - 1] >= l) v[i + l - 1].push_back(make_pair(i, 1)), v[i + f2[i + x - 1][i + y - 1]].push_back(make_pair(i, -1)); // 当前点作为左上端点
}
}
printf("%lld", ans);
return 0;
}