众人:这次题目好难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(l1→r1,l2→r2)=ans(0→r1,0→r2)−ans(0→l1−1,0→r2)−ans(0→r1,0→l2−1)+ans(0→l1−1,0→l2−1)
考虑
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(0→a−1,0→b−1)
分3个阶段
- a = 2 n , b = 2 n a=2^n,b=2^n a=2n,b=2n
- a = 2 n , b = 2 m ( n ≥ m ) a=2^n,b=2^m(n\geq m) a=2n,b=2m(n≥m)
- 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
p⊕y=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)) p⊕y=x(y∈[0,2n)),所以答案使一样的 2 m × n u m x 2^m\times num_x 2m×numx种
对于3,我们需要把一个数拆分成好几个小于它的数进行计算
一个二进制数小于另一个二进制数的条件是:前几位相等,中间有一位0
比1
小,后面几位随意放0
或1
例如计算
[
0
,
43
)
(
43
)
10
=
(
101011
)
2
[0,43)(43)_{10}=(101011)_2
[0,43)(43)10=(101011)2
分成以下四部分(?
表示随意取0
或1
)
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)0101
和000(0)101
都是加入了一个0
,而且是等价的,那么我们只在000(0)101
的地方记一次。于是就能发现:每个0
都要放在1
前面,每个1
都要放在0
前面,或者放在串尾。
问题简化了,假设由
i
i
i个0
,
j
j
j个1
放在串尾。我们考虑
a
−
c
−
i
(
0
≤
i
≤
a
−
c
)
a-c-i(0\leq i\leq a-c)
a−c−i(0≤i≤a−c)个0
要放在
d
d
d个1
的前面可以放方案数为
(
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}
(a−c−i+d−1a−c−i)=(a−c−i+d−1d−1)
转换成这种形式后可以看到,当
d
=
0
d=0
d=0时需要特判
同理,放
j
j
j个1
的方案数有
(
b
−
d
−
j
+
c
−
1
c
−
1
)
\begin{pmatrix}b-d-j+c-1\\c-1\end{pmatrix}
(b−d−j+c−1c−1)种
最终
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=0∑a−cj=0∑b−d(a−c−i+d−1d−1)(b−d−j+c−1c−1)(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~