3 2025 04 23 模拟赛总结
成绩表
![[12 题解/photo/Pasted image 20250423183537.png]]
做题情况
- T1:看了十分钟没什么思路,后来打表找到了一点规律,但是没写对(可能是因为细节太多)0pts
- T2:这个题看起来唬人,实际不难,想了大概20分钟,后来写出来了,100pts
- T3:这题是个三叉线段树,赛时没有想出来,直接打的暴力,25pts
- T4:赛时没有时间写,0pts
赛时心态
心态比较平稳,T1用时过长,有些慌了神,T3的解法没有遇到过,估计再来点时间我还是会放弃。T4现在看来并不算难题,赛时应该多花点时间去思考,有可能做出来。
题面及题解
T1
题目描述
定义 \(f(x)=x\oplus (x+2^k)\),其中 \(\oplus\) 是二进制下的异或运算。
给定两个整数 \(n,k\)。
请你输出 \(f(0)+f(1)+f(2)+\cdots+f(n)\) 的值。
【数据范围】
对于 \(100\%\) 数据,\(1\le T \leq 10^5\),\(0\le n < 2^{29}\),\(0\le k \leq 29\)。
题解
这题的难点在于如何快速求出\(\displaystyle f(x)=x\,\oplus\,(x+2^{k})\)
我们考虑对于每个\(\displaystyle x + 2^{k}\)
-
当\(\displaystyle x\)的第\(\displaystyle k\)位为0
\(x+2^{k}\)相当于把\(x\)的第\(k\)位变成1,此时\(f(x)=2^{k}\) -
当\(x\)的第\(k\)位为1
\(x+2^{k}\)会导致从第\(k\)位向左发生进位,此时的\(f(x)=2^{l}\),\(l\)为从第\(k\)位开始向左数连续1的个数,就像这样
![[12 题解/photo/Pasted image 20250423192448.png]]
所以我们可以去枚举连续1的长度,然后用 可能的情况数\(*\)这种情况的贡献即可求得答案
下面详细说说如何求得每种情况的情况数
设我们固定的1的长度为\(t\),注意:向左一位应该是0,否则不符合条件
首先,对于每个情况(也就是从第\(k\)位开始固定的1的长度),我们需要求出这个条件下\(\leq n\)的最大值\(p\),我们可以先将\(k\sim k+t\)位的一个0和\(t\)个1固定在相应的位置,然后枚举每一位,如果可以放入(不在\(k\sim k+t\)范围内且\(+p\)后的值\(\leq n\)),更新\(p\),这样就能够求出满足条件的最大的\(p\)
我们设p二进制中\(一个0+t个1\)的部分为\(x\),位于其前的部分为\(a\),其后的部分为\(b\),设当前有贡献的数为\(a'+x'+b'\)
而后我们分两种情况统计符合条件的情况
-
\(a'<a\)时,\(a'\)部分的贡献为\(a+1-1\),其中要考虑0和\(=a\)的情况
\(b'\)则可以取任意数,共有\(k-1+1\)位,其中\(+1\)是因为有第0位,共有\(2^{k}\)种情况
根据乘法原理,这种情况的贡献为\(a\times 2^{k}\) -
\(a'=a\)时,\(a'\)部分的贡献为\(1\),\(b'\)部分只能取\(\leq b\)的数,因此\(b'\)部分的贡献为\(b+1\)
两种情况综合起来,再统计到答案即可。
code
using namespace std;
typedef long long ll;
int t;
int n, k;
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &k);
ll sum = 0;
for(int i = 0; i <= 30; i++) {
ll p = (1 << i) - 1;
p = p << k;
if(p > n) continue;
for(int j = 30; j >= 0; j--) {
if(j >= k && j <= k + i) continue;
if(p + (1 << j) <= n) {
p += (1 << j);
}
}
int cnt1 = (p >> (k + i + 1)) << k;
int cnt2 = p % (1 << k) + 1;
sum += (ll)(cnt1 + cnt2) * (((1 << (i + 1)) - 1) << k);
}
printf("%lld\n", sum);
}
return 0;
}
T2
题目描述
在一个无限大的棋盘上有 \(n\) 个位置互不相同的棋子 \((x_i,y_i)\),你需要通过进行若干次以下操作删除全部的棋子:
-
选择一个格子 \((x,y)\)。
-
若 \((x,y)\) 上有棋子,则把这个棋子删掉,否则结束当前操作。
-
依次检查坐标为 \((x+1,y+1)\),\((x+1,y)\),\((x+1,y-1)\) 的格子上是否有棋子。当检查到第一个有棋子的格子时,停止检查,并将当前的 \((x,y)\) 更新为该格子的坐标后返回第二步。如果这三个格子都没有棋子,结束当前操作。
你要回答,最少操作多少次能把所有棋子删光。
【数据范围】
对于所有的测试数据,满足 \(1\le n\le 10^6\),\(1\le x_i,y_i\le 10^6\)。
题解
这题较简单,我们画个图即可大概想到怎么做,所以画个图
![[12 题解/photo/Pasted image 20250423201110.png|600]]
对于每个节点,我们都可以走向下方的三个点,所以我们可以从上到下,从左到右枚举每个点,如果有可以到达的点,则将ans--(ans开始=n)
说一下赛后我有些疑惑的一个地方,如下图
![[12 题解/photo/Pasted image 20250423202503.png|500]]
事实上,我们选蓝色路线和红色路线是等价的,答案都是2,所以我们选择哪个点向下走与这个点能够向下走的深度无关。
code
using namespace std;
#define pii pair<int, int>
const int N = 1e6 + 10;
int n;
map <pii, int> m;
struct node {
int a, b;
int fa, fb;
}c[N];
bool cmp(node x, node y) {
if(x.a == y.a) {
return x.b < y.b;
}
return x.a < y.a;
}
int main() {
scanf("%d", &n);
int ans = n;
for(int i = 1; i <= n; i++) {
int x, y;
scanf("%d%d", &x, &y);
c[i].a = c[i].fa = x;
c[i].b = c[i].fb = y;
}
sort(c + 1, c + 1 + n, cmp);
for(int i = 1; i <= n; i++) {
m[{c[i].a, c[i].b}] = i;
}
for(int i = 1; i <= n; i++) {
for(int tt = -1; tt <= 1; tt++) {
if(m.count({c[i].a + 1, c[i].b + tt})) {
int id = m[{c[i].a + 1, c[i].b + tt}];
if(c[id].fa == c[id].a && c[id].fb == c[id].b) {
c[id].fa = c[i].a;
c[id].fb = c[i].b;
// printf("fa: %d fb: %d sa: %d sb: %d\n",
// c[i].a, c[i].b, c[id].a, c[id].b);
ans--;
break;
}
}
}
}
printf("%d\n", ans);
return 0;
}
T3
题目描述
给定一个长度为\(3^{n}\)的由A和B组成的字符串\(s\)
\(q\)次询问,每次询问给定一个\(pos\),如果\(s[pos]==A\)则将\(s[pos]\)改为\(B\),反之亦然
每次询问,我们需要执行以下操作,直至字符串的长度为1
- 将\(s[i]\)变为\(s[3i],s[3i-1],s[3i-2]\)中出现次数较多的字符,字符串的长度变为原来的三分之一
输出最后的字符
这个题乍一看没有什么思路,实际上并不难。由题可知,每个变化后的字符都是由三个连续的字符变化而来,并且还有修改操作,所以我们可以用三叉线段树来维护,然后这题就能被我们轻而易举的解决了。
code
const int N = 6e5 + 10;
int n, m, cnt = 0;
char c[N];
struct node {
int dat, s[3];
}t[N << 2];
void update(int p) {
int ca = 0, cb = 0;
for(int i = 0; i < 3; i++) {
if(t[t[p].s[i]].dat) ca++;
else cb++;
}
t[p].dat = ca > cb ? 1 : 0;
return;
}
void build(int &p, int l, int r) {
p = ++cnt;
if(l == r) {
t[p].dat = c[l] == 'A' ? 1 : 0;
return;
}
int mid1 = l + (r - l + 1) / 3 - 1;
int mid2 = r - (r - l + 1) / 3;
build(t[p].s[0], l, mid1);
build(t[p].s[1], mid1 + 1, mid2);
build(t[p].s[2], mid2 + 1, r);
update(p);
}
void modify(int p, int l, int r, int pos) {
if(l == r) {
t[p].dat = t[p].dat == 1 ? 0 : 1;
return;
}
int mid1 = l + (r - l + 1) / 3 - 1;
int mid2 = r - (r - l + 1) / 3;
if(pos <= mid1) {
modify(t[p].s[0], l, mid1, pos);
} else if(pos <= mid2) {
modify(t[p].s[1], mid1 + 1, mid2, pos);
} else modify(t[p].s[2], mid2 + 1, r, pos);
update(p);
}
int main() {
scanf("%d%d", &n, &m);
scanf("%s", c + 1);
int root = 0;
int tt = pow(3, n);
build(root, 1, tt);
for(int i = 1; i <= m; i++) {
int pos;
scanf("%d", &pos);
modify(root, 1, tt, pos);
printf("%c\n", t[root].dat == 1 ? 'A' : 'B');
}
return 0;
}
终于到最后一题了,好累…… (T _ T)
T4
题目描述
给定一个 \(n\) 点 \(m\) 边的无向图,第 \(i\) 条道路连接了 \(u_i\) 与 \(v_i\),边权为 \(w_i\),第 \(i\) 个点的点权为 \(c_i\)。
给定 \(q\) 组询问,第 \(i\) 组询问求从 \(s_i\) 到 \(t_i\) 的路径的边权之和与点权的最大值的和的最小值。
可能有重边,但保证无自环。
对于 \(100\%\) 的数据,\(1 \le n \le 250\),\(1 \le m \le 10^4\),\(1 \le q \le 10^4\)。
题解
这题比赛的时候没有时间正经想,再加上一大坨英文糊脸,直接放弃了。
做了以后发现并不是很难。
边权和的最小值很容易想到可以用Floyd,但是点权的最大值并不好确定并转移
为了解决这个问题,我们可以先将点按照点权从小到大排序,而后按照此顺序枚举中间点,这样点权的最大值即为\(max(max(c[i].val,c[j].val),c[k].val)\)。
这样即可\(O(n^{3})\)预处理答案,通过此题
code
const int N = 255;
int n, m, q;
int d[N][N];
int ans[N][N];
struct node {
int val;
int id;
}c[N];
bool cmp(node a, node b) {
return a.val < b.val;
}
int main() {
memset(d, 0x3f, sizeof d);
memset(ans, 0x3f, sizeof ans);
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i++) {
scanf("%d", &c[i].val);
c[i].id = i;
}
for(int i = 1; i <= m; i++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
d[x][y] = min(d[x][y], z);
d[y][x] = min(d[y][x], z);
}
sort(c + 1, c + 1 + n, cmp);
for(int k = 1; k <= n; k++) {
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
d[c[i].id][c[j].id] = min(d[c[i].id][c[j].id],
d[c[i].id][c[k].id] + d[c[k].id][c[j].id]);
ans[c[i].id][c[j].id] = min(ans[c[i].id][c[j].id],
d[c[i].id][c[j].id] + max(max(c[i].val, c[j].val),c[k].val));
}
}
}
while(q--) {
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", ans[x][y]);
}
return 0;
}
完结撒花(w)