【比赛总结】20260715 模拟赛总结
P4873 [USACO14DEC] Cow Jog G
给定了每头奶牛的初始位置 \(st_i\) 与永久速度 \(v_i\),计算出对应的终点位置 \(ed_i\)。
如果两头奶牛 \(i,j\) 在整个过程中没有撞上,要满足 \(st_i<st_j\) 与 \(ed_i<ed_j\)。
题目已经保证了 \(st\) 从小到大排序,那么我们就要使得分出来的每一个段落的 \(ed\) 都是一个上升子序列,本质上就是求全局的最长不上升子序列长度。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, t, cnt, a[N], q[N];
struct node {
int st, v;
} lsy_birthday[N];
int find(int x) { //二分查找 q 中大于等于 x 的下标
int l = 1, r = cnt, res = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (q[mid] >= x)
res = mid, l = mid + 1;
else
r = mid - 1;
}
return res;
}
signed main() {
cin >> n >> t;
for (int i = 1; i <= n; i++) cin >> lsy_birthday[i].st >> lsy_birthday[i].v;
// sort(lsy_birthday + 1, lsy_birthday + n + 1, [](node x, node y) { return x.st < y.st; });
for (int i = 1; i <= n; i++) a[i] = lsy_birthday[i].st + t * lsy_birthday[i].v;
for (int i = 1; i <= n; i++) {
int x = find(a[i]);
q[x + 1] = a[i]; //由于是最大的小于a[i]的数,因此要+1
if (x + 1 > cnt)
cnt++; //开辟新空间
}
cout << cnt << endl; // q的长度
return 0;
}
P3107 [USACO14OPEN] Odometer S
赛时写了 y总 的递推写法,逐位遍历,可惜没有那个实力,写出来的代码滚粗暴力。
- 状态设计
定义 \(dp[i][und][k][is0]\):
- \(i\) 为当前位置;
- \(und\) 表示是否已经低于实际数字;
- \(k\) 是计数器,用来检查是否至少有一半的相同数字,表示当前目标数字与其它数字的差值(在我的代码中,\(k\) 从 \(25\) 开始);
- \(is0\) 是一个布尔值,表示是否有前导零;
定义 DP 函数 \(DP(str, len, num1, num2)\):
- \(str\):上界数字的字符串(长度可能小于 len,但调用时统一传入 len = str.length())。
- \(len\):数字的固定位数(实际数值可能短于 len,通过前导零补足)。
- \(num1\):第一个目标数字。
- \(num2\):第二个目标数字。当 \(num2 = -1\) 时,表示单数字模式:数字中只允许出现任意数字,但我们要统计 \(num1\) 出现次数过半的情况;当 \(num2 \ne -1\) 时,表示双数字模式:数字中非前导零位只能出现 \(num1\) 或 \(num2\),且我们只统计两者出现次数相等的情况。
该函数统计长度固定为 \(len\),且数值上不超过字符串 \(str\) 的数字中,满足特定“数字组成规则”的数量。
- 转移方程
- 合法性检查:
- 如果是双数字模式(
num2 != -1),并且当前不是前导零(即front_zero == 0或now != 0),那么now必须等于num1或num2,否则跳过。- 如果当前紧贴上界(
limit == 0),且now > str[i] - '0',则跳过(超出上界)。
更新前导零标记:
new_front_zero = front_zero && (now == 0)。即只有之前是前导零且当前也填 0,才保持为 1。更新计数器
k:只有当new_front_zero == 0(即当前位是非前导零有效位)时,才根据模式调整k:
- 单数字模式:若
now == num1,则k--;否则k++。- 双数字模式:若
now == num1,则k--;若now == num2,则k++。(注意:由于合法性检查,双数字模式下now只能取这两者之一)更新
limit:new_limit = limit | (now < str[i] - '0')。若之前已小于,则保持;否则若当前位小于上界对应位,则变为小于。将方案数累加到
f[i+1][new_limit][new_k][new_front_zero]。
- 状态初始化:初始化:\(f[0][0][20][1] = 1\),表示从最高位(还未填任何数字)开始,紧贴上界,计数器为 20,且处于前导零状态。
- 答案计算:
DP 结束后,枚举所有状态 \(f[len][limit][k][front\ zero]\),其中 \(front\ zero = 0\)(表示数字不含前导零,即有效数字非空)。
单数字模式:统计所有 \(k \le 20\) 的状态求和。
双数字模式:统计所有 \(k = 20\) 的状态求和。
第一步累加所有单数字 DP,会把所有“至少一个数字出现次数大于等于一半”的数字都计入。
对于偶数长度的数字,可能出现两个数字各占一半(例如 \(1212\),\(1\) 和 \(2\) 各出现 \(2\) 次)。这样的数字在单数字统计中会被计入两次:一次以 \(1\) 为主导(出现次数为一半,满足“大于等于一半”),一次以 \(2\) 为主导。但它本质上只应算一次。
第二步对每一对 \((a, b)(a < b)\) 减去 \(DP(str, len, a, b)\),该 DP 正好统计了由 \(a\) 和 \(b\) 组成且两者数量相等的数字。这样就扣除了重复的一次,保证每个数字只被计一次。
注意:对于奇数长度的数字,不可能有两个数字各占一半(因为一半不是整数),所以不会产生重复。
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 25;
ll f[N][2][2 * N][2];
/*
定义 dp[i][und][k][is0]:
$i$ 为当前位置;
$und$ 表示是否已经低于实际数字;
$k$ 是计数器,用来检查是否至少有一半的相同数字(在我的代码中,$k$ 从 $25$ 开始);
$is0$ 是一个布尔值,表示是否有前导零;
*/
string le, ri;
/**
* @brief 数位DP核心函数
* @param str 数字上界字符串
* @param len 数字长度
* @param num1 目标数字1
* @param num2 目标数字2,-1表示只统计num1
* @return 合法数字总数
*/
ll DP(string str, int len, int num1, int num2) {
ll anss = 0;
// 初始化DP数组,偏移20避免下标负数
memset(f, 0, sizeof(f));
f[0][0][20][1] = 1;
// 从高位到低位逐位枚举
for (int i = 0; i < len; ++i) {
for (int limit = 0; limit <= 1; ++limit) {
for (int k = 0; k < 50; ++k) {
for (int front_zero = 0; front_zero <= 1; ++front_zero) {
ll cnt = f[i][limit][k][front_zero];
// 无方案直接跳过,优化
if (cnt == 0) continue;
// 枚举当前位填0~9
for (int now = 0; now <= 9; ++now) {
// 双数字模式:非前导零位不能出现num1/num2以外数字
if (num2 != -1 && (now != 0 || front_zero == 0) && now != num2 && now != num1)
continue;
// 上界限制:紧贴上限时不能超过当前位数字
if (limit == 0 && now > str[i] - '0')
continue;
int nk = k;
int n0 = front_zero;
n0 = n0 & (now == 0); // 更新前导零标记
// 非前导零,统计数字次数偏移
if (n0 == 0) {
if (num2 == -1) {
// 单数字统计:目标数字-1,其他数字+1
if (num1 == now) nk--;
else nk++;
} else {
// 双数字均分:num1-1,num2+1
if (num1 == now) nk--;
else if (num2 == now) nk++;
}
}
// 状态转移
int new_limit = limit | (now < str[i] - '0');
f[i + 1][new_limit][nk][n0] += cnt;
}
}
}
}
}
// 统计答案
if (num2 == -1) {
// 单数字:偏移 k <=20 代表目标数字出现次数过半
for (int limt = 0; limt < 2; ++limt)
for (int j = 0; j <= 20; ++j)
anss += f[len][limt][j][0];
} else {
// 双数字均分:偏移k=20 代表两数字数量相等
for (int limt = 0; limt < 2; ++limt)
anss += f[len][limt][20][0];
}
return anss;
}
// 计算 [0, str] 内合法数字总数
ll solve(string str) {
ll anss = 0;
int len = str.length();
// 累加所有单数字合法数
for (int i = 0; i <= 9; ++i)
anss += DP(str, len, i, -1);
// 容斥去重:减去两种数字同时满足的重复统计
for (int i = 0; i <= 9; ++i)
for (int j = i + 1; j <= 9; ++j)
anss -= DP(str, len, i, j);
return anss;
}
int main() {
ll le, ri; cin >> le >> ri;
cout << solve(to_string(ri)) - solve(to_string(le - 1)) << endl;
return 0;
}
P3113 [USACO14DEC] Marathon G
线段树板子题,维护每一个点消失带来的代价就好了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
int n, m, reduck[N], bit[N];
struct ac {
int x, y;
} a[N];
int dis(int x, int y) { return abs(a[x].x - a[y].x) + abs(a[y].y - a[x].y); }
void add(int x, int d) {
while (x <= n) {
bit[x] += d;
x += (x & (-x));
}
}
int sum(int x) {
int sum = 0;
while (x) {
sum += bit[x];
x -= (x & (-x));
}
return sum;
}
struct ab {
int l, r, re;
} tree[4 * N];
void build(int id, int l, int r) {
tree[id] = { l, r };
if (l == r) {
tree[id].re = reduck[l];
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
tree[id].re = max(tree[id << 1].re, tree[id << 1 | 1].re);
}
void change(int id, int x, int y) {
if (tree[id].l == tree[id].r) {
tree[id].re = y;
return;
}
int mid = (tree[id].l + tree[id].r) >> 1;
if (x <= mid) change(id << 1, x, y);
else change(id << 1 | 1, x, y);
tree[id].re = max(tree[id << 1].re, tree[id << 1 | 1].re);
}
int Found(int id, int l, int r) {
if (tree[id].l >= l && tree[id].r <= r) return tree[id].re;
else if (tree[id].l > r || tree[id].r < l) return -1e18;
else return max(Found(id << 1, l, r), Found(id << 1 | 1, l, r));
}
signed main() {
cin >> n >> m;
for (int i = 1, x, y; i <= n; i++) {
cin >> x >> y;
a[i] = { x, y };
}
for (int i = 2; i <= n; i++)
add(i, abs(a[i].x - a[i - 1].x) + abs(a[i].y - a[i - 1].y));
for (int i = 2; i < n; i++)
reduck[i] = dis(i - 1, i) + dis(i + 1, i) - dis(i + 1, i - 1);
build(1, 1, n);
while (m--) {
string opt;
cin >> opt;
if (opt == "Q") {
int x, y; cin >> x >> y;
if (y < x) swap(x, y);
if (y - x + 1 <= 2) cout << sum(y) - sum(x) << '\n';
else cout << min(sum(y) - sum(x), sum(y) - sum(x) - Found(1, min(x + 1, n), max(y - 1, 1ll))) << '\n';
}
else {
int id, x, y; cin >> id >> x >> y;
add(id, -dis(id, id - 1));
add(id + 1, -dis(id, id + 1));
a[id] = { x, y };
add(id, dis(id, id - 1));
add(id + 1, dis(id, id + 1));
if (id + 1 <= n && id - 1 >= 1)
change(1, id, dis(id + 1, id) + dis(id - 1, id) - dis(id + 1, id - 1));
if (id + 2 <= n)
change(1, id + 1, dis(id + 1, id) + dis(id + 2, id + 1) - dis(id + 2, id));
if (id - 2 >= 1)
change(1, id - 1, dis(id - 1, id) + dis(id - 2, id - 1) - dis(id - 2, id));
}
}
return 0;
}

浙公网安备 33010602011771号