吉老师的题,出的很有水平(应该是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) 一共有 (inom{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
", 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);
}