2021CCPC吉林省赛题解ABCEGHIKLM

2021CCPC吉林省赛题解ABCEGHIKLM

M. Sequence

题意 ( 0.5   s 0.5\ \mathrm{s} 0.5 s)

给定一个长度为 n    ( 1 ≤ n ≤ 1 e 4 ) n\ \ (1\leq n\leq 1\mathrm{e}4) n  (1n1e4)的、元素范围为 [ − 1 e 5 , 1 e 5 ] [-1\mathrm{e}5,1\mathrm{e}5] [1e5,1e5]的序列 a [ ] a[] a[],输出 a [ ] a[] a[]的值域乘 n n n的值.

代码 -> 2021CCPC吉林省赛-M(模拟)

void solve() {
	int n; cin >> n;
	valarray<int> a(n);
	for (int i = 0; i < n; i++) cin >> a[i];

	cout << (ll)n * (a.max() - a.min());
}

int main() {
	solve();
}


A. Random Number Checker

题意 ( 0.5   s 0.5\ \mathrm{s} 0.5 s)

给定一个长度为 n    ( 1 ≤ n ≤ 1 e 4 ) n\ \ (1\leq n\leq 1\mathrm{e}4) n  (1n1e4)的、元素范围为 [ 1 , 1 e 9 ] [1,1\mathrm{e}9] [1,1e9]的整数序列 a [ ] a[] a[],若其中奇数的个数与偶数的个数相差不超过 1 1 1,输出"Good";否则输出"Not Good".

代码 -> 2021CCPC吉林省赛-A(模拟)

void solve() {
	int n; cin >> n;

	int odd = 0, even = 0;
	while (n--) {
		int a; cin >> a;
		if (a & 1) odd++;
		else even++;
	}
	
	cout << (abs(odd - even) <= 1 ? "Good" : "Not Good");
}

int main() {
	solve();
}


B. Arithmetic Exercise

题意 ( 0.5   s 0.5\ \mathrm{s} 0.5 s)

给定整数 a , b    ( 1 ≤ a ≤ b ≤ 1000 ) a,b\ \ (1\leq a\leq b\leq 1000) a,b  (1ab1000),求 a b \dfrac{a}{b} ba,四舍五入保留小数点后 k    ( 1 ≤ k ≤ 1000 ) k\ \ (1\leq k\leq 1000) k  (1k1000)位.

思路

显然当且仅当 a = b a=b a=b a b \dfrac{a}{b} ba的整数部分为 1 1 1,其余情况整数部分为 0 0 0.

注意到 a b \dfrac{a}{b} ba小数点后第 1 1 1位即 10 a b \dfrac{10a}{b} b10a的整数部分,但 k k k最大为 1000 1000 1000,乘 1000 1000 1000次会爆掉.注意到对答案有贡献的只有 a a a b b b的余数,故每次 a ∗ = 10 a*=10 a=10,计算答案后 a % = b a\%=b a%=b即可.

代码 -> 2021CCPC吉林省赛-B(思维+模拟)

void solve() {
	int a, b, k; cin >> a >> b >> k;

	if (a == b) {
		cout << "1." << string(k, '0');
		return;
	}

	string ans = "0.";
	for (int i = 0; i <= k; i++) {
		a *= 10;
		ans.push_back(a / b + '0');
		a %= b;
	}

	if (ans.back() >= '5') {
		ans.pop_back();
		char tmp = ans.back() + 1;
		ans.pop_back();
		ans.push_back(tmp);
	}
	else ans.pop_back();

	cout << ans;
}

int main() {
	solve();
}


E. Great Detective TJC

题意 ( 0.5   s 0.5\ \mathrm{s} 0.5 s)

t    ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t  (1t100)组测试数据.每组测试数据给定一个长度为 n    ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n  (1n1e5)的序列 a 1 , ⋯   , a n    ( 1 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}^9) a1,,an  (1ai1e9).数据保证所有测试数据的 n n n之和不超过 5 e 5 5\mathrm{e}5 5e5.

对每组测试数据,若存在 i , j ∈ [ 1 , n ] , i ≠ j   s . t .   a i   x o r   a j = 1 i,j\in[1,n],i\neq j\ s.t.\ a_i\ \mathrm{xor}\ a_j=1 i,j[1,n],i=j s.t. ai xor aj=1,则输出"Yes";否则输出"No".

思路

用哈希表查找是否存在元素 a i   x o r   1 a_i\ \mathrm{xor}\ 1 ai xor 1即可.

代码 -> 2021CCPC吉林省赛-E(哈希表)

void solve() {
	int n; cin >> n;

	umap<int, int> cnt;
	bool flag = false;
	while (n--) {
		int a; cin >> a;
		if (cnt.count(a ^ 1)) flag = true;
		cnt[a]++;
	}
	
	cout << (flag ? "Yes" : "No") << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


L. Suzuran Loves String

题意

对字符串 s s s,有如下两种操作:①删除 s s s的尾字符;②在 s s s的尾部添加一个字符.对字符串 s s s,记从下标 i    ( 0 ≤ i < n ) i\ \ (0\leq i<n) i  (0i<n)开始的后缀为 s i s_i si,定义后缀 s i s_i si s j    ( 0 ≤ i < j < n ) s_j\ \ (0\leq i<j<n) sj  (0i<j<n)间的距离 d ( s i , s j ) d(s_i,s_j) d(si,sj)为将 s i s_i si变为 s j s_j sj的最小操作次数.求 d ( s i , s j ) d(s_i,s_j) d(si,sj)的最大值.

t t t组测试数据.每组测试数据输入一个字符串 s    ( 2 ≤ ∣ s ∣ ≤ 1 e 6 ) s\ \ (2\leq |s|\leq 1\mathrm{e}6) s  (2s1e6).数据保证所有测试数据的 ∣ s ∣ |s| s之和不超过 5 e 6 5\mathrm{e}6 5e6.

对每组测试数据,输出 d ( s i , s j ) d(s_i,s_j) d(si,sj)的最大值.

思路

l e n = ∣ s ∣ len=|s| len=s.

①若 s s s只有一种字符,则取整个串为 s i s_i si,取尾字符为 s j s_j sj,此时 d ( s i , s j ) = l e n − 1 d(s_i,s_j)=len-1 d(si,sj)=len1,显然是最大值.

②若 s s s有多种字符,找到第一个与 s [ 0 ] s[0] s[0]不同的字符 s [ i ]    ( 1 ≤ i < l e n ) s[i]\ \ (1\leq i<len) s[i]  (1i<len),取整个串为 s i s_i si,取 s [ i . . . l e n − 1 ] s[i...len-1] s[i...len1] s j s_j sj,

​ 此时 d ( s i , s j ) = l e n + ( l e n − i ) = 2 l e n − i d(s_i,s_j)=len+(len-i)=2len-i d(si,sj)=len+(leni)=2leni,显然是最大值.

代码 -> 2021CCPC吉林省赛-L(思维)

void solve() {
	string s; cin >> s;

	int len = s.length();
	if (s == string(len, s[0])) cout << len - 1 << endl;
	else {
		for (int i = 1; i < len; i++) {
			if (s[i] != s[0]) {
				cout << 2 * len - i << endl;
				return;
			}
		}
	}
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


K. Bracket Sequence

题意 ( 0.5   s 0.5\ \mathrm{s} 0.5 s)

给定整数 n , k    ( 1 ≤ k , n ≤ 1 e 5 ) n,k\ \ (1\leq k,n\leq 1\mathrm{e}5) n,k  (1k,n1e5),求长度为 2 n 2n 2n的、包含 k k k种括号的合法括号序列数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.

思路

长度为 2 n 2n 2n的、包含 1 1 1种括号的合法括号序列数即Catalan数 H n H_n Hn.

k k k种括号时,只需考虑左括号,共 n n n个,每个左括号有 k k k种选择,另外 n n n个右括号只能取与其对应的左括号的相同类型,方案数为 k n k^n kn.

综上, a n s = H n ⋅ k n ans=H_n\cdot k^n ans=Hnkn.

代码 -> 2021CCPC吉林省赛-K(Catalan数)

const int MAXN = 2e5 + 5;
const int MOD = 1e9 + 7;
int fac[MAXN], ifac[MAXN];

void init() {  // 预处理阶乘及其逆元
	fac[0] = 1;
	for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;

	ifac[MAXN - 1] = qpow(fac[MAXN - 1], MOD - 2, MOD);
	for (int i = MAXN - 1; i; i--) ifac[i - 1] = (ll)ifac[i] * i % MOD;
}

int C(int n, int m) {  // 组合数C(n,m)
	return (ll)fac[n] * ifac[m] % MOD * ifac[n - m] % MOD;
}

void solve() {
	init();

	int n, k; cin >> n >> k;

	int ans = (ll)C(2 * n, n) * qpow(n + 1, MOD - 2, MOD) % MOD * qpow(k, n, MOD) % MOD;
	cout << ans;
}

int main() {
	solve();
}


H. Visit the Park

题意 ( 2   s 2\ \mathrm{s} 2 s)

给定一张包含 n n n个节点、 m m m条边的无向图.某人的路径是 a 1 , ⋯   , a k a_1,\cdots,a_k a1,,ak,他从节点 a 1 a_1 a1出发,严格按照路径行进.当他在节点 a i a_i ai时,他会随机地选择一条从节点 a i a_i ai到节点 a i + 1 a_{i+1} ai+1的路径并行进到节点 a i + 1 a_{i+1} ai+1.注意他可能多次经过同一节点.每条边上有一个 1 ∼ 9 1\sim 9 19的整数,当他通过该边时,他会将该数字从左到右地写在纸上,到达终点后纸上是一个 ( k − 1 ) (k-1) (k1)位数.求他到达终点后纸上的数字的期望,答案对 998244853 998244853 998244853取模.

第一行输入三个整数 n , m , k    ( 2 ≤ n , m , k ≤ 3 e 5 ) n,m,k\ \ (2\leq n,m,k\leq 3\mathrm{e}5) n,m,k  (2n,m,k3e5).接下来 m m m行每行输入三个整数 u , v , w    ( 1 ≤ u , v ≤ n , u ≠ v , 1 ≤ w ≤ 9 ) u,v,w\ \ (1\leq u,v\leq n,u\neq v,1\leq w\leq 9) u,v,w  (1u,vn,u=v,1w9),表示存在一条从节点 u u u到节点 v v v的无向边,其上的数字为 w w w.最后一行输入 k k k个整数 a 1 , ⋯   , a k    ( 1 ≤ a i ≤ n , a i ≠ a i + 1 ) a_1,\cdots,a_k\ \ (1\leq a_i\leq n,a_i\neq a_{i+1}) a1,,ak  (1ain,ai=ai+1).

若他无法到达终点,输出"Stupid Msacywy!";否则输出他到达终点后纸上的数字的期望,答案对 998244853 998244853 998244853取模.

思路

类似于游走,求出路径路径数、边权和,更新答案即可.

注意模数不是 998244353 998244353 998244353.

代码 -> 2021CCPC吉林省赛-H(游走)

const int MAXN = 3e5 + 5;
const int MOD = 998244853;
int n, m, k;  // 节点数、边数、路径节点数
vii edges[MAXN];
int a[MAXN];  // 路径

void get(int u, int v, int& cnt, int& sum) {  // 求从节点u到节点v的路径数cnt和边权和sum
	cnt = sum = 0;  // 注意清空

	for (auto& [nxt, w] : edges[u]) {
		if (nxt == v) {
			cnt++;
			sum += w;
		}
	}
}

void solve() {
	cin >> n >> m >> k;
	while (m--) {
		int u, v, w; cin >> u >> v >> w;
		edges[u].push_back({ v,w }), edges[v].push_back({ u,w });
	}
	for (int i = 0; i < k; i++) cin >> a[i];

	ll ans = 0;
	for (int i = 0; i < k - 1; i++) {
		int cnt = 0, sum = 0;  // 路径数、边权和
		get(a[i], a[i + 1], cnt, sum);

		if (!cnt) {
			cout << "Stupid Msacywy!";
			return;
		}

		ans = ((ll)ans * 10 + (ll)sum * qpow(cnt, MOD - 2, MOD) % MOD) % MOD;
	}
	cout << ans;
}

int main() {
	solve();
}


G. Matrix Repair

题意 ( 0.5   s ) (0.5\ \mathrm{s}) (0.5 s)

传输 n × n n\times n n×n 0 − 1 0-1 01矩阵 A A A的过程中有些元素可能丢失,丢失的元素用 − 1 -1 1表示.原矩阵有一个行校验码、列校验码,分别是行元素的异或值、列元素的异或值,它们在传输过程中不会丢失.给定 A A A和各行的行校验码、各列的列校验码,判断是否能确定原矩阵,若能则输出原矩阵;否则输出 − 1 -1 1.

第一行输入一个整数 n    ( 1 ≤ n ≤ 1000 ) n\ \ (1\leq n\leq 1000) n  (1n1000).接下来输入一个 n × n n\times n n×n 0 − 1 0-1 01矩阵 A A A,元素范围 { − 1 , 0 , 1 } \{-1,0,1\} {1,0,1}.接下来输入 n n n个整数 r 1 , ⋯   , r n r_1,\cdots,r_n r1,,rn,表示各行的行校验码.接下来输入 n n n个整数 c 1 , ⋯   , c n c_1,\cdots,c_n c1,,cn,表示各列的列校验码.数据保证至少存在一种 − 1 -1 1的取法使得矩阵满足各行的行校验码和各列的列校验码.

思路

维护每行、列 − 1 -1 1的个数 r o w c n t [ ] rowcnt[] rowcnt[] c o l c n t [ ] colcnt[] colcnt[].显然能确定填 0 0 0还是 1 1 1的只有 − 1 -1 1的个数为 1 1 1的行或列,填入后更新 r o w c n t [ ] rowcnt[] rowcnt[] c o l c n t [ ] colcnt[] colcnt[].用BFS完成填数过程,初始时先将只有一个 − 1 -1 1的行和列入队.①对 − 1 -1 1在行的情况, − 1 -1 1所在的列号 p o s pos pos当且仅当 c o l c n t [ p o s ] colcnt[pos] colcnt[pos]减到 1 1 1时入队;②对 − 1 -1 1在列的情况, − 1 -1 1所在的行号 p o s pos pos当且仅当 r o w c n t [ p o s ] rowcnt[pos] rowcnt[pos]减到 1 1 1时入队.

代码 -> 2021CCPC吉林省赛-G(思维+BFS)

const int MAXN = 1005;
int n;
int a[MAXN][MAXN];
int rowcnt[MAXN], colcnt[MAXN];  // 行、列-1的个数
int row[MAXN], col[MAXN];  // 行校验码、列校验码

void bfs() {
	qii que;  // first为-1所在的行或列,second行为1,列为2

	// 只有一个-1的行和列入队
	for (int i = 1; i <= n; i++)
		if (rowcnt[i] == 1) que.push({ i,1 });
	for (int j = 1; j <= n; j++)
		if (colcnt[j] == 1) que.push({ j,2 });

	while (que.size()) {
		auto& [idx, op] = que.front(); que.pop();

		int cnt = 0;  // 行或列中1的个数
		int pos = -1;  // 行或列中-1的位置
		if (op == 1) {  // 行
			for (int j = 1; j <= n; j++) {
				if (~a[idx][j]) cnt += a[idx][j];
				else pos = j;
			}

			if (!rowcnt[idx] || !colcnt[pos]) continue;  // 已无-1

			a[idx][pos] = row[idx] ^ (cnt & 1);  // 填数
			rowcnt[idx]--, colcnt[pos]--;  // 更新行、列-1的个数
			if (colcnt[pos] == 1) que.push({ pos,2 });  // 列-1的个数减到1时入队
		}
		else {  // 列
			for (int i = 1; i <= n; i++) {
				if (~a[i][idx]) cnt += a[i][idx];
				else pos = i;
			}

			if (!rowcnt[pos] || !colcnt[idx]) continue;  // 已无-1

			a[pos][idx] = col[idx] ^ (cnt & 1);  // 填数
			rowcnt[pos]--, colcnt[idx]--;  // 更新行、列-1的个数
			if (rowcnt[pos] == 1) que.push({ pos,1 });  // 行-1的个数减到1时入队
		}
	}
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			cin >> a[i][j];
			if (a[i][j] == -1) rowcnt[i]++, colcnt[j]++;
		}
	}
	for (int i = 1; i <= n; i++) cin >> row[i];
	for (int i = 1; i <= n; i++) cin >> col[i];

	bfs();

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			if (a[i][j] == -1) {
				cout << -1;
				return;
			}
		}
	}

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) cout << a[i][j] << " \n"[j == n];
}

int main() {
	solve();
}


C. Random Number Generator

题意 ( 0.5   s ) (0.5\ \mathrm{s}) (0.5 s)

Linear Congruence Method(LCG)是产生伪随机数的经典方法,它基于递推公式 x n + 1 = ( a x n + b )   m o d   m x_{n+1}=(ax_n+b)\ \mathrm{mod}\ m xn+1=(axn+b) mod m,其中 { x n } \{x_n\} {xn}是产生的伪随机数, a , b , m , x 0 a,b,m,x_0 a,b,m,x0是给定的常数.给定数 x x x,判断它能否由上述的LCG产生.

第一行输入五个整数 a , b , m , x 0 , x    ( 0 ≤ a , b , x 0 , x < m ≤ 1 e 9 , m ∈ p r i m e s ) a,b,m,x_0,x\ \ (0\leq a,b,x_0,x<m\leq 1\mathrm{e}9,m\in primes) a,b,m,x0,x  (0a,b,x0,x<m1e9,mprimes).

x x x能由上述的LCG产生,输出"YES";否则输出"NO".

思路

本题是https://www.luogu.com.cn/problem/P3306的弱化版.

已知条件是 x 0 x_0 x0,不方便,先特判 x 0 = x x_0=x x0=x的情况,其余情况求出 x 1 x_1 x1,转化为已知 x 1 x_1 x1.

下面要判断的数 x x x t t t表示,模数 m m m p p p表示.

特判 a = 0 , 1 ; b = 0 a=0,1;b=0 a=0,1;b=0的情况,下面讨论 a ≥ 2 , b ≥ 1 a\geq 2,b\geq 1 a2,b1的情况:

先求 x n = ( a x n − 1 + b )    ( m o d   p ) x_n=(ax_{n-1}+b)\ \ (\mathrm{mod}\ p) xn=(axn1+b)  (mod p)的通项,可先求出 x n = a x n − 1 + b x_n=ax_{n-1}+b xn=axn1+b的通项,再 m o d   p \mathrm{mod}\ p mod p.由不动点法知: x n + b a − 1 = a ( x n − 1 + b a − 1 ) = ⋯ = a n − 1 ( x 1 + b a − 1 ) x_n+\dfrac{b}{a-1}=a\left(x_{n-1}+\dfrac{b}{a-1}\right)=\cdots=a^{n-1}\left(x_1+\dfrac{b}{a-1}\right) xn+a1b=a(xn1+a1b)==an1(x1+a1b).

考虑求同余方程 t + b a − 1 ≡ a n − 1 ( x 1 + b a − 1 )    ( m o d   p ) t+\dfrac{b}{a-1}\equiv a^{n-1}\left(x_1+\dfrac{b}{a-1}\right)\ \ (\mathrm{mod}\ p) t+a1ban1(x1+a1b)  (mod p)的非负整数解,其中只有 n n n是变量,整理得 a n − 1 ≡ t + b a − 1 x 1 + b a − 1    ( m o d   p ) a^{n-1}\equiv \dfrac{t+\dfrac{b}{a-1}}{x_1+\dfrac{b}{a-1}}\ \ (\mathrm{mod}\ p) an1x1+a1bt+a1b  (mod p).因 1 ≤ a − 1 ≤ p − 2 1\leq a-1\leq p-2 1a1p2,则 gcd ⁡ ( a − 1 , p ) = 1 \gcd(a-1,p)=1 gcd(a1,p)=1,可用Fermat小定理求 a − 1 a-1 a1 p p p的逆元.特判 x 1 + b a − 1 x_1+\dfrac{b}{a-1} x1+a1b p p p的倍数的情况,此时 x n = − b a − 1 x_n=-\dfrac{b}{a-1} xn=a1b,其余情况RHS是整数,转化为求 a n − 1 ≡ b ′    ( m o d   p ) a^{n-1}\equiv b'\ \ (\mathrm{mod}\ p) an1b  (mod p)的解,用BSGS算法.

特判:

(1) a = 0 a=0 a=0时,①若 x 1 = t x_1=t x1=t,则输出 1 1 1;②若 b = t b=t b=t,则输出 2 2 2;③其余情况无解.

(2) a = 1 a=1 a=1时,

​ ①若 b = 0 b=0 b=0,则 x n = x n − 1 x_n=x_{n-1} xn=xn1.若 x 1 = t x_1=t x1=t,则输出 1 1 1,其余情况无解.

​ ②通项为 x n = x 1 + ( n − 1 ) b x_n=x_1+(n-1)b xn=x1+(n1)b,转化为解不定方程 ( n − 1 ) b + x 1 ≡ t    ( m o d   p ) (n-1)b+x_1\equiv t\ \ (\mathrm{mod}\ p) (n1)b+x1t  (mod p),即求不定方程 ( n − 1 ) b + m p = t − x 1 (n-1)b+mp=t-x_1 (n1)b+mp=tx1最小正整数解,用扩展Euclid算法.

代码 -> 2021CCPC吉林省赛-C(BSGS)

int exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
		return a;
	}

	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}

ll bsgs(int a, int b, int p) {
	if (1 % p == b % p) return 0;  // 特判t=0,因p可能为1,故写1%p

	int k = sqrt(p) + 1;  // 分块数

	// 预处理出所有b * a^y (mod p)
	umap<int, int> hash;
	for (int i = 0, j = b % p; i < k; i++) {
		hash[j] = i;  // 记录值的同时记录对应的y,较大的y会覆盖较小的y
		j = (ll)j * a % p;
	}

	// 预处理出a^k
	int ak = 1;
	for (int i = 0; i < k; i++) ak = (ll)ak * a % p;

	// 遍历x∈[1,k],在哈希表中查找是否存在满足的y
	for (int i = 1, j = ak; i <= k; i++) {  // j记录(a^k)^x
		if (hash.count(j)) return (ll)i * k - hash[j];  // 返回t值

		j = (ll)j * ak % p;
	}

	return -1;  // 无解
}

void solve() {
	int a, b, m, x0, x; cin >> a >> b >> m >> x0 >> x;

	if (x0 == x) {
		cout << "YES";
		return;
	}

	int x1 = ((ll)a * x0 + b) % m;

	if (a == 0) {  // 特判
		if (x1 == x || b == x) cout << "YES";
		else cout << "NO";
	}
	else if (a == 1) {  // 特判
		if (!b) cout << (x == x1 ? "YES" : "NO");
		else {
			// 求不定方程(n-1)b + mp = t - X0的最小正整数解
			int x, y;
			exgcd(b, m, x, y);
			x = ((ll)x * (x - x1) % m + m) % m;  // 保证x是正数
			cout << (~x ? "YES" : "NO");
		}
	}
	else {
		int C = (ll)b * qpow(a - 1, m - 2, m) % m;  // b/(a-1)
		int A = (x1 + C) % m;  // 分母
		if (!A) {  // 特判A=0
			int ans = (-C + m) % m;
			cout << (ans == x ? "YES" : "NO");
		}
		else {
			int B = (x + C) % m;  // 分子
			int ans = bsgs(a, (ll)B * qpow(A, m - 2, m) % m, m);
			cout << (~ans ? "YES" : "NO");
		}
	}
}

int main() {
	solve();
}


I. Nim Game

题意

n n n堆石头,其中第 i i i堆有 a i a_i ai个.每轮A和B选择一个区间 [ l , r ] [l,r] [l,r]在其上做Nim游戏,A先手.每个玩家每次可从任一堆中取走任意数量的石头,无法操作者败.两人都采取最优策略.现有操作:选择一个区间 [ l , r ] [l,r] [l,r],令其中每堆的石头数 + = x +=x +=x.

第一行输入两个整数 n , m    ( 1 ≤ n ≤ 1 e 5 , 1 ≤ m ≤ 1 e 5 ) n,m\ \ (1\leq n\leq 1\mathrm{e}5,1\leq m\leq 1\mathrm{e}5) n,m  (1n1e5,1m1e5),分别表示石头堆数、操作数.第二行输入 n n n个整数 a 1 , ⋯   , a n    ( 1 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9) a1,,an  (1ai1e9).接下来 m m m行每行输入一个操作,格式如下:① 1   l   r   x 1\ l\ r\ x 1 l r x,表示令区间 [ l , r ] [l,r] [l,r]中每堆的石头数 + = x +=x +=x;② 2   l   r 2\ l\ r 2 l r,表示询问选择 [ l , r ] [l,r] [l,r]做Nim游戏,B是否必胜,若是则输出"Yes";否则输出"No".数据保证操作中 1 ≤ l ≤ r ≤ n , 1 ≤ x ≤ 1 e 4 1\leq l\leq r\leq n,1\leq x\leq 1\mathrm{e}4 1lrn,1x1e4.

思路

显然可用BIT实现区间修改和区间查询.因选择区间 [ l , r ] [l,r] [l,r]做Nim游戏时,若其中石头数的异或和为 0 0 0,先手必败,即后手必胜,故操作②只需考察 [ l , r ] [l,r] [l,r]中石头数的异或和,但这无法用BIT快速查询.

考虑线性基.注意到若能不将 a [ i ] a[i] a[i]插入线性基,则 a [ i ] a[i] a[i]与线性基中某些元素异或和为零,可检查 [ l , r ] [l,r] [l,r]中的元素是否都能插入线性基,若至少存在一个元素不能插入线性基,则后手必胜;否则后手必败.

代码 -> 2021CCPC吉林省赛-I(树状数组+线性基+Nim游戏)

const int MAXN = 1e5 + 5;

struct FenwickTree {
	int n;  // 数组长度
	ll a[MAXN];  // 原数组,下标从1开始
	ll BIT1[MAXN];  // 维护差分数组diff[]的前缀和
	ll BIT2[MAXN];  // 维护i*diff[i]的前缀和

	int lowbit(int x) { return x & (-x); }

	void add(ll BIT[], int x, ll c) {  // BIT[x]+=c
		for (int i = x; i <= n; i += lowbit(i)) BIT[i] += c;
	}

	ll sum(ll BIT[], int x) {  // 求[1...x]的前缀和
		ll res = 0;
		for (int i = x; i; i -= lowbit(i)) res += BIT[i];
		return res;
	}

	ll get_pre(int x) {  // 求a[1...x]的前缀和
		return sum(BIT1, x) * (x + 1) - sum(BIT2, x);
	}

	void modify(int l, int r, ll c) {  // 区间修改:a[l...r]+=c
		add(BIT1, l, c), add(BIT2, l, c * l);
		add(BIT1, r + 1, -c), add(BIT2, r + 1, -c * (r + 1));
	}

	ll query(int l, int r) {  // 区间查询:求a[l...r]的和
		return get_pre(r) - get_pre(l - 1);
	}

	void build() {  // 对原数组建BIT
		for (int i = 1; i <= n; i++) {
			ll diff = a[i] - a[i - 1];
			add(BIT1, i, diff), add(BIT2, i, diff * i);
		}
	}
}bit;

bool insert(int x, vi& base) {  // 将数x插入线性基base,返回插入是否成功
	for (int i = 31; i >= 0; i--) {
		if (x >> i & 1) {
			if (base[i]) x ^= base[i];
			else {
				base[i] = x;  // 插入线性基
				return true;  // 插入成功
			}
		}
	}
	return false;  // 插入失败
}

void solve() {
	int m; cin >> bit.n >> m;
	for (int i = 1; i <= bit.n; i++) cin >> bit.a[i];
	bit.build();

	while (m--) {
		int op, l, r; cin >> op >> l >> r;
		if (op == 1) {
			int x; cin >> x;
			bit.modify(l, r, x);
		}
		else {
			vi base(35);  // 线性基
			bool ok = false;
			for (int i = l; i <= r; i++) {
				int a = bit.query(i, i);  // a[i]
				if (!insert(a, base)) {  // 注意是后手必胜
					ok = true;
					break;
				}
			}

			cout << (ok ? "Yes" : "No") << endl;
		}
	}
}

int main() {
	solve();
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值