Gravatar
终焉折枝
积分:1449
提交:196 / 358


T4 - Constructive 题解

题目简述

题目大意
给定 $N$ 种二维向量 $v_i = (a_i, b_i)$ 及其代价 $c_i$,每种向量可无限次使用。求构造出目标向量 $u = (x, y)$ 的最小总代价。

  • 向量数量 $N \le 90$
  • 目标坐标 $x, y \le 10^9$
  • 向量分量 $a_i, b_i \le 10$
  • 向量代价 $c_i \le 10^9$
The Key:这是一个 二维无界背包问题,或者等价于 二维网格上的最短路问题

子任务简析

从简单情况到一般情况的思考过程:

  • Subtask 1: 步长为 1 的坐标移动,结果显而易见。
  • Subtask 2 & 3: 坐标范围小,直接建图跑 Dijkstra
  • Subtask 4: 降维打击,转化为一维无界背包。
  • Subtask 5: 大坐标直线路径,引入同余类 BFS/DP 思想。

正解

解法 1:分治(官方题解)

几何中点引理
如果一组短向量的和为 $(x, y)$,那么我们通过调整顺序,一定可以把他们分成两半,使得每一半的和都在 $(\frac{x}{2}, \frac{y}{2})$ 的常数范围内。

转移方程:

$f(x, y) = \min_{\delta_x, \delta_y} \{ f(\lfloor \frac{x}{2} \rfloor + \delta_x, \lfloor \frac{y}{2} \rfloor + \delta_y) + f(\lceil \frac{x}{2} \rceil - \delta_x, \lceil \frac{y}{2} \rceil - \delta_y) \}$

C++ 实现 (官方 std 风格)
#include <bits/stdc++.h>
using namespace std;

const long long INF = 4e18;
const int MAX_LENGTH = 10;
long long one_vec[MAX_LENGTH + 1][MAX_LENGTH + 1];
unordered_map <long long, long long> dp;
const int MAX_X = 1000000001;

long long DP(long long x, long long y){
    long long pos = x * MAX_X + y;
    if(dp.count(pos)) return dp[pos];

    long long tmp = INF;
    if(x <= MAX_LENGTH && y <= MAX_LENGTH) tmp = one_vec[x][y];

    for (long long dx = -MAX_LENGTH; dx <= MAX_LENGTH; dx++){
        for (long long dy = -MAX_LENGTH; dy <= MAX_LENGTH; dy++){
            long long x1 = x / 2 + dx, y1 = y / 2 + dy;
            long long x2 = x - x1, y2 = y - y1;
            if(min({x1, x2, y1, y2}) < 0) continue;
            if(x1 + y1 == 0 || x2 + y2 == 0) continue;
            tmp = min(tmp, DP(x1, y1) + DP(x2, y2));
        }
    }
    return dp[pos] = tmp;
}

int main(){
    int N; long long x, y;
    cin >> N >> x >> y;
    for (int i = 0; i <= MAX_LENGTH; i++)
        for (int j = 0; j <= MAX_LENGTH; j++) one_vec[i][j] = INF;

    for (int a, b, c; N--;){
        cin >> a >> b >> c;
        one_vec[a][b] = min(one_vec[a][b], (long long)c);
    }
    long long ans = DP(x, y);
    if(ans >= INF) cout << "-1\n";
    else cout << ans << '\n';
    return 0;
}

解法 2:基底 + 小范围微调

核心逻辑:我们先在原点附近进行小范围的精确搜索(微调),然后通过解二元一次方程组,利用“性价比”最高的基底向量组快速跨越远距离。

[Image of Vector Decomposition] $$(x, y) = \underbrace{(dx, dy)}_{\text{微调部分}} + \underbrace{k_i v_i + k_j v_j}_{\text{基底部分}}$$
C++ 实现 (基底枚举法)
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
const ll INF = 4e18;

struct vec { int a, b; ll c; } v[105];
struct node {
    int x, y; ll d;
    bool operator > (const node& o) const { return d > o.d; }
};

int n; ll tx, ty, res = INF;
ll dp[205][205];

int main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> tx >> ty;
    for(int i = 1; i <= n; i++) cin >> v[i].a >> v[i].b >> v[i].c;
    
    for(int i = 0; i <= 30; i++)
        for(int j = 0; j <= 30; j++) dp[i][j] = INF;

    priority_queue<node, vector<node>, greater<node>> pq;
    dp[0][0] = 0; pq.push({0, 0, 0});
    while(!pq.empty()){
        node cur = pq.top(); pq.pop();
        if(cur.d > dp[cur.x][cur.y]) continue;
        for(int i = 1; i <= n; i++){
            int nx = cur.x + v[i].a, ny = cur.y + v[i].b;
            if(nx <= 30 && ny <= 30 && dp[nx][ny] > cur.d + v[i].c){
                dp[nx][ny] = cur.d + v[i].c;
                pq.push({nx, ny, dp[nx][ny]});
            }
        }
    }

    for(int i = 1; i <= n; i++){
        for(int j = i; j <= n; j++){
            ll det = 1LL * v[i].a * v[j].b - 1LL * v[j].a * v[i].b;
            for(int dx = 0; dx <= 30; dx++){
                for(int dy = 0; dy <= 30; dy++){
                    if(dp[dx][dy] == INF) continue;
                    ll rx = tx - dx, ry = ty - dy;
                    if(rx < 0 || ry < 0) continue;
                    if(det != 0){
                        ll na = rx * v[j].b - ry * v[j].a;
                        ll nb = ry * v[i].a - rx * v[i].b;
                        ll d = det;
                        if(d < 0) { d = -d; na = -na; nb = -nb; }
                        if(na >= 0 && nb >= 0 && na % d == 0 && nb % d == 0)
                            res = min(res, dp[dx][dy] + (na / d) * v[i].c + (nb / d) * v[j].c);
                    } else if(1LL * v[i].a * ry == 1LL * v[i].b * rx){
                        ll k = -1;
                        if(v[i].a != 0 && rx % v[i].a == 0) k = rx / v[i].a;
                        else if(v[i].b != 0 && ry % v[i].b == 0) k = ry / v[i].b;
                        else if(v[i].a == 0 && v[i].b == 0 && rx == 0 && ry == 0) k = 0;
                        if(k >= 0) res = min(res, dp[dx][dy] + k * v[i].c);
                    }
                }
            }
        }
    }
    if(res >= INF) cout << -1 << '\n';
    else cout << res << '\n';
    return 0;
}



题目4297  [TIOJ - 114學年度複試] Constructive      1      评论
2026-02-09 16:42:47    
Gravatar
终焉折枝
积分:1449
提交:196 / 358

T3 - Communication 题解

题目简述

题目大意
给定三个长度为 $N$ 的序列 $a, b, w$ 和两个参数 $L, R$。构造一张有向图,点 $u \to v$ 有边当且仅当 $L \le a_u + b_v \le R$。求从起点出发,到所有点的最短路(最短路定义为路径点权之和)。

  • 数据规模:$N \le 2 \times 10^5$
  • 点权限制:$w_i \ge 0$
The Key:这是一个隐式建图的最短路问题。 核心矛盾在于边数可能达到 $O(N^2)$,必须利用边存在的代数条件 $b_v \in [L - a_u, R - a_u]$ 进行区间优化寻点。

子任务分析

  • Subtask 1 ($N \le 7$):极小数据,全排列或暴力搜索。
  • Subtask 2 ($a_i$ 恒定):连边具有一致性,只需判断 $a_0 + b_v$ 是否合法。
  • Subtask 3 ($N \le 10^3$):显式建边跑 Dijkstra
  • Subtask 4 ($L = 1$):转化为前缀连边,按 $b_i$ 排序后建链优化。
  • Subtask 5 ($N \le 5 \times 10^4$):典型的线段树优化建图区间连边。

正解:Dijkstra + Set 优化寻点

核心算法流程

基于 Dijkstra 的贪心性质:当一个点从优先队列弹出时,其最短路已确定。我们只需解决:如何快速找到未访问且满足条件的邻居 $v$

  1. 将所有尚未确定最短路点(除了起点)存入 std::set,按 $b_i$ 值升序排列。
  2. 每次从优先队列中取出当前最短路最小的点 $u$。
  3. set 中执行 lower_bound,定位满足 $b_v \ge L - a_u$ 的最小位置。
  4. 从该位置开始向后迭代,只要满足 $b_v \le R - a_u$:
    • 更新 $v$ 的最短路。
    • 将 $v$ 加入优先队列。
    • 关键点:从 set 中永久删除 v
复杂度保证:由于每个点在 set 中仅会被删除一次,迭代器移动的总次数为 $O(N)$。整体复杂度受控于 set 的查询与删除,为 $O(N \log N)$。
C++ 正解实现 (std 风格)
#include <bits/stdc++.h>
#define uwu return 0;
using namespace std;

int main(){
    cin.tie(0), ios::sync_with_stdio(0);
    int N, L, R;
    cin >> N >> L >> R;

    vector<int> a(N), b(N);
    vector<long long> w(N);
    for(auto &i : a) cin >> i;
    for(auto &i : b) cin >> i;
    for(auto &i : w) cin >> i;

    // 按 b 值排序存储待访问点
    set <pair<int, int>> sort_by_b;
    priority_queue <pair<long long, int>> pq;
    vector <long long> dist(N, -1);
    
    for (int i = 1; i < N; i++) {
        sort_by_b.insert({b[i], i});
    }

    pq.push({-w[0], 0}); // w[0] 为起点距离,取负值用于大根堆模拟小根堆

    while(!pq.empty()){
        pair <long long, int> tp = pq.top();
        pq.pop();
        
        // 剪枝:如果已经确定过更短路径,跳过
        if (dist[tp.second] != -1 && dist[tp.second] < -tp.first) continue;
        dist[tp.second] = -tp.first;

        // 在 set 中查找 b[v] 在 [L - a[u], R - a[u]] 范围内的所有点
        auto it = sort_by_b.lower_bound({L - a[tp.second], -1});
        while (it != sort_by_b.end() && it->first + a[tp.second] <= R) {
            pq.push({tp.first - w[it->second], it->second});
            // 访问后立即删除,确保每个点只被处理一次
            it = sort_by_b.erase(it);
        }
    }

    for(int i = 0; i < N; i++) {
        cout << dist[i] << (i == N - 1 ? "" : " ");
    }
    cout << '\n';
    uwu;
}

题目4293  [TIOJ - 114學年度複試] Communication      1      评论
2026-02-09 09:57:18    
Gravatar
终焉折枝
积分:1449
提交:196 / 358

T3 - Communication 题解

题目简述

题目大意

给定三个长度为 $N$ 的序列 $a, b, w$ 和两个参数 $L, R$。构造一张有向图,点 $u \to v$ 有边当且仅当:

$L \le a_u + b_v \le R$

求从起点出发,到所有点的最短路(路径上所有点的点权 $w_i$ 之和)。

The Key: 这是一个隐式建图的最短路问题。由于边数可能达到 $O(N^2)$,核心优化在于利用条件 $b_v \in [L - a_u, R - a_u]$ 进行区间连边或范围搜索。

子任务分析

子任务 特点 / 策略 复杂度
Subtask 1 $N \le 7$,全排列枚举或 DFS $\mathcal{O}(N!)$
Subtask 3 $N \le 10^3$,直接显式建边 $\mathcal{O}(N^2)$
Subtask 5 线段树优化建图 $\mathcal{O}(N \log N)$

正解思路:Dijkstra + 集合优化

在 Dijkstra 算法执行过程中,由于点权 $w_i \ge 0$,一旦一个点从优先队列中取出,其最短路即确定。我们不需要真的把边连出来。

执行流程:

  1. 将所有尚未确定最短路的点放入一个按 $b_i$ 排序的 std::set(或平衡树)。
  2. 从优先队列弹出当前最短路点 $u$。
  3. set 中利用 lower_bound 寻找满足 $b_v \ge L - a_u$ 的第一个点。
  4. 向后遍历 set,只要满足 $b_v \le R - a_u$:
    • 更新点 $v$ 的最短路。
    • 将 $v$ 加入优先队列。
    • 关键:将 $v$ 从 set 中永久删除(因为 $v$ 的最短路已找到,不会再被其他点松弛)。

由于每个点仅被删除一次,总的时间复杂度为 $\mathcal{O}(N \log N)$。

C++ 实现代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    cin.tie(0), ios::sync_with_stdio(0);
    int N, L, R;
    cin >> N >> L >> R;
    vector<int> a(N), b(N);
    vector<long long> w(N);
    for(int &i : a) cin >> i;
    for(int &i : b) cin >> i;
    for(long long &i : w) cin >> i;

    set<pair<int, int>> sort_by_b;
    priority_queue<pair<long long, int>> pq;
    vector<long long> dist(N, -1);

    for (int i = 1; i < N; i++) sort_by_b.insert({b[i], i});
    pq.push({-w[0], 0});

    while(!pq.empty()) {
        pair<long long, int> tp = pq.top();
        pq.pop();
        int u = tp.second;
        long long d = -tp.first;
        if (dist[u] != -1) continue;
        dist[u] = d;

        auto it = sort_by_b.lower_bound({L - a[u], -1});
        while(it != sort_by_b.end() && it->first + a[u] <= R) {
            pq.push({-(d + w[it->second]), it->second});
            it = sort_by_b.erase(it); // 每个点只会被删除一次
        }
    }

    for(int i = 0; i < N; i++) cout << dist[i] << (i == N-1 ? "" : " ");
    return 0;
}
        

题目4296  [TIOJ - 114學年度複試] Interactive      1      评论
2026-02-09 09:48:36    
Gravatar
终焉折枝
积分:1449
提交:196 / 358

T1 - Output Only 题解

题目简述

题目大意

给定一棵 $N$ 个节点的有根树,根为 1。每个节点有一个初始颜色 $c_i \in [0, k-1]$。

操作:选择一个节点 $u$ 和颜色 $p$,将 $u$ 及其子树内所有节点的颜色 $c_v \leftarrow (c_v + p) \pmod k$。

目标:通过若干次操作,使所有节点颜色变为 0。题目要求在发生 $Q$ 次单点颜色修改 $(w_i, x_i)$ 后,分别求出达成目标所需的最少操作次数。

  • $N, Q, k \le 2 \times 10^5$

The Key:利用 差分 的思想,将子树修改转化为对“父子颜色关系”的维护。这是一个典型的树上差分或转化贡献维度的思维题。


子任务分析

Subtask 1:树是一条链

当树是一条链(例如 $1 \to 2 \to \dots \to N$)时,我们可以利用序列差分的思想。定义 $d_i = (c_i - c_{i-1}) \pmod k$,其中 $c_0 = 0$。

对于以 $u$ 为根的子树(链上的后缀)进行操作,只会改变 $d_u$ 的值,而不会影响 $d_{u+1} \dots d_N$。为了让所有 $c_i = 0$,等价于让所有差分值 $d_i = 0$。如果某位置 $d_i \neq 0$,我们就必须在 $i$ 处进行一次操作来修正它。因此答案为 $\sum_{i=1}^N [d_i \neq 0]$。

时间复杂度:$\mathcal{O}(N + Q)$

Subtask 2:$k=2$

当只有两种颜色时,问题可以简化为统计 坏边。考虑一条边 $(u, v)$,其中 $u$ 是 $v$ 的父节点。

如果在 $v$ 处不进行任何操作,那么 $v$ 的颜色变化量将完全取决于 $u$ 的变化量。即操作后 $c'_v - c'_u \equiv c_v - c_u \pmod k$。要使最终 $c'_v = c'_u = 0$,必须满足 $c_v - c_u \equiv 0$。如果初始 $c_v \neq c_u$,我们就必须在 $v$ 处进行一次操作。对于 $k = 2$,这等价于统计两端颜色不同的边数(根节点视作父节点颜色为 0)。

Subtask 4:$N, Q \le 5 \times 10^4$

对于一般情况,可以考虑 根号分治

  • 按照节点的度数 $\deg$ 分为大点和小点。
  • 修改小点时,直接暴力遍历其所有相邻边,更新答案。
  • 修改大点时,更新大点的信息,维护大点与大点之间的关系。

时间复杂度:$\mathcal{O}(Q\sqrt{N})$


正解思路

根据上述性质探索,我们可以得到核心结论:

本题等价于维护树上 两端颜色不同的边数

$Ans = [c_{\text{root}} \neq 0] + \sum_{(u, v) \in E} [c_u \neq c_v]$

动态维护时,对于点 $u$:

  • 父边贡献:当 $c_u \neq c_{fa}$ 时,贡献加 1。
  • 子边贡献:使用 cnt[u][col] 维护 $u$ 的子节点中颜色为 col 的数量。子边贡献即为 son_amnt[u] - cnt[u][c_u]

时间复杂度:$\mathcal{O}(N \log N + Q \log N)$

C++ 实现代码

#include <bits/stdc++.h>
using namespace std;

const int SIZE = 2e5 + 5;
map<int, int> amnt_by_color[SIZE];
vector<int> graph[SIZE];
int color[SIZE], pa[SIZE], son_amnt[SIZE];

void dfs(int nd, int rt) {
    pa[nd] = rt;
    for(auto i : graph[nd]) {
        if(i == rt) continue;
        amnt_by_color[nd][color[i]]++;
        son_amnt[nd]++;
        dfs(i, nd);
    }
}

int main() {
    cin.tie(0), ios::sync_with_stdio(0);
    int N, k, Q;
    cin >> N >> k >> Q;
    for (int i = 1; i <= N; i++) cin >> color[i];
    for (int i = 1, u, v; i < N; i++) { cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
    dfs(1, 0);
    
    int different_edge = (color[1] == 0 ? 0 : 1);
    for (int i = 1; i <= N; i++) { for(auto j : amnt_by_color[i]) { if(j.first != color[i]) different_edge += j.second; } } for (int w, x; Q--; ) { cin >> w >> x;
        // 减去修改前的贡献
        different_edge -= (color[pa[w]] != color[w] && w != 1);
        different_edge -= (son_amnt[w] - amnt_by_color[w][color[w]]);
        
        if(pa[w]) amnt_by_color[pa[w]][color[w]]--;
        color[w] = x;
        if(pa[w]) amnt_by_color[pa[w]][color[w]]++;
        
        // 加上修改后的贡献
        different_edge += (color[pa[w]] != color[w] && w != 1);
        different_edge += (son_amnt[w] - amnt_by_color[w][color[w]]);
        
        // 修正根节点特判
        if(w == 1) different_edge = (color[1] != 0) + (son_amnt[1] - amnt_by_color[1][color[1]]);
        
        cout << different_edge << '\n'; } return 0; } 

题目4294  [TIOJ - 114學年度複試] Output Only      1      评论
2026-02-09 09:46:54    
Gravatar
RpUtl
积分:1505
提交:162 / 303

$n\le 100$

对于询问 $[l,r]$,暴力枚举 $l',r'$,然后暴力计算,时间复杂度为 $O(mn^3)$。

或者简单优化一下到 $O(mn^2)$。期望得分 $10$。

$n\le 1000$

先预处理出所有区间的权值 $v_{l,r}$。

然后可以用各种做法求出所有区间的子区间的权值 $ans_{l,r}$。

时间复杂度为 $O(n^2+m)$。期望得分 $30$。

特殊性质

按位与,按位或,$\gcd$ 三种运算有一个很厉害的性质,以 $\gcd$ 为例:对于一个数 $x$,不断令 $x\gets \gcd(x,v)$,则 $x$ 值发生变化的次数为 $\log V$ 次。

具体的原因可以均摊分析,考虑 $x$ 的质因子可重集合,显然其大小不超过 $\log V$,每次取 $\gcd$,这个集合要么不变,要么大小至少减一,只有集合大小改变才会影响 $x$ 的值,所以 $x$ 值发生变化的次数为 $\log V$ 次。

按位与和按位或也是如此,每一位上只会从一种值变成另一种值,所以变化次数也不超过 $\log V$。

数据随机后,若一个区间长度超过 $100$,答案就极有可能变成 $0$ 了。

然后乱搞一下应该可以得到 $40$ 的分数。

$n\le 10^5$

考虑离线扫描线。

有这样一种经典扫描方式,令 $r:1\to n$,假设当前扫到 $r$,我们让 $\forall l \in [1,r]$ 的位置 $v_l$ 加上区间 $[l,r]$ 的权值。

这样若对于询问 $[L,R]$,若 $r$ 扫到了 $R$,则查询 $v_{L\sim R}$ 的区间和即可。这是非常经典的增量贡献求答案的方法。

回到本题,使用上面所述的扫描线算法,因为上述特殊性质的存在,假设当前扫到 $r$,设 $A_i$ 为 $i\sim r$ 的区间按位与的和,$B_i,C_i$ 同理,则这三个数组分别可以划分为 $\log V$ 段,每一段内值相同。

这样我们得到 $3\log V$ 断点,设 $D_i=A_iB_iC_i$。则 $D_i$ 也被划分成 $\log V$ 段,每一段内部值相同。

那么我们令 $\forall i\in [1,r],v_i\gets v_i+D_i$,这样更新过程可以转化成对 $v$ 数组的 $\log V$ 段区间加法。

查询答案即为查询区间和,找断点可以二分加线段树,总而言之时间复杂度为 $O(n\log^2 n+m\log n)$,期望得分 $80$。

如果你用下文所述找断点的方法,用树状数组常数小的话也可以得到 $100$。

$n\le 5\times 10^5,m\le 5\times 10^6$

考虑正解,静态询问,且 $q\le 5\times 10^6$,应该要做到 $O(1)$ 回答询问,考虑离线扫描线。

有这样一种不经典的扫描线方法:维护一个指针 $r$ 从 $1$ 扫到 $n$,每扫到一个位置就回答右端点在 $r$ 的询问的答案。考虑此时在第 $l$ 个位置上维护 $w_{r,l}$ 表示 $[l,r]$ 所有子区间的价值之和。

不难发现我们把题目又读了一遍。考虑当 $r-1\to r$ 时,$w_{r,l}$ 相比于 $w_{r-1,l}$ 的答案应该有怎样的变化。

注意 $w_{r,l}$ 的理解,可以理解成当指针扫到 $r$ 时的版本时,第 $l$ 个位置的值,并不需要真正维护二维数组,只需要滚动维护第二维即可,下文同样有类似的表达方法。

令 $A_{r,i}$ 表示扫到 $r$ 时 $i\sim r$ 的所有数按位与之和,$B_{r,i},C_{r,i}$ 同理,则需要更新:

$$w_{r,i}\gets w_{r-1,i}+\sum_{j=i}^rA_{r,j}B_{r,j}C_{r,j}$$

先考虑如何维护 $A_{r,i},B_{r,i},C_{r,i}$。不难发现,当 $r\to r+1$ 时,发生更改的 $A_{r,i},B_{r,i},C_{r,i}$ 都是 $1\sim r$ 的一段后缀。

对于 $x<y$,若 $C_{r+1,y}=\gcd(C_{r,y},c_{r+1})=C_{r,y}$,则显然有 $C_{r+1,x}=\gcd(C_{r,x},c_{r+1})=C_{r,x}$,因为 $C_{r,x},C_{r,y}$ 和 $C_{r,y},c_{r+1}$ 在集合上都是子集关系。

当 $r\to r+1$考虑这样一种更新方式,从 $r$ 出发,用 $r+1$ 的值倒着更新 $r-1,r-2,\dots$ 的位置,直到 $A_{r,i},B_{r,i},C_{r,i}$ 都不发生改变,则更新完成。

分析复杂度,以 $A_{r,i}$ 为例,这个位置对最终复杂度有贡献当且仅当某次更新时 $A_{r,i}$ 发生了改变,根据最上面的分析,这种改变次数是不超过 $\log V$ 的,所以更新的总的复杂度为 $O(n\log V)$。

此时,当 $r-1\to r$ 时,维护一个前缀和 $s_{r,i}$:

$$s_{r,i}=\sum_{j=1}^nA_{r,j}B_{r,j}C_{r,j}$$

则更新可以表示为:

$$w_{r,l}\gets w_{r-1,l}+s_{r,r}-s_{r,l-1}$$

令 $v_i=s_{i,i}$,则有:

$$w_{r,l}=\sum_{i=l}^rv_i-\sum_{i=l}^rs_{i,l-1}$$

前面的部分显然可以单独维护,后边的部分则是查询 $s$ 所有版本在位置 $i$ 上的历史和。

因为我们每次修改都是暴力修改 $s$,所以问题变成:

1. 单点修改某个位置的值 $s_i$。

2. 单点查询某个位置的历史和

这个东西已经非常简洁了,考虑第 $i$ 个位置的历史和 $hs_i$,$s_i$ 上一次修改的时间 $lst_i$,若在时刻 $t$ 修改 $s_i$ 为 $val$,则:

$$hs_i\gets hs_i+(t-lst_i)s_i,s_i\gets val,lst_i\gets t$$

则在 $t$ 时刻查询位置 $i$ 的历史和为:

$$hs_i+(t-lst_i+1)s_i$$

然后我们惊奇的发现这个问题就解决了。

时间复杂度为 $O(n\log n+m)$,期望得分 $100$。

代码实现

首先是 $\gcd$,可以用 C++ 自带的 `__gcd` 函数,也可以手写二进制优化更相减损术的 $\gcd$,两种写法各有优劣。

然后是快读快输,这个题已经卡常到普通的 fread 和 fwrite 已经无法满足了,建议找个又臭又长的封装版快读快输使用。

然后将询问储存:对于储存询问,不建议将左右端点分开两个数组储存,建议直接用结构体或者 pair 来储存询问左右端点(我也不知道为什么啊,可能是寻址更快吧)。

然后是将离线:不要用 vector,可以用链表在端点处挂上询问。不要用 sort,可以用计数排序。

luogu 的原题需要严厉的卡常,这里不知道是否需要。



Gravatar
RpUtl
积分:1505
提交:162 / 303

$n\le 10$

暴搜,时间复杂度 $O(n!m)$,期望得分 $20$。

$n\le 300$

令 $p_u$ 表示 $u$ 的父亲节点。根据 $l_i\le r_i<i$,不难有 $p_u<u$。

设 $u<v$,则 $p_v$ 一定在 $(u,v)$ 的路径上。

考虑证明:若 $p_v$ 不在 $(u,v)$ 路径上,当且仅当 $v$ 时 $u$ 的祖先,根据 $p_u<u$ 可以得到 $u$ 的祖先编号小于 $u$,即 $u>v$,与 $u<v$ 矛盾,故原命题得证。

以上完成本题最为关键的结论证明。

若对于每一次加操作,都暴力完成,复杂度显然是指数级。

不过对于每一种加操作,都可以用 $(u,v)$ 表示(下文均假设 $u<v$)且 $(u,v)$ 的加操作可以转化为 $(u,p_v)$ 的加操作。

若对链 $(a,b)$ 的加操作对链 $(c,d)$ 的加操作有转化,必须满足 $b<d$。且对于 $a\ne b$ 的情况,对链 $(a,c)$ 的加操作和链 $(b,c)$ 的加操作之间不会互相转化。

也就是说,我们以第二维按照 $v$ 划分所有链,可以分成若干层,当前层只会影响这层之前的层,且层内部之间不会影响。

可以设计出一个递推状物,设 $g_{u,v}$ 表示所有操作对 $(u,v)$ 这条链加上的期望值,则有:


$${g_{u,p_v}\gets g_{u,p_v}+\frac{g_{u,v}}{r_v-l_v+1}},  u<p_u  $$

$${g_{p_{v},u} \gets g_{u,p_v}+\frac{g_{u,v}}{r_v-l_v+1}},  u>p_u$$


注意到编码过程中,为了方便讨论,我们始终只对 $u<v$ 的状态操作,因此若出现 $p_v<u$ 的情况,要操作 $g_{p_v,u}$ 而不是 $g_{u,p_v}$。

其实是简单排列组合原理,每一层内部的转移因为无所谓,所以第一维的枚举顺序可以随便来。

递推完所有的 $g_{u,v}$ 后,考虑计算点 $u$ 的答案,根据结论,只要 $v<u$,那么 $(v,u)$ 的路径一定包含 $(p_u,u)$ 这条链,且对于任意 $(a,u),(b,u)$ 之间互不影响,说明 $(a,u),(b,u)$ 代表的加操作独立,可以直接加起来。

因此答案为 $\sum_{v<u}g_{v,u}$。直接做需要枚举每个点的父亲,时间复杂度 $O(n^3+m)$。期望得分 $60$。

$n\le 2000$

考虑优化这个过程,把 $g$ 数组当成一个二维平面,代码中 $k>i$ 的部分实际上是对横着的一段加上了一个数。$k<i$ 的部分是对竖着的一段加上了一个数。

可以用二维差分优化,递推的过程中求差分的前缀和数组,因为要求前缀和,所以第一维的枚举顺序也需要确定。

时间复杂度 $O(n^2+m)$,期望得分 $100$。



题目4250  时空跳跃 AAAAAAAAAA      3      评论
2026-02-07 15:57:29