牛客网提高五练习
吉老师的题,出的很有水平(应该是T1,T2难度)
同余方程
题意
给出 \(l1, l2, r1, r2, m\) 询问 \(\displaystyle \sum_{i = l1}^{r1} \sum_{j = l2}^{r2} [m | i \oplus j]\)
分析
对于前 \(30\) 分,我们可以简单枚举
对于第二个点,我们已知一个数
显然对于 \(10 ^ {18}\) 的数据范围,我们只能考虑 \(O(1)\) 或者 \(O(logn)\) 的算法
由于如果需要枚举 \([l, r]\) 中的数,时间复杂度将会乘上 \(O(n)\),我们需要考虑直接进行计算
因为确定了 \(x\) ,不同 \(y\) 的值与 \(x\) 异或的值一定不一样,并且一定是紧密排布的
因此我们可以直接计算出这些数的开头以及结尾,还有 \(m\) 的倍数的个数
对于所有数据
我们考虑将这个问题转换为前缀问题
记 \(f(x, y)\) 为 \(\displaystyle \sum_{i = 0}^{x} \sum_{j = 0}^{y} [m | i \oplus j]\)
那么我们可以将原问题转换为 \(f(r1, r2) - f(l1, r2) - f(r1, l2) + f(l1, l2)\)
\(f(x, y)\) 可以通过二进制分解后选取一部分做上一种情况来求解
代码
#include <cstdio>
#include <iostream>
using namespace std;
#define qword long long
qword l1, l2, r1, r2, p, ans = 0;
#define M 998244353LL
qword solve(qword l, qword r, qword p) {
if (l % p) l = (l / p + 1) * p;
if (r % p) r = (r / p) * p;
if (l > r) return 0;
return (r / p - l / p + 1) % M;
}
qword solve(qword a, qword b){
qword res = 0;
for (qword i = a; i > 0; i -= i & -i)
for (qword j = b; j > 0; j -= j & -j){
qword m = min(i & -i, j & -j);
qword n = max(i & -i, j & -j);
qword x = ((i - (i & -i)) ^ (j - (j & -j))) | (n - 1);
qword y = x - n + 1;
(res += m % M * solve(y, x, p) % M) %= M;
}
return res;
}
int main() {
cin >> l1 >> r1 >> l2 >> r2 >> p;
if (r1 <= 5000 && r2 <= 5000) {
for (qword i = l1; i <= r1; ++ i)
for (qword j = l2; j <= r2; ++ j) {
qword res = i ^ j;
if (res % p == 0) ans ++;
if (ans > M) ans -= M;
}
}
else ans = solve(++r1, ++r2) - solve(l1, r2) - solve(r1, l2) + solve(l1, l2);
cout << (ans + M) % M << endl;
}
旅行
题意
每条边至少走一次,从 \(1\) 出发回到 \(1\)
解析
应为这是一个闭合回路,如果我们将重复走的边进行拆分,那这张图就是个欧拉回路
因此,我们要保证每个点的度都是偶数
考虑添加哪些边使得每个点的度是偶数
由于每加一条边,只会改变两个点的度,并且这个度是可以传递的 最短路 + dp? 时间暴了
发现由于边权全部是 \(2\) 的幂次,并且幂次高的一定比所有幂次小的的和大,所以我们只要选取了高幂次的,我们就可以通过选取低幂次来降低总的 \(ans\)
由此,我们可以发现我们最终选取的值全部都在这个图的 MST 上
那么原问题将转变成选取那些边,可以使得这棵树上所有的点的权值全是偶数
简单DFS即可
注意取模
代码
#include <cstdio>
#include <iostream>
using namespace std;
#define qword long long
const int maxn = 5e5 + 10;
int n, m, u[maxn], v[maxn], w[maxn], fa[maxn], d[maxn], ver[maxn << 1], head[maxn << 1], Next[maxn << 1];
long long mi[maxn], tot = 0, ans = 0, edge[maxn << 1];
bool vis[maxn];
#define CH ch = getchar()
const int q = 998244353;
template <class T> inline void read(T &x) {
x = 0; int ch, f = 0;
for (CH; ch < '0' || ch > '9'; CH) if (ch == '-') f = 1;
for (; ch >= '0' && ch <= '9'; CH) x = (x << 1) + (x << 3) + (ch ^ 48);
if (f) x = -x;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void addEdge(int x, int y, int z) {
ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
int dfs(int x) {
vis[x] = 1;
for (int i = head[x]; i; i = Next[i]) {
int y = ver[i], z = edge[i];
if (vis[y]) continue;
int isAdd = dfs(y);
if (isAdd) d[y] ++, d[x] ++, ans += z, ans %= q;
}
if (d[x] & 1) return 1;
else return 0;
}
int main() {
read(n), read(m); mi[0] = 1;
for (int i = 1; i <= m; ++ i) {
read(u[i]), read(v[i]); fa[i] = i;
d[u[i]] ++, d[v[i]] ++; mi[i] = (mi[i - 1] << 1) % q;
ans += mi[i]; ans %= q;
}
for (int i = 1; i <= m; ++ i) {
int uu = u[i], vv = v[i]; // ww -> 2 ^ i
int x = find(uu), y = find(vv);
if (x != y) addEdge(uu, vv, mi[i]), addEdge(vv, uu, mi[i]), fa[x] = y;
}
dfs(1);
cout << ans << endl;
}
串串
题意
如题所述
解析
\(T\) 是从 \(S\) 中删除一些字符得到的,\(T\) 一共有 \(\binom{c+d}{c}\) 种
考虑逆过程,我们在 \(T\) 上面插入 \(a-c\) 个 \(0\),\(b-d\) 个 \(1\) 来得到 \(S\)
但是直接插入会计算重复,比如 \(T\) 是 \(000\),插入一个 \(0\) 有 \(4\) 种位置,但是得 到的都是相同的结果
为了避免计算重复,我们限制所有统计的插入方案,必须对应着 \(T\) 在 \(S\) 中最 靠前的一次出现,比如在上面的例子中,我们只统计 \(0\) 插入到最末尾的情况。
显然在这样的要求下, \(0\) 只能插入到 \(1\) 前面或者末尾,\(1\) 只能插入到 \(0\) 前面或者末尾。因此在一共 \(c+d+1\) 个间隔中,除了末尾以外,其他间隔都只能插入一种数字
可以枚举末尾有几个 \(0\) 几个 \(1\),然后用插板法统计方案
代码
#include <cstdio>
#define qword long long
const int p = 1e9 + 7;
const int maxn = 2010;
int ans, C[maxn << 1][maxn], a, b, c, d;
int main() {
scanf("%d %d %d %d",&a, &b, &c, &d);
C[0][0] = 1;
for (int i = 1; i <= a + b; ++ i){
C[i][0] = 1;
for (int j = 1; j <= i; ++ j)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % p;
}
if(!c || !d){
printf("%lld\n", C[a + b][a]);
return 0;
}
for (int i = 0; i <= a - c; ++ i)
for (int j = 0; j <= b - d; ++ j){
qword ret = C[i + j][j];
(ret *= C[c + d][d]) %= p;
int cq0 = a - c - i, cq1 = b - d - j;
(ret *= C[cq1 + c - 1][c - 1]) %= p;
(ret *= C[cq0 + d - 1][d - 1]) %= p;
(ans += ret ) %= p;
}
printf("%d", ans);
}