OIFC 2025.11.25 模拟赛总结
生日当天还有模拟赛(
T1 Pocky游戏
题目描述
Mdk 和 Hmr 正在吃 Pocky,她们感到有些无聊,于是决定玩一个小游戏。
现在有一根长度为 \(n\) 的 Pocky,其中从左往右数第 \(i\) 单位长度 Pocky 的美味值为 \(a_i\)。现在 Mdk 和 Hmr 决定吃掉这根 Pocky,Mdk 从左往右吃,Hmr 从右往左吃。
-
Mdk 先开始吃,她会从左侧吃 \(1\) 或 \(2\) 单位长度的 Pocky。
-
接下来,如果上一个人吃了 \(k\) 单位的 Pocky,则下一个人可以吃 \(k\) 或 \(k + 1\)(如果剩余长度 \(\geq k + 1\))单位的 Pocky。
-
如果剩下的 Pocky 长度小于 \(k\),则这个游戏将会停止。
双方都想要最大化自己吃掉的 Pocky 的美味值减掉对方吃掉的 Pocky 的美味值,问在双方都使用最优策略的情况下,Mdk 吃掉的 Pocky 的美味值减去 Hmr 吃掉的 Pocky 的美味值将会是多少?
数据范围:\(1 \leq n \leq 4000, |a_i| \leq 10^4\)。空间限制:\(256 MB\)。
题解
类似博弈论,所以可以直接写 dfs 求解。
具体地,我们令 \(f_{l, r, k}\) 表示两个人分别到了 \(l, r\),且上一个人走到了 \(k\),会得到的结果。转移方程与博弈论模型类似,是 \(\max\) 里面套两个 \(\min\)。
这样做,转移是 \(\mathcal{O}(1)\) 的,但状态是 \(\mathcal{O}(n^3)\) 的,需要优化。
注意到 \(k\) 不会很大,因为很快就取完了。具体地,需要满足 \(\frac{k(k + 1)}{2} \leq n\),因此 \(k\) 这一维开到 \(90\) 即可。
另一方面,两个人轮流取数,所以差不会太大。由于每次取 \(k\) 或 \(k + 1\),所以一个回合最多差 \(1\),即总共不会差超过 \(\sqrt{n}\)。
这样,状态就优化成 \(n \cdot \sqrt{n} \cdot \sqrt{n}\) 了,可以通过。
参考代码(记搜版):
#include<bits/stdc++.h>
//#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
const int _ = 0;
int n, s[4005], val[4005][90][90][2];
bool vis[4005][90][90][2];
inline int dfs(int l, int r, int k, int p){
if(vis[l][r - l + 1][k][p]) return val[l][r - l + 1][k][p];
int res = ~(0^_^0);
if(p == 0)
if(r - l + 1 < k)
res = 0;
else if(r - l + 1 >= k + 1)
res = max(dfs(l + k, r, k, 1) + s[l + k - 1] - s[l - 1], dfs(l + k + 1, r, k + 1, 1) + s[l + k] - s[l - 1]);
else
res = dfs(l + k, r, k, 1) + s[l + k - 1] - s[l - 1];
else
if(r - l + 1 < k)
res = 0;
else if(r - l + 1 >= k + 1)
res = min(dfs(l, r - k, k, 0) - s[r] + s[r - k], dfs(l, r - (k + 1), k + 1, 0) - s[r] + s[r - (k + 1)]);
else
res = dfs(l, r - k, k, 0) - s[r] + s[r - k];
vis[l][r - l + 1][k][p] = true;
return val[l][r - l + 1][k][p] = res;
}
signed main(){
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i++) s[i] = s[i - 1] + read();
write(dfs(1, n, 1, 0));
return 0;
}
T2 宝石装置
题目描述
Hmr 发现了一个宝石装置,并对其进行了研究:
这个装置由 \(n\) 颗宝石组成,依次编号为 \(1, 2, 3, \cdots, n\)。
这些宝石之间由总计 \(m\) 条路径连接。其中第 \(i\) 条路径可以由 \((u_i, v_i, a_i, b_i, x_i)\) 五个参数组成,代表着,在第 \(u_i\) 个宝石被激活之后,这条路径将会开始运作,在 \(a_ix_i + b_i\) 单位时间后,\(v_i\) 也将会被激活。其中 \(u_i, v_i, a_i, b_i\) 是固定的,\(x_i\) 可以由 Hmr 设置成任意非负实数。
在使用这个宝石装置的过程中,Hmr 可以在 \(0\) 时刻主动使用魔力激活任意一个节点 \(S\),然后宝石装置将会通过路径激活其他的宝石。如果一个宝石在不超过 \(V_i\) 时刻激活了,那么称这个宝石是有效的。
如果有效的宝石数量不小于 \(K\),那么这个装置将会开始工作,并释放出 \(\min\limits_{i = 1}^m x_i\) 的能量。
Hmr 想要知道,这个装置最多可以释放多少能量。
数据范围:\(1 \leq K \leq n \leq 2000, 1 \leq m \leq 10^4, 1 \leq u_i, v_i \leq n, 1 \leq a_i, b_i, V_i \leq 10^9\)。时间限制:\(2000 ms\)。
题解
赛时想到了 \(\mathcal{O}(nm\log n\log V)\) 做法,但不知道随机序列的严格前缀最大值期望个数(呜呜。
假设答案是 \(mid\),则所有 \(x\) 都取 \(mid\) 肯定是最优的!
因此,考虑二分答案,然后跑全源 Dijkstra 判断即可。
考虑转换顺序,先枚举起点再二分答案,这样二分的左端点可以取目前最大的答案。
这样可以得到一个优化:如果 \(l\) 都不符合要求,就不用二分了。
注意到:随机序列的严格前缀最大值期望只有 \(\mathcal{O}(\log n)\) 个。
因此我们随机调整枚举起点的顺序即可,这样期望只需要进行 \(\mathcal{O}(\log n)\) 次二分答案。
参考代码:
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("inline")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fstrict-aliasing")
#include<bits/stdc++.h>
//#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
struct Edge{
int v, a, b;
};
int n, m, K, id, V[2005];
vector<Edge> e[2005];
struct Node{
int u; double dis;
bool operator > (const Node& a) const {
return dis > a.dis;
}
};
double dis[2005];
bool vis[2005];
priority_queue<Node, vector<Node>, greater<Node> > q;
set<int> s;
inline bool judge(int start, double x){
for(int i = 1; i <= n; i++) dis[i] = 1e18;
memset(vis, false, sizeof(vis));
while(!q.empty()) q.pop(); s.clear();
q.push({start, 0}), s.insert(start), dis[start] = 0;
while(!q.empty()){
if(s.size() >= K) return true;
int u = q.top().u;
q.pop();
if(vis[u]) continue;
vis[u] = true;
for(int i = 0; i < e[u].size(); i++){
int v = e[u][i].v;
double w = (double)1.0 * e[u][i].a * x + 1.0 * e[u][i].b;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if(dis[v] <= V[v]) s.insert(v);
if(s.size() >= K) return true;
q.push({v, dis[v]});
}
}
}
return s.size() >= K;
}
int p[2005];
signed main(){
freopen("gem.in", "r", stdin);
freopen("gem.out", "w", stdout);
n = read(), m = read(), K = read(), id = read();
for(int i = 1; i <= n; i++) V[i] = read();
for(int i = 1; i <= m; i++){
int u = read(), v = read(), a = read(), b = read();
e[u].push_back({v, a, b});
}
for(int i = 1; i <= n; i++) p[i] = i;
mt19937 rnd(26145983);
shuffle(p + 1, p + 1 + n, rnd);
double ans = -1, eps = 0.000001;
for(int i = 1; i <= n; i++){
double l = min(ans + eps, 1e9), r = 1e9, res = -1;
if(!judge(p[i], l)) continue; // !
while(l <= r){
double mid = 1.0 * (l + r) / 2.0;
if(judge(p[i], mid)){
res = mid;
l = mid + eps;
}else{
r = mid - eps;
}
}
ans = max(ans, res);
}
if(ans < 0){
puts("-1");
}else{
cout << fixed << setprecision(6) << ans << '\n';
}
return 0;
}
T3 第二个宝石装置
1
T4 无限队列
题目描述
Mdk 发现了一个可数无穷长的队列,其中队列从左往右数第 \(i\) 个元素初始编号为 \(i\)。
Mdk 想要对序列进行 \(d\) 次操作,每一次操作描述如下:
她会同时删掉队列中的 \(n\) 个元素,它们分别是从左往右数的第 \(a_1, a_2, \cdots, a_n\) 个元素,空缺的位置将由右侧的元素来补齐。
Mdk 想要知道最后某几个位置的元素的编号,但是她现在并没有时间,所以她希望你来告诉她。她会给你 \(Q\) 个位置 \(x_i\),希望你能够回答 \(d\) 次操作之后,从左往右数第 \(i\) 个数的编号是多少。
数据范围:\(1 \leq n, Q \leq 5 \times 10^5\),\(1 \leq d, a_i, x_i \leq 10^{12}\),\(a_i\) 两两互不相同。
题解
给出部分分做法。
暴力算部分分吗?
Sol 0
依题意模拟即可。
期望得分:\(20\)。
Sol 1
优先想到了这个做法。
赛时还记得打过一场 Div.2(Upd on Day2:CF2169D1),那场的 D1 和这道题类似,当时场切了。
考虑到,对于位置 \(x\),一次操作后相当于其下标减少了 \(a\) 中不超过它的数的个数。而且,无论操作多少次,数字的相对位置不会改变。
因此,对于每一组询问,进行二分答案,然后 \(\mathcal{O}(d)\) 检查即可。
期望得分:\(40\)。
参考代码(\(40\) 分):
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("inline")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fstrict-aliasing")
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
int n, a[500005], Q, d;
inline int check(int x){
for(int i = 1; i <= d; i++){
x -= upper_bound(a + 1, a + 1 + n, x) - a - 1;
}
return x;
}
inline int Solve(int x){
int l = 1, r = 1e18, ans;
while(l <= r){
int mid = (l + r) >> 1ll;
if(check(mid) >= x){
ans = mid;
r = mid - 1;
}else{
l = mid + 1;
}
}
return ans;
}
signed main(){
freopen("queue.in", "r", stdin);
freopen("queue.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + 1 + n);
Q = read(), d = read();
while(Q--) write(Solve(read())), putchar('\n');
return 0;
}
Sol 2
考虑优化 Sol 1,这个 trick 来自 Sol 1 中提到的那场 CF 的 D2(Upd on Day2:CF2169D2)。
实际上,对于一个数 \(x\),假设 \(a\) 中最后一个不超过 \(x\) 的数为 \(y\),则 \(x\) 不超过 \(y\) 的情况下每次减少的量不变。
因此,我们可以通过除法代替多次减法,就获得了根号复杂度的算法。
期望得分:\(70\)。
参考代码(\(70\) 分):
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("inline")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fstrict-aliasing")
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
inline void write(int x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
return;
}
int n, a[500005], Q, d;
inline int check(int x){
int dd = d;
while(dd > 0){
int pos = upper_bound(a + 1, a + 1 + n, x) - a - 1, y = a[pos];
if(!pos) break;
int ci = (x - y) / pos;
ci = max(min(ci, dd), 1ll);
x -= pos * ci;
dd -= ci;
}
return x;
}
inline int Solve(int x){
int l = 1, r = 1e18, ans;
while(l <= r){
int mid = (l + r) >> 1ll;
if(check(mid) >= x){
ans = mid;
r = mid - 1;
}else{
l = mid + 1;
}
}
return ans;
}
signed main(){
freopen("queue.in", "r", stdin);
freopen("queue.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
sort(a + 1, a + 1 + n);
Q = read(), d = read();
while(Q--) write(Solve(read())), putchar('\n');
return 0;
}
Sol 3
考虑正解。

浙公网安备 33010602011771号