Loading

Cnoi2020

整体感受

先来说一下我的整体感受。

首先这个A的难度还是可以的,毕竟A题本来的定位就是送分,而且也不是纯模拟枚举就可以过的无脑题。不过提高组以上的选手大概都可以1min切?

B的话虽然水但也不是那么模板吧,反正B本来也是送分的。

这个C、D都是概率期望对数学不好的一点都不友好,而且被喷板子。

总体来说还是可以的吧,考察也还算全面。因为中间被拉去搞whk所以只切了AB和C的40分/kk。

Solution

[Cnoi2020]子弦

出现次数最多的非空子串所包含的每个单独的字符也都跟着出现了,所以长度为1的串出现次数只多不少,所以只考虑长度为1的串就行了。

然而其实这题还是后缀自动机的板子。

namespace Solve{
	static int cnt[26], ans;
	static char s[10000010];
	void MAIN() {
		scanf("%s", s + 1);
		int len = strlen(s + 1);
		for (int i = 1; i <= len; i++) cnt[s[i] - 'a']++;
		for (int i = 0; i < 26; i++) ans = max(ans, cnt[i]);
		StandardIO :: print(ans);
	}
}

[Cnoi2020]雷雨

一定是先分别到一个点然后剩下的路一起走,所以枚举那个点,然后算3个最短路就好了。(也被喷最短路板子)

namespace Solve{
	const int MAXN = 1010;
	const long long inf = 0x3f3f3f3f3f3f3f3f;
	struct node{
		int x, y;
		long long dis;
		friend bool operator < (node pp, node qq) {
			return pp.dis > qq.dis;
		}
	};
	priority_queue<node> q;
	static int n, m, a, b, c;
	static int dx[4] = {0, 0, 1, -1};
	static int dy[4] = {1, -1, 0, 0};
	static long long dis[3][MAXN][MAXN], mp[MAXN][MAXN], ans;
	bool vis[MAXN][MAXN];
	void calc(int id, int sx, int sy) {
		memset(vis, false, sizeof(vis));
		dis[id][sx][sy] = mp[sx][sy];
		while (!q.empty()) q.pop(); 
		q.push(node{sx, sy, dis[id][sx][sy]});
		while (!q.empty()) {
			node cur = q.top();
			q.pop();
			if (vis[cur.x][cur.y]) continue;
			vis[cur.x][cur.y] = true;
			for (int i = 0; i < 4; i++) {
				node nxt = node{cur.x + dx[i], cur.y + dy[i], cur.dis + mp[cur.x + dx[i]][cur.y + dy[i]]};
				if (dis[id][nxt.x][nxt.y] > nxt.dis) {
					dis[id][nxt.x][nxt.y] = nxt.dis;
					q.push(nxt);
				}
			}
		}
	}
	void MAIN() {
		StandardIO :: read(n); StandardIO :: read(m);
		StandardIO :: read(a); StandardIO :: read(b); StandardIO :: read(c);
		for (int i = n; i >= 1; i--) {
			for (int j = 1; j <= m; j++) {
				StandardIO :: read(mp[i][j]);
				dis[0][i][j] = dis[1][i][j] = dis[2][i][j] = inf;
			}
		}
		calc(0, n, a);
		calc(1, 1, b);
		calc(2, 1, c);
		ans = inf;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				ans = min(ans, dis[0][i][j] + dis[1][i][j] + dis[2][i][j] - 2 * mp[i][j]);
			}
		}
		StandardIO :: print(ans);
	}
}

[Cnoi2020]梦原

考虑一个点 \(v\) 接在另一个点 \(u\) 的后面,如果 \(a_u > a_v\) 则在消 \(u\) 的过程中就可以把 \(v\) 消掉了,所以只考虑 \(a_u<a_v\)

这种情况的贡献是 \(a_v-a_u\),类似的题:积木大赛。再乘上 \(v\) 连边的概率就好了。

实现考虑树状数组。

namespace Solve{
	const long long mod = 998244353;
	const int MAXN = 1000010;
	static int n, nn, k, h[MAXN], g[MAXN];
	static long long ans;
	static long long sum[MAXN], num[MAXN];
	int lowbit(int x) { return x & -x; }
	void Sub(int x, long long v) { while (x <= nn) sum[x] = (sum[x] - v + mod) % mod, num[x]--, x += lowbit(x); }
	void Add(int x, long long v) {while (x <= nn) sum[x] = (sum[x] + v) % mod, num[x]++, x += lowbit(x); }
	long long Ask_Sum(int x) {
		long long ret = 0;
		while (x) ret = (ret + sum[x]) % mod, x -= lowbit(x);
		return ret;
	}
	long long Ask_Num(int x) {
		long long ret = 0;
		while (x) ret = (ret + num[x]) % mod, x -= lowbit(x);
		return ret;
	}
	long long ksm(long long a, int b) {
		long long ret = 1;
		while (b) {
			if (b & 1) ret = (ret * a) % mod;
			a = (a * a) % mod;
			b >>= 1;
		}
		return ret;
	}
	void MAIN() {
		StandardIO :: read(n); StandardIO :: read(k);
		for (int i = 1; i <= n; i++) StandardIO :: read(h[i]), g[i] = h[i];
		sort(g + 1, g + 1 + n);
		nn = unique(g + 1, g + 1 + n) - g - 1;
		for (int i = 1; i <= n; i++) h[i] = lower_bound(g + 1, g + 1 + nn, h[i]) - g;
		ans = g[h[1]];
		Add(h[1], g[h[1]]);
		for (int i = 2; i <= n; i++) {
			int fir = max(1, i - k);
			if (fir > 1) Sub(h[fir - 1], g[h[fir - 1]]);
			ans = (ans + (g[h[i]] * Ask_Num(h[i]) % mod - Ask_Sum(h[i]) + mod) % mod * ksm(min(i - 1, k), mod - 2) % mod) % mod;
			Add(h[i], g[h[i]]);
		}
		StandardIO :: print(ans);
	}
}

[Cnoi2020]线形生物

近乎图上随机游走的板子。

解法一

先设每条边的出度为 \(d_i\),从 \(i\) 走到 \(n+1\) 期望走 \(f_i\) 步。则有 \(f_i=\frac{1}{d_i}\times\sum_{i\rightarrow j}f_j+1\)

分析可得:\(f_i=\frac{1}{d_i}\times(f_{i+1}+\sum_{i\rightarrow j,j\neq i+1}f_j)+1\Rightarrow f_{i+1}=d_i\times(f_i-1)-\sum_{i\rightarrow j, j \neq i+1}f_j\)

由此我们可以知道 \(f_i\) 一定是关于 \(f_1\) 的线性函数,即 \(f_i=A_if_1+B_i\)。显然 \(A_1=1,B_1=0\)

\(f_{n+1}=A_{n+1}f_1+B{n+1}=0 \Rightarrow f_1=-\frac{B_{n+1}}{A_{n+1}}\)

所以我们现在要求 \(A,B\)。根据刚刚的递推式可以得出:

\(A_{i+1}f_1+B_{i+1}=d_i(A_if_1+B_i-1)-\sum_{i\rightarrow j}(A_jf_1+B_j)\).

\(A_{i+1}=d_iA_i-\sum_{i\rightarrow j}A_j\).

\(B_{i+1}=d_i(B_i-1)-\sum_{i\rightarrow j}B_j\).

然后由于返祖边只有 \(m\) 条,所以可以在 \(O(n+m)\) 的时间内解决。

namespace Solve{
	const long long mod = 998244353;
	const int MAXN = 2000010;
	static int id, n, m;
	static long long d[MAXN], a[MAXN], b[MAXN];
	vector<int> vec[MAXN];
	long long ksm(long long x, long long y) {
		long long ret = 1;
		while (y) {
			if (y & 1) ret = (ret * x) % mod;
			x = (x * x) % mod;
			y >>= 1;
		}
		return ret;
	}
	void MAIN() {
		read(id); read(n); read(m);
		for (int i = 1, u, v; i <= m; i++) {
			read(u); read(v);
			vec[u].push_back(v);
			d[u]++;
		}
		for (int i = 1; i <= n; i++) d[i]++;
		a[1] = 1, b[1] = 0;
		for (int i = 1; i <= n; i++) {
			a[i + 1] = d[i] * a[i] % mod;
			b[i + 1] = d[i] * (b[i] - 1) % mod;
			for (int j = 0; j < (int)vec[i].size(); j++) {
				a[i + 1] = ((a[i + 1] - a[vec[i][j]]) % mod + mod) % mod;
				b[i + 1] = ((b[i + 1] - b[vec[i][j]]) % mod + mod) % mod;
			}
		}
		print(((-b[n + 1] % mod + mod) % mod * ksm(a[n + 1], mod - 2) % mod + mod) % mod);
	}
}

解法二

定义 \(f_i\) 代表从 \(i\)\(i+1\) 的期望步数。

则有 \(f_i=\frac{1}{d_i}(1+\sum_{i\rightarrow j}sum_i-sum_{j-1})\)。其中 \(sum_i\)\(f\) 的前缀和。再把 \(f_i\)\(sum_i\) 分离出来也可以做。

最后答案是 \(\sum_{i=1}^{n}f_i\)

posted @ 2020-09-20 23:44  Gemini7X  阅读(339)  评论(0编辑  收藏  举报
Title