ICPC 2026 Asia Online Test 1 解题报告
N. Red Sequence
考场上成功推到最后一步,但是因为忘记 cdq 遗憾离场。
首先定义 \(dp_{i}\) 表示到第 \(i\) 个为止的最长长度。 \(dp_i=\max{(dp_{i-1}, dp_j+(i-j))}=\max{(dp_{i-1},(dp_j-j)+i)}\)。但是这样的 \(j\) 是有要求的!我们考虑一下前缀和。假如说红黄蓝的前缀和分别是 \((Sa_i,Sb_i,Sc_i)\) 那么上面式子的 \(j\) 有这样的要求。
\(\begin{equation}\left\{\begin{aligned}Sa_i-Sa_j\ge Sb_i-Sb_j\\Sa_i-Sa_j\ge Sc_i-Sc_j\end{aligned}\right.\end{equation}\)
移项化简我们可以得到
\(\begin{equation}\left\{\begin{aligned}Sa_i-Sb_i\ge Sa_j-Sb_j\\Sa_i-Sc_i\ge Sa_j-Sc_j\end{aligned}\right.\end{equation}\)
因为对于每个 \(i\),\(i\) 是确定的,于是我们可以把前面的东西看成常数,我们不妨把 \((x_i,y_i)=(Sa_i-Sb_i,Sa_i-Sc_i)\) 这个问题扔到平面上。
于是这就是要求
\(\begin{equation}\left\{\begin{aligned}x_j\leq x_i\\y_j\leq y_i\end{aligned}\right.\end{equation}\)
满足这个要求的点的 \(dp-j\) 的最大值。直接跑 cdq 分治就行了。
我们甚至还有 \(x_i,y_i\in[-10^6,10^6]\) 于是不需要离散化。稍微平移一下坐标系就可以了。
本质是一个三维偏序问题,时间复杂度 \(\mathcal{O}(n\log^2n)\),评测机牛逼,能跑过 \(n=10^6\)。
#include<bits/stdc++.h>
#define rep(I, J, K) for(int I = (int)J; I <= (int)K; ++I)
#define per(I, J, K) for(int I = (int)J; I >= (int)K; --I)
using namespace std;
const int N = 3e6 + 10;
int n, Sa[N], Sb[N], Sc[N];
struct Point {
int x, y, z, id;
bool operator < (const Point &point) const {
return (y == point.y) ? z < point.z : y < point.y;
}
}a[N];
int dp[N];
const int minf = 0xcfcfcfcf;
struct BIT {
int val[N];
const int MAXN = 2e6 + 10;
int lowbit(int x) { return x & -x; }
void insert(int x, int va) {
while(x <= MAXN) {
val[x] = max(val[x], va);
x += lowbit(x);
}
}
void init() {
memset(val, 0xcf, sizeof val);
}
void Clear(int x) {
while(x <= MAXN) {
val[x] = minf;
x += lowbit(x);
}
}
int Query(int x) {
int res = minf;
while(x) {
res = max(res, val[x]);
x -= lowbit(x);
}
return res;
}
}bit;
// 明确一下 dp 的定义 dp 表示到第 i 个为止的答案
// dp_j + (i-j) 满足这样要求的 j 其实是 [j + 1,i] 中间刚好是 i-j+1 个数,好像并没有什么问题?
void solve(int l, int r) {
if(l == r) return;
int mid = l + r >> 1;
solve(l, mid);
vector<Point> left(a + l, a + mid + 1);
vector<Point> right(a + mid + 1, a + r + 1);
sort(left.begin(), left.end());
sort(right.begin(), right.end());
int i = 0;
for (auto &p : right) {
while (i < left.size() && left[i].y <= p.y) {
bit.insert(left[i].z, dp[left[i].id] - left[i].id);
++i;
}
dp[p.id] = max(dp[p.id], bit.Query(p.z) + p.id);
}
rep(k, 0, i - 1) bit.Clear(left[k].z);
rep(i, mid + 1, r) dp[i] = max(dp[i - 1], dp[i]);
solve(mid + 1, r);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
rep(i, 1, n) cin >> Sa[i] >> Sb[i] >> Sc[i];
rep(i, 1, n) Sa[i] += Sa[i - 1], Sb[i] += Sb[i - 1], Sc[i] += Sc[i - 1];
rep(i, 0, n) a[i].x = a[i].id = i, a[i].y = Sa[i] - Sb[i] + 1000000 + 1, a[i].z = Sa[i] - Sc[i] + 1000000 + 1;
bit.init();
memset(dp, 0xcf, sizeof dp);
dp[0] = 0;
solve(0, n);
rep(i, 1, n) cerr << dp[i] << ' ';
cerr << endl;
cout << dp[n] << endl;
return 0;
}
G. Toll Gates on a Tight Schedule
考场上队友写出来了,赛后补一下。
首先能想到我们可以把 \(1\) 到 \(n\) 的所有路径按照到 \(1\) 的距离分层,那么假如说第 \(i\) 层有 \(c_i\) 个节点,那么第 \(i\) 层给答案的最多贡献就是 \(\times c_i\)。那么根据经典结论,全部拆成 \(e\) 乘积是最大的,整数的话就是 \(2\) 和 \(3\) 了。那么也大概就是一个 \(\mathcal{O}(3^{\frac{n}{3}})\) 的一个东西。很容易想到 meet in the middle。我们能拆成一个 \(\mathcal{O}(3^\frac{n}{6})\) 的两个东西合并,\(x+y\leq C\)。我们把 \(x\) 和 \(y\) 序列排序之后双指针就可以了。
最后总时间复杂度是 \(\mathcal{O}(n3^\frac{n}{6})\),\(n=90\) 的情况下过的还是比较极限的。
当然有一些小细节需要注意:比如说 \(1\) 到 \(n\) 本身路径可能不是很多,所以他可能诱导你让你的划分在 \(1\) 这种边缘位置,但是实际上不在 \(1\) 到 \(n\) 路径上的点很多,这些点占用了很多内存空间,所以要注意把这些点去掉。
调了一天,确实难写。
#include<bits/stdc++.h>
#define rep(I, J, K) for(int I = (int)J; I <= (int)K; ++I)
#define per(I, J, K) for(int I = (int)J; I >= (int)K; --I)
using namespace std;
const int N = 5e5 + 10;
typedef long long ll;
typedef unsigned long long ull;
int n, m;
ll C, c[N];
vector<int>G[N], _G[N];
bool OKK[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> C;
rep(i, 1, n) cin >> c[i];
rep(i, 1, m) {
int x, y;
cin >> x >> y;
G[x].push_back(y);
_G[y].push_back(x);
}
// 先 bfs 求出最短路
queue<int>Q;
Q.push(1);
vector<int>dis(n + 5, 0), _dis(n + 5, 0);
while(!Q.empty()) {
int u = Q.front(); Q.pop();
for(int v : G[u]) {
if(v != 1 && !dis[v]) {
dis[v] = dis[u] + 1;
Q.push(v);
}
}
}
Q.push(n);
while(!Q.empty()) {
int u = Q.front(); Q.pop();
for(int v : _G[u]) {
if(v != n && !_dis[v]) {
_dis[v] = _dis[u] + 1;
Q.push(v);
}
}
}
rep(u, 1, n) OKK[u] = (dis[u] + _dis[u] != dis[n]);
if(n == 1) {
cout << (c[1] <= C) << '\n';
return 0;
}
if(!dis[n]) {
puts("0");
return 0;
}
vector<ll>ways(n + 5), _ways(n + 5);
ways[1] = 1;
rep(i, 0, dis[n] - 1) {
// 现在是第 i 层
rep(j, 1, n) if(dis[j] == i) {
for(int v : G[j]) if(dis[v] == i + 1) ways[v] += ways[j];
}
}
_ways[n] = 1;
per(i, dis[n], 1) {
rep(j, 1, n) if(dis[j] == i) {
for(int v : _G[j]) if(dis[v] == i - 1) _ways[v] += _ways[j];
}
}
int maxd = -1;
ll maxx = 1ll << 62;
// rep(i, 1, n) cerr << i << ' ' << dis[i] << ' ' << ways[i] << ' ' << _ways[i] << '\n';
rep(i, 0, dis[n]) {
ll mx = 0;
rep(j, 1, n) if(dis[j] == i) {
mx = max(mx, max(ways[j], _ways[j]));
// cerr << ways[j] << ' ' << _ways[j] << ' ' << i << '\n';
if(mx >= maxx) break;
}
cerr << i << ' ' << maxx << '\n';
if(mx < maxx) {
maxx = mx;
maxd = i;
}
}
// 前面走 maxd 步.
cerr << maxd << ' ' << dis[n] << '\n';
vector<vector<ll>>Cost(n + 5, vector<ll>()), _Cost(n + 5, vector<ll>());
vector<bool>vis(n + 5, 0);
// 前面走 maxd 步.
Cost[1].push_back(c[1]);
Q.push(1);
while(!Q.empty()) {
int u = Q.front(); Q.pop();
for(int v : G[u]) {
if(dis[v] == dis[u] + 1 && dis[v] <= maxd && !OKK[v]) {
if(Cost[v].empty()) Cost[v].reserve(min(ways[v], 2000000ll));
for(ll &x : Cost[u]) if(x + c[v] <= C) Cost[v].push_back(x + c[v]);
if(!vis[v]) Q.push(v), vis[v] = 1;
}
}
}
rep(i, 1, n) vis[i] = 0;
Q.push(n);
_Cost[n].push_back(c[n]);
while(!Q.empty()) {
int u = Q.front(); Q.pop();
for(int v : _G[u]) {
if(dis[v] == dis[u] - 1 && dis[v] >= maxd && !OKK[v]) {
if(_Cost[v].empty()) _Cost[v].reserve(min(_ways[v], 2000000ll));
for(ll &x : _Cost[u]) if(x + c[v] <= C) _Cost[v].push_back(x + c[v]);
if(!vis[v]) vis[v] = 1, Q.push(v);
}
}
}
ull res = 0;
// cout << maxd << endl;
rep(i, 1, n) {
if(dis[i] == maxd) {
if(Cost[i].empty() || _Cost[i].empty()) continue;
sort(Cost[i].begin(), Cost[i].end());
sort(_Cost[i].begin(), _Cost[i].end());
// cerr << i << ":\n";
// for(ll x : Cost[i]) cerr << x << ' ';
// cerr << '\n';
// for(ll x : _Cost[i]) cerr << x << ' ';
// cerr << '\n';
int k = _Cost[i].size() - 1;
rep(j, 0, Cost[i].size() - 1) {
while(k >= 0 && _Cost[i][k] + Cost[i][j] > C + c[i]) --k;
res += (k + 1);
}
}
}
cout << res << '\n';
return 0;
}

浙公网安备 33010602011771号