Logo ryp 的博客

博客

标签
暂无

MX718 模拟赛记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-18 15:36:26

7 月 18 日模拟赛

A

题目里头说 $S_i = 1$ 当且仅当 $i \equiv b \pmod m$,我们就知道一个串中如果两个 $1$ 的距离不是 $m$ 就肯定无解。场上没有理解这个充要条件,以为是必要,被捆爆了。

判完段内的无解,我们就把问题转化为了:对于每个 $S_i$ 有一个 $z_i$ 表示其第一个 $1$,这显然是小于 $m$ 的。我们于是建出 $[0, m)$ 的所有点,表示当前长度模掉 $m$ 的值。

对于每一个串,我们需要从 $b - z_i \bmod m$ 转移过来,然后转移到 $b - z_i + \lvert S\rvert\bmod m$。

于是问题就转化为了一条从 $0$ 到 $0$ 的欧拉回路。跑就完了。

B 跳蚤市场

题意:给定 $l_i, r_i, v_i$,对方可任取 $w_i\in [l_i, r_i]$,我们可以选一个集合 $S$,这之后 $v_i$ 被选中的概率是

$$\dfrac {w_i}{w_0 + \sum_S w_i}$$

我们要求最大化这个 $v_i$ 的总期望。

我们考虑如果选某个数 $j$ 更优,就说明

$$\dfrac{\sum_S v_iw_i}{\sum_S w_i} \lt \dfrac{\sum_S v_iw_i + v_jw_j}{\sum_S w_i + w_j}$$

整理一下得到

$$\dfrac{\sum_S v_iw_i}{\sum_S w_i} \lt v_j$$

非常美妙的柿子!我们于是得知,如果 $v_i$ 不能选,那么小于 $v_i$ 的所有肯定也不选;反之不一定。

因为值域小,我们可以用线段树二分来完成。

C 约会

组合记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-21 16:15:04

$n\choose m$,从一个大小为 $n$ 的集合中选出 $m$ 个,不考虑选出顺序的方案数。

很明显,集合大小如果增加一,那么这个新元素要么选要么不选,即

$${n\choose m} = {n-1\choose m}+{n-1\choose m - 1}$$

插板法:

$n$ 个人,分成 $k$ 组的方案数量。

用插板法,相当于总共 $n - 1$ 个间隔中选出 $k$ 个,相邻两个间隔是一组,即 $n-1\choose k - 1$。

用 dp,设 $f(i, j)$ 为前 $i$ 个元素,分成 $j$ 组的方案数,有显然转移

$$f(i, j) = \sum\limits_{k=0}^{i-1} f(k, j - 1)$$

MX22 测 C 题

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-22 19:49:53

ABD 都是基本题,加起来就打了 150 值得反省。

主要说一下 C 题。

$\mathcal{O}(nm\log V)$ 的做法是比较显然的。我们从最高位枚举就可以了。

注意这个做法的本质,就是我们不断的根据最高位将当前的边集合 $E$ 分为 $E_0$ 和 $E_1$。

如果 $E_0$ 就能让图连通,我们肯定是选 $E_0$ 的,并在这上面继续这个过程;否则,我们当前的边集仍然是 $E$。

我们的答案就是按照这个过程走得到的答案。

我们发现很难独立考虑新边对每一位的贡献。

考虑每一位

放球问题

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-25 07:30:51

$n$ 个球,$m$ 个盒子,

盒相同,球相同,允许空

设 $f(i, j)$ 表示 $i$ 个球,$j$ 个盒子的方案数,有

$$f(i, j) = f(i - j, j) + f(i, j - 1)$$

由于球是不同的,所以我们不能单独考虑每个球的状态。这 $i$ 个球,要么每个都要放,也就是 $f(i - j, j)$,要么是至少一个不放,也就是 $f(i, j - 1)$。

最后答案是 $f(n, m)$。

盒相同,球相同,不许空

我们提前取出 $n$ 个球不放,于是方案数就是上一问的 $f(n - m, m)$。

盒相同,球不同,允许空

设 $f(i, j)$ 表示 $i$ 个球,$j$ 个盒子的放置

盒相同,球不同,不许空

盒不同,球相同,允许空

盒不同,球相同,不许空

盒不同,球不同,允许空

盒不同,球不同,不许空

MX26测 D 题

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-26 19:55:03

我们可以用类似点边容斥的东西,也即点的贡献是正的,边的贡献是负的,然后统计区间和,即得到总贡献。

点的贡献是显然的,那么怎么统计边的贡献呢?

边的贡献,来源于开关状态的变化。

考虑在开关管辖大小上根号分治。我们设阈值 $B$,管辖小于 $B$ 的叫做小开关,否则叫做大开关。

开关之间只有三种:对小,对大和大带小。

小开关之间的贡献,可以枚举其管辖范围暴力算;大开关由于只有 $\dfrac N B$ 个,所以可以预处理出每个大开关和哪几个相邻,然后暴力统计就可以了;

小开关和大开关带起来就不一样了。一个大开关对的小开关数量可能很多,因此不能直接做。但是我们可以考虑从小开关那里算贡献。

小开关状态变化的时候,我们枚举了它管辖的所有点;这时候,如果这个管辖的点旁边是个大开关的话,我们就在这个大开关的贡献上加一,表示它状态翻转会增加一条边。

大概就是这样,细节好像不少,写一下。

估计可以到紫了。紫并不恐怖。


更新一下细节。

如果更新的是一个小点,我们就暴力扫它的管辖范围,然后看看构成了多少个新边。同时,如果某个点旁边是一个大开关,我们需要将这个大开关的贡献加上一。

否则如果更新的是大点,我们就先看和它相邻的所有大点,然后看小点维护出来的和它的贡献即可。

MX729 B

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-07-29 14:55:27

我非常喜欢的组合题。

首先我们把贡献式化简:

$$ \begin{aligned} (\sum |A_i - B_i|)^2 &= (\sum (\max - \min))^2 \ &= (\sum(\max + \min) - 2\sum\min)^2 \ &= (2m - 2\sum\min)^2 \end{aligned} $$

这个式子拆出来之后,贡献式就变得只跟 $\min$ 有关了。我们设 $s = \sum\min$,那么一个 $s$ 的贡献就是 $(2m - 2s)^2$

于是我们现在就只需要知道一个 $s$ 对应了多少个 $A$ 与 $B$ 序列。

首先,$\min$ 的选法是可知的,是 $n + s - 1\choose s$,也就是插板。

这个时候我们已经从这两个序列中选完 $n$ 个位置了。考虑剩下 $n$ 个位置的选法。

剩下 $n$ 个数的总和是 $m - s$,并且我们选的每个数不能小于对应位置所选的最小值。

但是我们并不知道,也不能知道每个位置上选的最小值。

我们考虑枚举有 $x$ 个位置上,$A_i$ 取的是最小值,这里有一个 $n\choose x$,现在考虑对应的 $B_i$ 的方案,因为这里 $A_i$ 的方案已经在前面算过了。

由于这些 $B_i$ 至少选到最小值,我们就把最小值先选上,然后剩下 $m - s$ 分配给 $x$ 个数可空,方案数是 $x + m - s + 1\choose m - s$。

剩下的 $n - x$ 个位置,同理,$A_i$ 选不为最小值的任意数,且和为 $m - s$,方案数是显然的 $m - s - 1 \choose n - x - 1$。

最后的式子是

$$ \sum\limits_{s=0}^m \sum\limits_{x=0}^n \bigg({{n \choose x}{n+s-1\choose s}{x + m - s - 1\choose m - s}{m - s - 1 \choose n -x - 1}{(2m - 2s)^2}}\bigg) $$

祭祀

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-06-17 12:40:43

本题即求最大的独立点集。

如果设关系为两点是否可达,然后做一遍传递闭包,我们就把原问题转化为了图的最大反链。

最大反链就是说,在这条链上的所有点,都不满足这一偏序关系。我们统计最大反链的数量就是第一问的答案。

这怎么做?根据 Dilworth 定理,最大反链等于最小链覆盖,后者是经典题。匈牙利或者 Dinic 都好,前者好写。

然后看第二问,也就是构造一组解。

最开始感觉这问挺水的,好像很好构造,但是实际上比第三问麻烦。

我们先第一问匹配出来,然后从右边的非匹配点开始 DFS,右点只走非匹配边,左点只走匹配边,我们就可以知道哪些点被 DFS 到。

取集合 $S$ 代表左边被访问到与右边没有访问到的点的并,那么 $S$ 是一个最小点覆盖,它的补集就是最大独立集。

为啥?

点覆盖指的就是使得每条边的两个端点至少有一个被覆盖的点的集合。根据我们上面的构造方法,右边选取的点一定是匹配点。而如果一个右边的匹配点没有被选入,其一定是由左边某个匹配点访问到了,否则就产生了一条增广路,就不是最大匹配了。因此,一条边要么被左边点选中,要么被右边点选中。

根据这样,我们取 $S$ 的补集就能得到构造方案。

第三问要求我们判断一个点是否可能在最大独立集中。强制选择这个点,删除与它有关的所有点,然后再跑一遍就可以了。

无向图最小环计数(口胡)

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-06-17 21:16:24

明显最小环一定是简单环,那么如果枚举 $(u, v)$,跑不包含此边的最短路计数,那么 $(u, v)$ 构成的简单环的大小便是 $\rho (v) + 1$,数量就是最短路数量。

但这样会重,于是要除以环大小。

P1054 [NOIP2005 提高组] 等价表达式 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-06-18 10:33:04

看了一圈题解区只有我用了暴力展开 /jy

实际上并不难,还有点水,也不难调。

相信各位应当都会中缀表达式的计算:我们维护一个符号栈和一个数据栈,然后分类讨论根据优先级计算即可。这一点不管是别的题目还是题解区已经讲的很好了,OI Wiki 上的讲解也不错。

这篇题解的重点是把普通整数上的操作扩展到多项式。

由于只有一个字母,我们可以维护系数向量 $v$,其中 $v_k$ 代表 $a^k$ 的系数,常数就是 $a^0$ 的系数。于是,我们就可以用 $O(k)$ 时间做到加减操作。

然后考虑乘和乘方。先考虑朴素的 $O(n^2)$ 乘法。(通过手工模拟可得),代码为:

for (int i = 0; i < K; i++)
  	for (int j = 0; i + j < K; j++) 
      	r.k[i + j] += k[i] * x.k[j];

如果要求更高的速度,可以用 FFT 优化。但是本题数据范围比较小,所以不用。

乘方可以用经典的快速幂,复杂度是 $O(k^2\log n)$ 的,或者是 $O(k \log^2 n)$(FFT 优化)。

接下来就是代码时间了,其实思路是非常好想的。

但是有几点注意:

  • 数据换行符含有 \r,不能简单地用 getline

  • 最后一个点卡爆 long long,所以需要对这个多项式取模(所以不妨用 NTT

  • 括号可能不匹配 遇到这种情况,应该直接丢掉多余的括号

接下来放看起来还是很不错的代码:

#include <iostream>
#include <stack>
#include <cstring>
#define siz(x) static_cast<int> ((x).size ())
#define fi first
#define se second
#define int long long
using namespace std;
const int N = 30, K = 500, P = 998244353;

class poly {
public:
	int k[K];
	
	poly () { memset (k, 0, sizeof k); }
	poly (const poly &x) { memcpy (k, x.k, sizeof k); }
	poly &operator = (const poly &x) { memcpy (k, x.k, sizeof k); return *this; }
	poly (int x, bool flag = false) { memset (k, 0, sizeof k), k[flag] = x; }
	poly operator + (const poly &x) {
		poly r;
		for (int i = 0; i < K; i++) r.k[i] = (k[i] + x.k[i]) % P;
		return r;
	}
	
	poly operator - (const poly &x) {
		poly r;
		for (int i = 0; i < K; i++) r.k[i] = (k[i] + P - x.k[i]) % P;
		return r;
	}
	
	poly operator * (const poly &x) {
		poly r;
		for (int i = 0; i < K; i++) for (int j = 0; i + j < K; j++) (r.k[i + j] += k[i] * x.k[j] % P) %= P;
		return r;
	}
	
	poly operator ^ (int x) {
		poly r = *this, p = *this;
		--x;
		while (x) {
			if (x & 1) r = r * p;
			x >>= 1, p = p * p;
		}
		return r;
	}
	
	bool operator == (const poly &x) {
		for (int i = 0; i < K; i++) if (k[i] != x.k[i]) return false;
		return true;
	}
	
	friend ostream &operator << (ostream &out, const poly &x) {
		for (int i = K - 1; i >= 2; i--) if (x.k[i]) {
			if (x.k[i] == 1) out << "a^" << i << " + ";
			else if (x.k[i] == -1) out << "-a^" << i << " + ";
			else out << x.k[i] << "a^" << i << " + ";
		}
		if (x.k[1] == 1) out << "a + ";
		else if (x.k[1] == -1) out << "-a + ";
		else if (x.k[1]) out << x.k[1] << "a + ";
		out << x.k[0];
		return out;
	}
};


stack<char> op;
stack<poly> q;

void ins (char c)
{
	poly y, x;
	
	if (c == '(') return;
	if (q.empty ()) cout << "fuck off", exit (0);
	y = q.top (), q.pop ();
	if (q.empty ()) cout << "fuck off", exit (0);
	x = q.top (), q.pop ();
	switch (c) {
	case '+': q.push (x + y); break;
	case '-': q.push (x - y); break;
	case '*': q.push (x * y); break;
	case '^': q.push (x ^ y.k[0]); break;
	default: break;
	}
}

int prio (char c)
{
	switch (c) {
	case '(': return 0;
	case '+': case '-': return 1;
	case '*': return 2;
	case '^': return 3;
	default: return -1;
	}
}

bool space (char c) { return c == ' ' || c == '\n' || c == '\r'; }

poly expand (void)
{
	int level = 0;
	char c;

	while (!q.empty ()) q.pop ();
	while (space (c = getchar ()));
	do {
		if (isdigit (c)) {
			int x = 0;
			do x = (x * 10 % P + c - '0') % P; while (isdigit (c = getchar ()));
			while (space (c) && c != '\n') c = getchar ();
			q.push (poly (x));
			if (c == '\n') break;
			else continue;
		}
		
		switch (c) {
		case 'a': q.push (poly (1, true)); break;
		case '(': op.push ('('), level++; break;
		case ')':
			if (--level < 0) break;
			while (!op.empty () && op.top () != '(') ins (op.top ()), op.pop ();
			op.pop (); break;
		default:
			while (!op.empty () && prio (op.top ()) >= prio (c)) ins (op.top ()), op.pop ();
			op.push (c); break;
		}
		while (space (c = getchar ()) && c != '\n');
	} while (c != '\n');
	
	while (!op.empty ()) ins (op.top ()), op.pop ();
	return q.top ();
}

int read (void)
{
	int res = 0;
	char c;
	while (!isdigit (c = getchar ()));
	do res = res * 10 + c - '0'; while (isdigit (c = getchar ()));
	return res;
}

signed main (void)
{
	int n;
	poly res;
	
	res = expand ();
	n = read ();
	
	for (int i = 1; i <= n; i++) if (expand () == res) putchar ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i - 1]);
	putchar ('\n');
	return 0;
}

Lisp 语言及其解释器

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-06-18 15:45:19

Lisp 语言语法很简单:

  • (function arg1 arg2),表示以参数 arg1arg2 等等调用函数 function;例如:(+ 1 2)(write (+ 2 3))

  • (if cond body alter),是判断语句,当 cond 不为零时执行 body,否则执行 alter

  • (lambda args body),生成一个闭包,也就是通常说的函数,args 是参数,body 是语句。例如,(lambda (x y) (+ x y))(lambda (x y) (lambda (t) (if (= t 0) x y)))

这两种语法都是递归的,也就是说你可以随便嵌套。

可调用对象分两种:语言内置的与闭包。语言内置的,比如 +, read, write;闭包是自己定义的。

语法看起来很简陋,甚至连定义变量都好像做不到,但实际并不是这样。定义变量可以用 lambda 实现:

((lambda (x) (+ x 1)) 1) => 2,相当于 x = 1, x + 1

((lambda (x y) (+ x y)) 2 3),相当于 x = 2, y = 3, x + y

我们还可以利用闭包做到一些神奇的东西,比如构造链表:

((lambda (cons car cdr) (write (cdr (cons 1 2))))
(lambda (x y) (lambda (t) (if (= t 0) x y)))
(lambda (x) (x 0))
(lambda (x) (x 1)))

这类似于 C++ 中的:

pair cons (int x, int y)
{
	int helper (int t) { return t ? y : x; }
	return helper;
}

int car (pair x) { return x (0); }
int cdr (pair x) { return x (1); }

牛逼炸了。

还能递归,例如求阶乘:

(write ((lambda (this n) (if (= n 0) 1 (* n (this this (- n 1))))) (lambda (this2 n2) (if (= n2 0) 1 (* n2 (this2 this2 (- n2 1))))) (read)))

解释器

共 208 篇博客