校内测试2020-7-13校内测试

Posted youthrhythms

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了校内测试2020-7-13校内测试相关的知识,希望对你有一定的参考价值。

2020-7-13校内测试总结+题解。

比赛链接

点击打开链接

比赛经历

打开 A 题,发现是个套路拆位。15分钟码完,搁着,不想对拍。

打开 B 题,发现是个 dp 或者贪心之类的。搁着,去看 C

打开 C 题,发现数据 (nle 10^9) 感觉是个矩阵乘法(巧了我恰好也讲矩阵乘法)。发现 80pts 比较简单。但还没有想好如何用矩阵优化。

回来看 B 题,写了一个 (mathcal O(n^2)) 的暴力和一个 (C_i=1)(mathcal O(n)) 的贪心部分分。仔细想想,发现这转移好像是一次函数的转移,那么最优转移应该是凸包上的点。想到一个感觉方向正确办法,先搁着,写 C 的暴力。

再看 C 题,写了一个 80 分的暴力。然后开始码 B 题的“方向正确的办法”。

考完发现方向正确的办法是错的(但方向的确正确),C 题的暴力写挂了,身败名裂。

A-幸运值

题解

常见套路。

首先拆位,然后对于每一位都转化为只有 0/1 的问题。所以只要有奇数个 1 这一位就有贡献。考虑枚举有多少个 1,然后使用组合数计算即可。

时间复杂度 (mathcal O(nlog_2 n))

代码

#include <cstdio>
using namespace std;
typedef long long ll;
const int CN = 1e5 + 5, MOD = 998244353;
int ad(int x, int y) { return ((x + y) > MOD) ? (x + y - MOD) : (x + y); }
int dc(int x, int y) { return ((x - y) < 0) ? (x - y + MOD) : (x - y); }
int ml(int x, int y) { return (ll) x * y % MOD; }
int ksm(int x, int y) {
	int ret = 1;
	for (; y; y >>= 1, x = ml(x, x))
		if (y & 1) ret = ml(ret, x);
	return ret;
}
int N, K, A[CN], fac[CN], ifac[CN];
void Init() {
	fac[0] = 1;
	for (int i = 1; i <= N; ++i) fac[i] = ml(fac[i - 1], i);
	for (int i = 0; i <= N; ++i) ifac[i] = ksm(fac[i], MOD - 2);
}
int binom(int x, int y) {
	if (x < 0 || y < 0 || x < y) return 0;
	return ml(fac[x], ml(ifac[y], ifac[x - y]));
}
int main() {
	scanf("%d%d", &N, &K);
	Init();
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
	int ans = 0;
	for (int i = 0; i <= 30; ++i) {
		int cnt[] = {0, 0};
		for (int j = 1; j <= N; ++j) ++cnt[(A[j] >> i) & 1];
		for (int num = 1; num <= K; num += 2) {
			int res = K - num;
			ans = ad(ans, ml((1 << i) % MOD, ml(binom(cnt[1], num), binom(cnt[0], res))));
		}
	}
	printf("%d
", ans);
	return 0;
}

B-公交运输

题解

下文中,决策通常指“决策点”。决策点就是“转移点”。例如:(f_i)(j) 转移,那么 (j) 就是 (f_i) 决策点。

最优决策指所有决策中使结果最小的决策。

不难想到想到一个暴力:

(f_i) 表示到 (i) 就换乘的最小代价。暴力枚举 (1le j < i-1) ,转移即可。
具体地,这样转移:

[f_i=min_{j=1}^{i-1}{f_j+V_jcdotfrac{i-j}{C_j}} ]

当然转移的时候需要判断 (i) 是否可以走到 (j)

可以发现转移是以 (i) 为主元是一个一次函数((j) 固定)。所以最优决策是一个凸包的样子,维护加入直线和求凸包上点值,李超树维护即可。

而且复杂度应该是 (mathcal O(ncdot maxccdot log^2n)) 的,明显过不了。

可以发现一个性质:如果 (i < j) 并且 (V_i ge V_j) ,那么必然从 (V_j) 转移而不是从 (V_i) 转移(当然是在即能从 (i) 也能从(j) 转移的前提下)。这是因为走到 (i) 后必然可以走到 (j) ,所以换乘一下就好了。

考虑维护一个决策的单调栈,每次加入一个决策就将所有栈顶 (V) 比当前决策的 (V) 大的决策弹出。

现在先意会一下最优决策的情况:对于一个 (V) 比较大的决策 (j),只可能在 (i)(j) 比较近的时候最为 (i) 的最优决策。对于一个 (V) 比较小的决策 (j) ,虽然有可能在 (i)(j) 比较近的地方不选择决策 (j) ,但是 (i) 比较大的时候就会选择了。

那么正解就呼之欲出了。我们只要知道两个决策的分界点在哪里即可,也就是将两个一次函数求交点。所以每次保证三个事情:

  • 栈内斜率单调
  • 相邻两直线交点单调
  • 栈顶两直线交点 (j) 满足 (j<i)(f_i) 是当前需要计算的值)

一个点进一次,出一次。时间复杂度 (mathcal O(ncdot maxc))

代码

#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
typedef double db;
typedef long long ll;
using namespace std;
const int CN = 1e6 + 5, inf = 0x3f3f3f3f;
const db eps = 1e-7;
int N, maxc, C[CN], V[CN], f[CN];
vector<int> kind[11][11];
bool judge(int i, int j, int t) {
	ll k1 = V[i], k2 = V[j];
	ll b1 = (ll)f[i] * C[j] - (ll)i * V[i];
	ll b2 = (ll)f[j] * C[j] - (ll)j * V[j];
	if (k1 > k2) return (b2 - b1) <= t * (k1 - k2);
	else return (b2 - b1) >= t * (k1 - k2);
}
int transfer(int i, int j) { return f[j] + (i - j) / C[j] * V[j]; }
bool cross(int i, int j, int k) {
	ll k1 = V[i], k2 = V[j], k3 = V[k];
	ll b1 = (ll)f[i] * C[j] - (ll)i * V[i];
	ll b2 = (ll)f[j] * C[j] - (ll)j * V[j];
	ll b3 = (ll)f[k] * C[k] - (ll)k * V[k];
	return (b1 - b2) * (k3 - k2) < (b2 - b3) * (k2 - k1);
}
int main() {
	memset(f, 0x3f, sizeof(f));
	scanf("%d%d", &N, &maxc);
	for (int i = 0; i < N; ++i)
		scanf("%d%d", &C[i], &V[i]);
	kind[C[0]][0].push_back(0);
	f[0] = 0;
	for (int i = 1; i <= N; ++i) {
		f[i] = inf;
		for (int j = 1; j <= maxc; ++j) {
			vector<int> &v = kind[j][i % j];
			if (!v.empty()) {
				while (v.size() >= 2 && judge(v[v.size() - 2], v.back(), i))
					v.pop_back();
				f[i] = min(f[i], f[v.back()] + (i - v.back()) / j * V[v.back()]);
			}
		}
		if (f[i] == inf) printf("-1
");
		else printf("%d
", f[i]);
		if (i != N) {
			vector<int> &v = kind[C[i]][i % C[i]];
			while (!v.empty() && V[i] <= V[v.back()])
				v.pop_back();
			while (v.size() >= 2) {
				int p1 = v[v.size() - 2], p2 = v.back();
				if (!cross(p1, p2, i))
					break ;
				v.pop_back();
			}
			v.push_back(i);
		}
	}
	return 0;
}

总结

  • 这个套路记住。类似题目有 [JSOI2011]柠檬 (考试的时候居然没有想起来)。其实这题还是蛮套路的呢。

C-密码

题解

所有匹配可以分为两类:

  • 完全被前 (m) 个字符串包含的。
  • 交错的在两个拼起来的字符串之间,但是因为长度问题并不可能跨了三个字符串。

(f_i) 表示第 (i) 个字符串中给定字符串出现次数,(f_i) 的转移是一个递推式+常数。下面就是处理拼起来的情况。为了计算相邻两个字符串之间产生的贡献,考虑记录每个字符串首尾分别是哪个单位字符串(单位字符串为题目给定的字符串)。那么预处理出来任何两个单位字符串的之间的贡献。

考虑这是个递推式,并且 (nle 10^9) 所以需要使用矩阵乘法优化转移。

代码

但如何优化还没想好,更别说代码了。

总结

代码还没写好,更别说总结了。


以上是关于校内测试2020-7-13校内测试的主要内容,如果未能解决你的问题,请参考以下文章

校内服务器压力测试

18清明校内测试T1

10.5校内测试DP概率

8.23校内测试贪心线段树优化DP

8.15校内测试队列manacher

2019.7.24 校内测试 分析+题解