2026.7.7测试总结
省流:100 + 100 + 40 + 60
T1 最终测试
题意就是给定 \(n\) 个人,每个人一共做两道题,有一半概率拿到 \(a_{i, 1/2}\) 分,有一半概率得 \(0\) 分(怎么这么像我QwQ),求期望排名。
显然每个人的排名期望如下:
然后显然期望转计数,统计 \(s_j > s_i\) 就很简单了,直接将所有人可能的所有分数进行排序,找有几个在他前面就可以了。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e5 + 5;
pair<int, int> a[M];
double res[M];
int read(){
int sgn = 1, ans = 0;
char c = getchar();
while(!isdigit(c)){
if(c == '-') sgn = -sgn;
c = getchar();
}
while(isdigit(c)) ans = ans * 10 + c - '0', c = getchar();
return sgn * ans;
}
signed main(){
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
int n = read();
vector<int>vec;
for(int i = 1; i <= n; i++) {
int x = read(), y = read();
if(x < y) swap(x, y);
a[i] = make_pair(x, y);
vec.push_back(x + y);
vec.push_back(x);
vec.push_back(y);
vec.push_back(0);
}
sort(vec.begin(), vec.end());
for(int i = 1; i <= n; i++) {
int rnk = 0, x = a[i].first, y = a[i].second;
rnk += 4 * n - (upper_bound(vec.begin(), vec.end(), x + y) - vec.begin());
rnk += 4 * n - (upper_bound(vec.begin(), vec.end(), x) - vec.begin());
rnk += 4 * n - (upper_bound(vec.begin(), vec.end(), y) - vec.begin());
rnk += 4 * n - (upper_bound(vec.begin(), vec.end(), 0) - vec.begin());
rnk -= (x + y) > x;
rnk -= (x + y) > y;
rnk -= (x + y) > 0;
rnk -= x > y;
rnk -= x > 0;
rnk -= y > 0;
printf("%.10f\n", 1.0 + rnk * 1.0 / 16.0);
}
return 0;
}
T2 空间跳跃
神中神。
题意如下:
- 如果当前在 \(n\),且 \(\min(|n|, |n-d|) \le l\),则可以跳到 \(n - d\),其中 \(d\) 和 \(l\) 都是给定的正整数。
- 如果当前在 \(n\),则可以跳到 \(2n\)。
- 如果当前在 \(n\) 且 \(n \equiv 1 \pmod 3\) 则可以跳到 \(\frac{n-1}{3}\)。
问能否在 \(1500\) 次内从 \(1\) 跳到 \(k\),其中 \(|k| \le 10^7\)。
首先正着搜索显然是不行的,考虑我们怎么从 \(k\) 变到 \(1\)。
我们看一下这些操作反过来是什么:
- 如果当前在 \(n\),且 \(\min(|n|, |n+d|) \le l\),则可以跳到 \(n + d\),其中 \(d\) 和 \(l\) 都是给定的正整数。
- 如果当前在 \(n\),且 \(n \equiv 0 \pmod 2\),则可以跳到 \(n\)。
- 如果当前在 \(n\) 则可以跳到 \(3 \times n + 1\)。
这不就是经典 \(3n+1\) 猜想吗?所以在 $x \ge 0 $ 的时候显然直接做即可,打个表发现最大操作数大约 \(600\) 多左右。 \(x < 0\) 同理,也是一直跳,然后跳到 \(10^3\) 范围内用 \(1\) 操作变成正数就可以了,打个表发现最大操作数大约 \(800\) 多左右。
注意过程中的数最大可以到大约 \(10^{14}\) 这个量级,不开**见祖宗。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int q, d, l, pos;
int read(){
int sgn = 1, ans = 0;
char c = getchar();
while(!isdigit(c)){
if(c == '-') sgn = -sgn;
c = getchar();
}
while(isdigit(c)) ans = ans * 10 + c - '0', c = getchar();
return sgn * ans;
}
signed main(){
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
q = read(), d = read(), l = read();
while(q--) {
pos = read();
vector<int>ans;
int x = pos;
while(x != 1) {
if(x < 0) {
if(x + d > 0 && min(abs(x), abs(x + d)) <= l) x += d;
else if(x < -1000) {
if(x % 2 == 0) x /= 2;
else x = x * 3 + 1;
} else x += d;
} else if(x == 0) {
x = 1;
}
else if(x % 2 == 0) x /= 2;
else x = x * 3 + 1;
ans.push_back(x);
}
cout << ans.size() << ' ';
reverse(ans.begin(), ans.end());
for(auto v : ans) cout << v << ' ';
cout << pos << '\n';
}
return 0;
}
T3 快速访问
题目很简洁,对于 \(i \in [1,n]\) 求
我们记 \(d_u = \text{depth}(u)\)(根 \(0\) 的深度为 \(0\))。对任意两点 \(u, v\),设 \(l = \text{lca}(u,v)\),有
平方展开:
对于固定的 \(u\),我们要对 \(v\in S\) 求和。记:
- \(A = |S|\)
- \(B = \sum_{v\in S} d_v\)
- \(C = \sum_{v\in S} d_v^2\)
- \(D(u) = \sum_{v\in S} d_{\text{lca}(u,v)}\)
- \(E(u) = \sum_{v\in S} d_{\text{lca}(u,v)}^2\)
- \(F(u) = \sum_{v\in S} d_v \cdot d_{\text{lca}(u,v)}\)
显然前三项是固定值,在第一次 dfs 时就可以求出。接下来考虑剩下三项。
对于形如 \(\sum_{v\in S} g(\text{lca}(u,v))\) 的项,可以用差分转化到路径上:
对每个 \(v\),在它到根的路径上(不含根),对每个节点 \(x\) 加上 \(\Delta_g(x) = g(x)-g(\text{parent}(x))\)。
那么对于查询 \(u\),把 \(u\) 到根的路径上的 \(\Delta_g\) 求和,正好得到 \(g(\text{lca}(u,v))\)。
- \(D(u)\):\(g(x)=d_x\),\(\Delta(x)=d_x-d_{\text{pa}(x)}=1\)(边权)。
→ 每个 \(v\) 到根的路径上所有非根节点 \(+1\)。 - \(E(u)\):\(g(x)=d_x^2\),\(\Delta(x)=d_x^2-d_{\text{pa}(x)}^2=2d_{\text{pa}(x)}+1=2d_x-1\)。
→ 每个 \(v\) 到根的路径上非根节点加 \(2d_x-1\)。 - \(F(u)\):\(g(x)=d_v\cdot d_x\),\(\Delta(x)=d_v(d_x-d_{\text{pa}(x)})=d_v\)。
→ 每个 \(v\) 到根的路径上非根节点加 \(d_v\)。
然后就做完了,用线段树 + 树剖维护这些值的变化即可。代码就不贴了。
T4 门童
什么叫假贪心 60 分。。其实这个贪心还是有点说法的,起码我看出来决策单调性了。
乐子贪心代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e5 + 5;
struct node{
int st, ed, f;
}a[M];
bool cmp(node x, node y) {
if(x.st == y.st) return x.ed < y.ed;
else return x.st < y.st;
}
int read(){
int sgn = 1, ans = 0;
char c = getchar();
while(!isdigit(c)){
if(c == '-') sgn = -sgn;
c = getchar();
}
while(isdigit(c)) ans = ans * 10 + c - '0', c = getchar();
return sgn * ans;
}
signed main(){
freopen("d.in", "r", stdin);
freopen("d.out", "w", stdout);
int n = read(), l = read();
int x = read(), y = read(), z = read(), w = read();
int ans = 0, cost = l * (y + z), tim = 0;
for(int i = 1; i <= n; i++) {
a[i].st = read(), a[i].ed = read(), a[i].f = read();
a[i].ed += a[i].st;
}
sort(a + 1, a + 1 + n, cmp);
for(int i = 1; i <= n; i++) {
if(2 * l < a[i].ed - tim || (a[i].st - tim) * x > cost + (a[i].st - tim - 2 * l) * w) {
ans -= (a[i].st - tim) * x;
ans += a[i].f * (a[i].ed - a[i].st);
tim = a[i].st;
} else{
ans -= cost;
ans += (a[i].st - 2 * l - tim) * w;
if(a[i].f > w) ans += a[i].f * (a[i].ed - a[i].st), tim = a[i].st;
else ans += w * (a[i].ed - a[i].st), tim = a[i].ed;
}
}
cout << ans;
return 0;
}
别笑,你也过不了第二关
好了现在讲正解。我们注意到去门口接人的时间点只可能是这个人刚来或者这个人快走了,不然可以躺在沙发上刷好感度。
注意到这个就迈进了一大步,我们令 \(dp_i\) 表示在时间点 \(i\) 的时候获得的最大好感度。那么显然有 \(\mathcal{O}(n^2)\) 转移式子:
- 若一直在门口等待:
- 若离开门口去沙发,再回来:
枚举上一次在门口的关键点 \(j\),且牛牛在 \(k_j\) 时刻离开门口,在 \(k_i\) 时刻回到门口。
需要满足:\(k_i-k_j\ge 2L\),并且区间内的所有选手,都必须满足 \(t_r+p_r\ge k[i]\)。
合法区间可以预处理成:\([\max\{t_j\mid t_j\le k_i],\ t_j+p_j<k_i\},\ k_i - 2L]\),若没有这样的选手,则左区间为 \(0\)。
则有转移:
美美获得 \(70\) 分。下面是满分做法:
定义前缀和:
则区间接待收益为:
代入转移二,得到:
其中:
对于每个 \(j\),它贡献一条直线:
其中:
查询时候的 \(x=key_i\),再加上常数:
对每个 \(j\),它的直线在查询时刻 \(key_i\) 可用,当且仅当:
也就是 \(j\) 处在一个动态区间内。
由于随着 \(i\) 增大:
- \(low_i\) 单调不降
- \(R_i=key_i-2L\) 单调不降
- 查询点 \(key_i\) 单调不降
- 斜率 \(C_j+x_{11}\) 也单调不降
因此可以用李超线段树 + 线段树区间维护,复杂度 \(O(n\log^2 n)\)。

浙公网安备 33010602011771号