成绩表
![[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)