Codeforces Round #736 (Div. 2)

Codeforces Round #736 (Div. 2)

CF 一个星期莫得比赛,所以现在才补。赛时过了 A~D,现在也只会 E(

据说 Div. 1 直接是数学专场了,幸好打的是 Div. 2。

A

给定质数 \(5\leq P\leq 10^9\),求构造两个数满足 \(2\leq a<b\leq P\)\(P\bmod a=P\bmod b\)​​。

质数一定是奇数,奇数 \(\bmod 2=1\),然后 \(P\bmod P-1=1\)

于是第一次 < 1min 过了 A。

B

给定一个棋盘,棋子类似国际象棋的兵的方式移动。

自己这边一排,对面一排且不移动,求能到达对面最后一排的兵的最大个数。

长得就像个贪心,正面能放就放正面,左边能放就放左边,右边能就放右边,否则不放。

于是第一次 < 4min 过了 B。(这就是梦幻开局吗

C

给定一个点集,有三种操作:

  1. 连接两个点。
  2. 断开两个点。
  3. 查询点的存活个数。

若一个点的直接连边都连向比自己编号大的点,那么它没了,同时删掉与它相连的边。

每次查询相互独立,也就是说每次查询一开始都有 \(n\) 个点或者。

看上去挺厉害但是分析一下很简单的思维题。

不难发现最终存活的所有点都不连通,再分析得到:

  1. 一个点直接连的点都比自己小时,它一定是安全的。
  2. 否则它会没。

考虑证明,\(1\) 的正确性显然,因为所有点都对它没有影响。

若一个节点有连向一个比自己大的节点,那么最终这个点不可能存在,因为最终连通块里不会有 \(>1\) 个节点。

要么在删的过程中它就没了,要么最终它或了下来,直接仅连向那个比它大的节点,然后没了。

考虑修改操作,不难想到用 map 模拟一下。(话说好像大家都用的是 set,因为根本没必要存个 pair

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;

const int N = 2e5 + 10;
int n, m, q;
map<int, bool> G[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

bool Get(int u){return (G[u].lower_bound(u) == G[u].end());}

int main(){
	n = read(), m = read();
	for(int i = 1; i <= m; i ++){
		int u = read(), v = read();
		G[u][v] = G[v][u] = true;
	}
	int ans = 0;
	for(int i = 1; i <= n; i ++) ans += Get(i);
	q = read();
	while(q --){
		int opt = read();
		if(opt == 1){
			int u = read(), v = read();
			ans -= Get(u) + Get(v);
			G[u][v] = G[v][u] = true;
			ans += Get(u) + Get(v);
		}
		else if(opt == 2){
			int u = read(), v = read();
			ans -= Get(u) + Get(v);
			G[u].erase(G[u].find(v));
			G[v].erase(G[v].find(u));
			ans += Get(u) + Get(v);
		}
		else
			printf("%d\n", ans);
	}
	return 0;
}

D

给定序列 \(A\),求最长的优秀序列。

一个序列是优秀的,当且仅当:\(m\geq 2,a_l\bmod m=a_{l+1}\bmod m=\cdots=a_r\bmod m\)

一开始挺没有思路的,然后想到的 作差 + 双指针 + 线段树维护 的做法,然后细节爆炸交了 \(5\) 发。

然后在最后 5 min 才过的 D,也没时间看大水题 E 了,排名 \(-=\inf\) 直接掉大分。

考虑对一个优秀序列作差,\(a_{l+1}-a_l,a_{l+2}-a_{l+1},\cdots,a_{r}-a_{r-1}\) 一定都是 \(m\) 的倍数。

也就是需要在差分序列上找一个最长的 \(\gcd >1\) 的序列。

直接双指针扫描,配合某种支持快速求区间 \(\gcd\) 的数据结构即可。

这里用的线段树,官方正解用的 ST 表,时间效率理论上一样的,而且线段树还可以支持修改。

一个关键细节是特判 \(n=1\) 的情况,因为这个 WA 了 \(2\) 发。

然后因为特判没有读入直接 return 又 WA 了 \(2\)​ 发,一定要记得多组数据要完整读入啊啊啊啊啊啊!

(其实还有一发是没开 long long

#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;

const int N = 2e5 + 10;
int T, n, a[N], dat[N << 2];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int Gcd(int a, int b){while(b){int c = a; a = b, b = c % b;} return a;}

void Build(int p, int l, int r){
	if(l == r) {dat[p] = a[l]; return;}
	int mid = (l + r) >> 1;
	Build(p << 1, l, mid);
	Build(p << 1 | 1, mid + 1, r);
	dat[p] = Gcd(dat[p << 1], dat[p << 1 | 1]);
}

int Query(int p, int l, int r, int L, int R){
	if(L <= l && r <= R) return dat[p];
	int mid = (l + r) >> 1;
	if(L > mid) return Query(p << 1 | 1, mid + 1, r, L, R);
	if(R <= mid) return Query(p << 1, l, mid, L, R);
	return Gcd(Query(p << 1, l, mid, L, R), Query(p << 1 | 1, mid + 1, r, L, R)); 
}

void Work(){
	n = read() - 1;
	if(!n){int a = read(); puts("1"); return;}
	int x, y = read();
	for(int i = 1; i <= n; i ++){
		x = read();
		a[i] = abs(x - y);
		y = x;
	}
	Build(1, 1, n);
	int ans = 0;
	int l = 1, r = 1;
	while(true){
		if(l > n) break;
		r = max(r, l);
		while(r + 1 <= n && Query(1, 1, n, l, r + 1) > 1)
			r ++;
		if(Query(1, 1, n, l, r) > 1) ans = max(ans, r - l + 1);
		l ++;
	}
	printf("%lld\n", ans + 1);
}

signed main(){
	T = read();
	while(T --) Work();
	return 0;
}

E

多次询问,求 \(\sum_{i=0}^{n} \binom{3i}{x}\)​​,\(n\leq 10^6\)

预处理阶乘及其逆元,然后 \(O(nq)\) 的暴力很好想。其实正解也很好想。

因为组合数的种种优美性质,求多个组合数时总是能找到递推预处理,然后 \(O(1)\) 输出的简单方法。

\(f(x,j)=\sum_{i=0}^n\binom{3i+j}{x}\),因为 \(\binom{3i+j}{x}=\binom{3i+j-1}{x-1}+\binom{si+j-1}{x}\),所以直接有:

  1. \(f(x,1)=f(x,0)+f(x-1,0)\)
  2. \(f(x,2)=f(x,1)+f(x-1,1)\)

然后简单容斥一下,因为 \(f(x,0)+f(x,1)+f(x,2)=\sum_{i=0}^{3n+2}\binom{i}{x}=\binom{3n+3}{x+1}\),所以有:

  1. \(f(x,0)=\binom{3n+3}{x+1}-f(x,1)-f(x,2)\)

简单换一下方程得到最终柿子:

  1. \(f(x,0)=\tfrac{\binom{3n+3}{x+1}-2f(x-1,0)-f(x-1,1)}{3}\)
  2. \(f(x,1)=f(x,0)+f(x-1,0)\)
  3. \(f(x,2)=f(x,1)+f(x-1,1)\)

初始化 \(f(0,0)=f(0,1)=f(0,2)=n+1\)

然后 \(ans(x)=f(x,0)\),可以 \(O(n)\) 预处理,\(O(1)\) 回答啦,总复杂度 \(O(n+q)\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 3e6 + 10;
const LL MOD = 1e9 + 7;
int n, q;
LL fac[N], inv[N], f[N][3];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

LL Pow(LL a, LL b){
	LL sum = 1;
	for(; b; b >>= 1){
		if(b & 1) sum = sum * a % MOD;
		a = a * a % MOD;
	}
	return sum;
}

void Get_Inv(){
	int t = 3 * n + 3;
	fac[0] = 1;
	for(int i = 1; i <= t; i ++) fac[i] = fac[i - 1] * i % MOD;
	inv[t] = Pow(fac[t], MOD - 2);
	for(int i = t - 1; i >= 1; i --) inv[i] = inv[i + 1] * (i + 1) % MOD;
}

LL C(int n, int m){
	if(n < m) return 0;
	return fac[n] * inv[m] % MOD * inv[n - m] % MOD;
}

int main(){
	n = read(), q = read();
	Get_Inv();
	LL Inv3 = Pow(3, MOD - 2);
	f[0][0] = f[0][1] = f[0][2] = n + 1;
	for(int i = 1; i <= n * 3; i ++){
		f[i][0] = Inv3 * (((C(3 * n + 3, i + 1) - 2 * f[i - 1][0] - f[i - 1][1]) % MOD + MOD) % MOD) % MOD;
		f[i][1] = (f[i][0] + f[i - 1][0]) % MOD;
		f[i][2] = (f[i][1] + f[i - 1][1]) % MOD;
	}
	while(q --){
		int x = read();
		printf("%lld\n", f[x][0]);
	}
	return 0;
}
posted @ 2021-08-06 21:01  LPF'sBlog  阅读(37)  评论(0编辑  收藏  举报