1119考试总结

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_{d\mid n} d \sum_{a = 1}^{n} [gcd(a, n) == d] = n ^ 2 - \sum_{d\mid n} d \sum_{a = 1}^{n / d} [gcd(a, n/d) == 1] = n ^ 2 - \sum_{d\mid n} d \sum_{a = 1}^{n / d} \sum_{q \mid gcd(a, n/d)} \mu(q)\)

\(f(n) = \displaystyle n ^ 2 - \sum_{d\mid n} \sum_{q\mid \frac{n}{d}} d *\mu(q) \sum_{a = 1}^{n / d} [q \mid a] = n ^ 2 - \sum_{d\mid n} \sum_{q\mid \frac{n}{d}} \mu(q) id(\frac{n}{d} / q) * \frac{d^2q}{n} * \frac{n}{dq} = n ^ 2 - \sum_{d\mid 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(T\sqrt 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\n", 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;
}
posted @ 2020-11-20 08:15  C锥  阅读(105)  评论(0编辑  收藏  举报