牛客网NOIP赛前集训营-提高组(第五场)Solution

众人:这次题目好难T^T
Jiry:这次题目简单就没做ppt了
众人:初赛切线段期望为什么是1/3?
Jiry:你积分积一下就好,这不是高考范围的吗
众人:吉老师您出复赛吗?
Jiry:你认为我会出我就不会出,你认为我不会出我就会出
(疯狂暗示)


A-同余方程
我认为T1是最难的(雾
(细节多得要死。。。)
显然 a n s ( l 1 → r 1 , l 2 → r 2 ) = a n s ( 0 → r 1 , 0 → r 2 ) − a n s ( 0 → l 1 − 1 , 0 → r 2 ) − a n s ( 0 → r 1 , 0 → l 2 − 1 ) + a n s ( 0 → l 1 − 1 , 0 → l 2 − 1 ) ans(l1\to r1,l2\to r2)=ans(0\to r1,0\to r2)-ans(0\to l1-1,0\to r2)-ans(0\to r1,0\to l2-1)+ans(0\to l1-1,0\to l2- 1) ans(l1r1,l2r2)=ans(0r1,0r2)ans(0l11,0r2)ans(0r1,0l21)+ans(0l11,0l21)
考虑 s o l v e ( a , b ) = a n s ( 0 → a − 1 , 0 → b − 1 ) solve(a,b)=ans(0\to a-1,0\to b-1) solve(a,b)=ans(0a1,0b1)
分3个阶段

  1. a = 2 n , b = 2 n a=2^n,b=2^n a=2n,b=2n
  2. a = 2 n , b = 2 m ( n ≥ m ) a=2^n,b=2^m(n\geq m) a=2n,b=2m(nm)
  3. a , b a,b a,b无限制

(突然发现貌似有重复,那就把读入的 m m m记作 M M M吧)
对于1,在区间 [ 0 , 2 n ) [0,2^n) [0,2n)中找到所有的 M M M的倍数 x x x,对于每个 x x x,每个 p ∈ [ 0 , 2 n ) p\in [0,2^n) p[0,2n),都有一个对应的 y y y,使 p ⊕ y = x p\oplus y=x py=x,所以满足条件的解共有 2 n × n u m x 2^n\times num_x 2n×numx

对于2,在区间 [ 0 , 2 n ) [0,2^n) [0,2n)中找到所有的 M M M的倍数 x x x,对于每个 x x x,同样对于任何 p ∈ [ 0 , 2 m ) p\in [0,2^m) p[0,2m),都有 p ⊕ y = x ( y ∈ [ 0 , 2 n ) ) p\oplus y=x(y\in [0,2^n)) py=x(y[0,2n)),所以答案使一样的 2 m × n u m x 2^m\times num_x 2m×numx

对于3,我们需要把一个数拆分成好几个小于它的数进行计算
一个二进制数小于另一个二进制数的条件是:前几位相等,中间有一位01小,后面几位随意放01
例如计算 [ 0 , 43 ) ( 43 ) 10 = ( 101011 ) 2 [0,43)(43)_{10}=(101011)_2 [0,43)(43)10=(101011)2
分成以下四部分(?表示随意取01)
0?????
100???
10100?
101010
对于任意两个前缀确定的二进制数,可以用类似于2的方法算出方案数
例如
100???
1010??
异或后
001???
所以最终方案数是[001000,001111]中 M M M的倍数个数 × 2 2 \times 2^2 ×22
类似的模拟即可
时间复杂度 θ ( l o g 2 n ) \theta(log^2n) θ(log2n)
一定一定要小心边界

#include <cstdio>
#include <string.h>
#include <algorithm>
#define MOD 998244353
#define L 64

using namespace std;
typedef long long LL;
LL m, bit[L][L], bin1[L], bin2[L], cnt1, cnt2;

inline LL count(LL num) {
	if (num < 0) return 0ll;
	return num / m + 1;
}

inline LL calc(LL x, LL y) {
	LL p = max(x, y), q = min(x, y);
	LL bg;
	if (x == y) bg = bit[x][y];
	if (x < y) bg = bit[min(y - 1, cnt1 - 1)][y];
	if (x > y) bg = bit[x][min(x - 1, cnt2 - 1)];
	return (count(bg + (1ll << p) - 1) - count(bg - 1)) % MOD * ((1ll << q) % MOD) % MOD;
}

inline LL solve(LL a, LL b) {
	memset(bit, 0, sizeof(bit));
	memset(bin1, 0, sizeof(bin1));
	memset(bin2, 0, sizeof(bin2));
	if (a > b) swap(a, b);
	cnt1 = 0;
	cnt2 = 0;
	while (a > 0) {
		bin1[cnt1++] = a & 1;
		a >>= 1;
	}
	while (b > 0) {
		bin2[cnt2++] = b & 1;
		b >>= 1;
	}
	LL sum = 0;
	for (LL i = cnt1 - 1; i >= 0; --i) {
		bit[i][cnt2 - 1] = bit[i + 1][cnt2 - 1] ^ (bin1[i + 1] << (i + 1));
		for (LL j = cnt2 - 2; j >= 0; --j) {
			bit[i][j] = bit[i][j + 1] ^ (bin2[j + 1] << (j + 1));
		}
	}
	for (LL i = cnt1 - 1; i >= 0; --i) {
		if (bin1[i]) {
			for (LL j = cnt2 - 1; j >= 0; --j) {
				if (bin2[j]) {
					sum += calc(i, j);
					if (sum > MOD) sum -= MOD;
				}
			}
		}
	}
	return sum;
}

int main() {
	LL l1, r1, l2, r2;
	scanf("%lld%lld%lld%lld%lld", &l1, &r1, &l2, &r2, &m);
	LL ans = (solve(r1 + 1, r2 + 1) - solve(l1, r2 + 1) - solve(r1 + 1, l2) + solve(l1, l2)) % MOD;
	if (ans < 0) ans += MOD;
	printf("%lld\n", ans);
	return 0;	
}

B-旅游
题意就是给你一张边权为 2 i 2^i 2i的无向图,问走完每条边最后回到起点的最小距离是多少
其实我们的目标是把这个普通的无向图变成所有点度数都是偶数的欧拉图,并且可以发现每条边至少走1次,至多走2次。
所以我们可以建一棵自小生成树,而且可以断言不在这棵生成树上的边最多只经过一次
为什么呢?可以发现两点之间的最短路径一定是生成树上的路径( 2 i 2^i 2i有一些神奇的性质,如果树上的路径比较长,那么树边上一定有一条边比非树边来得长,所以生成树就不是最小的了),所以不在树上的边一定只走一次。

好了那么既然已经有了生成树,我们就可以从叶子节点往上一条一条地加边,方案是唯一的。

Code

#include <cstdio>
#include <algorithm>
#define MOD 998244353
#define N 500010

using namespace std;
typedef long long LL;
struct Edge{
	int to, nxt, len;
}e[N << 1];
LL ans;
int fa[N], lst[N], du[N], f[N], cnt;

inline int gfa(int x) {
	if (fa[x] == x) return x;
	fa[x] = gfa(fa[x]);
	return fa[x];
}

inline void add(int x, int y, int z) {//这里的z存的是边的序号
	e[++cnt].to = y;
	e[cnt].nxt = lst[x];
	e[cnt].len = z;
	lst[x] = cnt;
}

void dfs(int x, int fa, int w) {
	for (int i = lst[x]; i; i = e[i].nxt) {
		if (e[i].to == fa) continue;
		dfs(e[i].to, x, e[i].len);
	}
	if (du[x]) {
		du[fa] ^= 1;
		ans += f[w];
		if (ans > MOD) ans -= MOD;
	}
	return;
}

int main() {
	int n, m, u, v;
	scanf("%d%d", &n, &m);
	f[0] = 1;
	for (int i = 1; i <= m; ++i) {
		f[i] = (f[i - 1] << 1) % MOD;
	}
	for (int i = 1; i <= n; ++i) {
		fa[i] = i;
	}
	ans = ((f[m] << 1) - 2) % MOD;
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d", &u, &v);
		du[u] ^= 1;
		du[v] ^= 1;
		int f1 = gfa(u), f2 = gfa(v);
		if (f1 != f2) {
			if (f1 > f2) swap(f1, f2);
			fa[f2] = f1;
			add(u, v, i);
			add(v, u, i);
		}
	}
	dfs(1, 1, 0);
	printf("%d\n", ans);
	return 0;
}

C-串串
首先t可以由s删去一些字符得到,说明t是s的子序列,也就是说s可以有t添加字符得到
但是我们随意添加字符的话,一定会有重复的情况,那要怎么避免重复呢?吉老师告诉我们,每个串只在字典序最小的地方计算一次(这里的字典序是指放入0,1的顺序)
因为00(0)0101000(0)101都是加入了一个0,而且是等价的,那么我们只在000(0)101的地方记一次。于是就能发现:每个0都要放在1前面,每个1都要放在0前面,或者放在串尾
问题简化了,假设由 i i i0 j j j1放在串尾。我们考虑 a − c − i ( 0 ≤ i ≤ a − c ) a-c-i(0\leq i\leq a-c) aci(0iac)0要放在 d d d1的前面可以放方案数为 ( a − c − i + d − 1 a − c − i ) = ( a − c − i + d − 1 d − 1 ) \begin{pmatrix}a-c-i+d-1\\a-c-i\end{pmatrix}=\begin{pmatrix}a-c-i+d-1\\d-1\end{pmatrix} (aci+d1aci)=(aci+d1d1)
转换成这种形式后可以看到,当 d = 0 d=0 d=0时需要特判
同理,放 j j j1的方案数有 ( b − d − j + c − 1 c − 1 ) \begin{pmatrix}b-d-j+c-1\\c-1\end{pmatrix} (bdj+c1c1)
最终
a n s = ( c + d c ) ∑ i = 0 a − c ∑ j = 0 b − d ( a − c − i + d − 1 d − 1 ) ( b − d − j + c − 1 c − 1 ) ( i + j i ) ans=\begin{pmatrix}c+d\\c\end{pmatrix}\sum_{i=0}^{a-c}\sum_{j=0}^{b-d}\begin{pmatrix}a-c-i+d-1\\d-1\end{pmatrix}\begin{pmatrix}b-d-j+c-1\\c-1\end{pmatrix}\begin{pmatrix}i+j\\i\end{pmatrix} ans=(c+dc)i=0acj=0bd(aci+d1d1)(bdj+c1c1)(i+ji)
( i + j i ) \begin{pmatrix}i+j\\i\end{pmatrix} (i+ji)表示放在串尾的方案数, ( c + d c ) \begin{pmatrix}c+d\\c\end{pmatrix} (c+dc)表示串t的方案数
c == 0 || d == 0时,t一定是s的字串

#include <cstdio>
#include <algorithm>
#define MOD 1000000007
#define N 4010

using namespace std;
typedef long long LL;
LL fact[N], ie[N];

inline LL qui_pow(LL x, int y) {
	if (y == 1) return x % MOD;
	LL t = qui_pow(x, y >> 1);
	if (y & 1) return t * t % MOD * x % MOD;
	else return t * t % MOD;
}

inline LL comb(LL n, LL m) {
	if (n < m) return 0;
	return fact[n] * ie[m] % MOD * ie[n - m] % MOD;
}

int main() {
	LL a, b, c, d;
	scanf("%lld%lld%lld%lld", &a, &b, &c, &d);
	LL M = max(max(a, b), max(c, d)) << 1;
	fact[0] = fact[1] = 1;
	ie[0] = ie[1] = 1;
	for (LL i = 2; i <= M; ++i) {
		fact[i] = fact[i - 1] * i % MOD;
		ie[i] = qui_pow(fact[i], MOD - 2);
	}
	if (c == 0 || d == 0) {
		LL ans = comb(a + b, a);
		printf("%lld\n", ans);
		return 0;
	}
	LL ans = 0;
	for (int i = 0; i <= a - c; ++i) {
		for (int j = 0; j <= b - d; ++j) {
			ans += comb(a - c - i + d - 1, d - 1) * comb(b - d - j + c - 1, c - 1) % MOD * comb(i + j, i) % MOD;
			if (ans > MOD) ans -= MOD;
		}
	}
	ans = ans * comb(c + d, c) % MOD;
	printf("%lld\n", ans);
	return 0;
}

最后再来%一发JSJ涨rp~

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值