开坑难填之A层邀请赛1
A. Race
据说很容易想到Trie树?但我当时只想到了暴力……(原因是Trie树还不会qwq)

//我相信我没分~ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 3; const ll mod = 998244353; int m, n, a[maxn]; ll r[maxn], ans; struct node { int id; ll val; bool operator < (const node &T) const { return T.val < val; } }c[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { n = read(); m = read(); for(int i=1; i<=n; i++) a[i] = read(); int Day = (1<<m); for(int j=0; j<Day; j++) { //printf("j = %d\n", j); for(int i=1; i<=n; i++) { c[i].val = (a[i]^j); c[i].id = i; //printf("c[%d].val = %lld\n", i, c[i].val); } sort(c+1, c+1+n); /*for(int i=1; i<=n; i++) { printf("%lld\n", c[i].val); }*/ for(int i=1; i<=n; i++) { int id = c[i].id; r[id] = (r[id]+(ll)(i-1)*(i-1))%mod; } } for(int i=1; i<=n; i++) { ans ^= r[i]; //printf("r[%d] = %lld\n", i, r[i]); //printf("ans = %lld\n", ans); } printf("%lld", ans); return 0; }
正解:先把x^2拆开变成(x1+x2+x3+...)*(x1+x2+x3+...)其中每一个xi都为1,代表当天更大的数,再继续展开就变成了(x1^2+x2^2+...+2*x1*x2+2*x1*x3+...),所以答案就和在它之前的点对的个数有了关系。
对每一个a[i],枚举在a[i]之前的点对,其中一个是从M-1到j-1异或值和a[i]相同,另一个是从M-1到k-1和a[i]相同,它们分别在第j位和第k位比a[i]的对应位大,find是找到前面的对应位相同,ch^1是当前位和a[i]相反,而单独找到相反还不够可能异或更小了,这就给天数设置了限制条件,一定可以找到满足条件的日子因为它所有位都是满的。j==k的情况是1<<(M-1)因为对M的限制只有一位。
这个应该不算是按位计算贡献吧,只是把位拆开来枚举点对而已……不过这么说好像也可以?
找点对的时候避免重复把k循环到j-1而不是把k==j continue掉。点对当然可以合并(在计算贡献时用树上的size),因为点对本来就是拆开的,拆成几个都一样。

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e5 + 5; const int N = 1e7 + 2; const int mod = 1e9 + 7; const int INF = 1061109567; int n, M, tot=1, fa[maxn][31]; ll ans[maxn], sum, a[maxn]; struct node { ll ch[2], size; }t[maxn*20]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void insert(int x) { int p = 1; t[p].size++; fa[x][M] = 1; for(int i=M-1; i>=0; i--) { int me = (a[x]>>i)&1; if(!t[p].ch[me]) t[p].ch[me] = ++tot; p = t[p].ch[me]; t[p].size++; fa[x][i] = p; } } void work(int x) { for(int i=0; i<=M-1; i++) { int me1 = 0, find1 = 0, me2 = 0, find2 = 0; for(int j=0; j<i; j++) { me1 = (a[x]>>i)&1; me2 = (a[x]>>j)&1; me1^=1; me2^=1; find1 = fa[x][i+1]; find2 = fa[x][j+1]; ans[x] = (ans[x]+2*t[t[find1].ch[me1]].size*t[t[find2].ch[me2]].size%mod*(1ll<<(M-2ll))%mod)%mod; } me1 = (a[x]>>i)&1; me1^=1; find1 = fa[x][i+1]; ans[x] = (ans[x]+t[t[find1].ch[me1]].size*t[t[find1].ch[me1]].size%mod*(1ll<<(M-1ll))%mod)%mod; } } int main() { n = read(); M = read(); for(int i=1; i<=n; i++) { a[i] = read(); } for(int i=1; i<=n; i++) { insert(i); } for(int i=1; i<=n; i++) { work(i); } for(int i=1; i<=n; i++) { sum ^= ans[i]; } printf("%lld\n", sum); return 0; }
B. 青蛙
赛时正解好像我也想到了,不会数cnt+1……WA 0……
如果这些石头连一只青蛙不付代价地跳过去都不够,那就让代价最小的青蛙把所有石头跳一遍,其他青蛙直接从头到尾;如果这些石头可以让至少一只青蛙不付代价地跳过去,那就尽可能让代价大的去跳石头,每次卡着上限去跳给其他青蛙也留足机会,不用考虑石头有剩余,直接分配给能跳过去的某只青蛙就好了,距离只会减得更小,一次跳不过去的青蛙直接从头到尾。
那种卡上限跳的思路可以用set实现,虽然有log不过据说可以过,但当时这么想纯属瞎蒙,并不会证明,于是我去借鉴了另一种跳法——%%%Chen_jr%%%妙不可言
讲题那天好像就是这么讲的,蒟蒻直到看见代码才知道说的是什么意思……

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 5e5 + 5; const int N = 1e7 + 2; const int mod = 1e9 + 7; const int INF = 1061109567; int T, n, m, k, d, c[maxn], a[maxn]; bool f[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { T = read(); while(T--) { n = read(); m = read(); k = read(); d = read(); for(int i=1; i<=m; i++) { c[i] = read(); } for(int i=1; i<=k; i++) { a[i] = read(); } sort(a+1, a+k+1); sort(c+1, c+m+1); int p = 0, cnt = 0; for(int i=0; i<=k; i++) f[i] = 0;//memset for(; p<=k; p++) { if(a[p]-1 <= d) f[p] = 1;//表示有青蛙跳到了第p个石头上 else break; } for(int i=1; i<=k; i++) { //为什么不判断f[p]不能是1,因为p跳完就往后增加,当然不可能是1 if(f[i] && a[p]-a[i]<=d && p <= k)//从有青蛙的石头开始向后转移,最靠左的青蛙尝试最近的空石头 { f[p] = 1; f[i] = 0; p++;//青蛙跳走了记得清空 } } for(int i=k; i>=1; i--)//然后还要能到终点才行 { if(n-a[i] <= d && f[i]) cnt++; else break; } ll ans = 0; if(cnt == 0) { a[0] = 1; a[k+1] = n; for(int i=1; i<=k+1; i++) { if(a[i]-a[i-1] > d) ans += c[1]; } ans -= c[1];//因为后面没有else,c[1]又加了一遍 } for(int i=1; i<=m-cnt; i++) { ans += c[i]; } printf("%lld\n", ans); } return 0; }
C. 序列
首先,暴力可以水个25分。然后,吉司机线段树是什么鬼啊,容我先去学习一下……
学习了一波,原来就是一种可以维护区间次小值的线段树,还可以记录区间最小值被修改的次数,单点查询的话直接就是答案了。算是收藏一种板子,目前还不知道怎么应用……

#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 5; const int N = 1e7 + 2; const int mod = 1e9 + 7; const ll INF = 0x3f3f3f3f3f3f3f3f; int n, m, val[maxn]; char c[3]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { ll val; int op; }ans; struct tree { struct node { ll dt, mi, cmi; int ctmi, ctdt; }t[maxn<<2]; void pushup(int x) { int ls = x<<1, rs = x<<1|1; if(t[ls].mi < t[rs].mi) { t[x].mi = t[ls].mi; t[x].cmi = t[ls].cmi; t[x].cmi = min(t[x].cmi, t[rs].mi==t[x].mi?INF:t[rs].mi); t[x].cmi = min(t[x].cmi, t[rs].cmi==t[x].mi?INF:t[rs].cmi); } else { t[x].mi = t[rs].mi; t[x].cmi = t[rs].cmi; t[x].cmi = min(t[x].cmi, t[ls].mi==t[x].mi?INF:t[ls].mi); t[x].cmi = min(t[x].cmi, t[ls].cmi==t[x].mi?INF:t[ls].cmi); } } void build(int x, int l, int r) { if(l == r) { t[x].mi = val[l]; t[x].cmi = INF; return; } int mid = (l + r) >> 1; build(x<<1, l, mid); build(x<<1|1, mid+1, r); pushup(x); } void pushdown(int x) { int ls = x<<1, rs = x<<1|1; if(t[x].ctdt) { t[ls].ctdt += t[x].ctdt; t[rs].ctdt += t[x].ctdt; t[ls].dt += t[x].dt; t[rs].dt += t[x].dt; //这个可以直接加是因为dt不是区间和,而且最后只有单点查询 t[ls].mi += t[x].dt; t[rs].mi += t[x].dt; t[ls].cmi += t[x].dt; t[rs].cmi += t[x].dt; t[x].dt = 0; t[x].ctdt = 0; } if(t[x].ctmi) { if(t[ls].mi < t[x].mi) { t[ls].ctmi += t[x].ctmi; t[ls].mi = t[x].mi; } if(t[rs].mi < t[x].mi) { t[rs].ctmi += t[x].ctmi; t[rs].mi = t[x].mi; } t[x].ctmi = 0; } } void modify_add(int x, int l, int r, int L, int R, int dt) { if(L <= l && r <= R) { t[x].mi += dt; t[x].cmi += dt; t[x].dt += dt; t[x].ctdt++; return; } pushdown(x); int mid = (l + r) >> 1; if(L <= mid) modify_add(x<<1, l, mid, L, R, dt); if(R > mid) modify_add(x<<1|1, mid+1, r, L, R, dt); pushup(x); } void query(int x, int l, int r, int pos) { if(l == r) { ans.val = t[x].mi; ans.op = t[x].ctmi + t[x].ctdt; return; } pushdown(x); int mid = (l + r) >> 1; if(pos <= mid) query(x<<1, l, mid, pos); else query(x<<1|1, mid+1, r, pos); } void modify_min(int x, int l, int r, int L, int R, int dt) { if(L <= l && r <= R) { if(t[x].mi >= dt) return; if(t[x].cmi > dt) { t[x].mi = dt; t[x].ctmi++; return; } //如果取max的数小于最小值就不用管,大于次小值继续递归,在两个值之间 //只修改最小值 //次小值是严格次小,当然一定能继续递归啊,如果次小值存在的话 } pushdown(x); int mid = (l + r) >> 1; if(L <= mid) modify_min(x<<1, l, mid, L, R, dt); if(R > mid) modify_min(x<<1|1, mid+1, r, L, R, dt); pushup(x); } }T; int main() { n = read(); for(int i=1; i<=n; i++) { val[i] = read(); } T.build(1, 1, n); m = read(); for(int i=1; i<=m; i++) { scanf("%s", c); if(c[0] == 'Q') { int pos = read(); T.query(1, 1, n, pos); printf("%lld %d\n", ans.val, ans.op); } else { int l = read(), r = read(), dt = read(); if(c[0] == 'A' && dt) T.modify_add(1, 1, n, l, r, dt); if(c[0] == 'M') T.modify_min(1, 1, n, l, r, dt); } } return 0; }
Cat就是这样靠着Chen_jr大佬的题解苟且偷生的……
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现