有 t ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t (1≤t≤100)组测试数据.每组测试数据输入两个整数 n , m ( 1 ≤ n , m ≤ 100 ) n,m\ \ (1\leq n,m\leq 100) n,m (1≤n,m≤100).
对每组测试数据,输出 m ∑ i = 1 n i 2 \displaystyle m\sum_{i=1}^n i^2 mi=1∑ni2.
∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n − 1 ) 6 \displaystyle \sum_{i=1}^n i^2=\dfrac{n(n+1)(2n-1)}{6} i=1∑ni2=6n(n+1)(2n−1).
最终答案是 O ( n 3 m ) O(n^3m) O(n3m)级别的,不会爆int.
void solve() {
int n, m; cin >> n >> m;
cout << n * (n + 1) * (2 * n + 1) / 6 * m << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
给定一个既约分数 x y \dfrac{x}{y} yx,将其化为连分数的形式 a 0 + 1 a 1 + 1 a 2 + 1 ⋱ + 1 a n a_0+\dfrac{1}{a_1+\dfrac{1}{a_2+\dfrac{1}{\ddots+\dfrac{1}{a_n}}}} a0+a1+a2+⋱+an1111.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据输入两个整数 x , y ( 1 ≤ x , y ≤ 1 e 9 , gcd ( x , y ) = 1 ) x,y\ \ (1\leq x,y\leq 1\mathrm{e}9,\gcd(x,y)=1) x,y (1≤x,y≤1e9,gcd(x,y)=1).
对每组测试数据,先输出 n ( 0 ≤ n ≤ 100 ) n\ \ (0\leq n\leq 100) n (0≤n≤100),再输出 a 0 , ⋯ , a n ( 0 ≤ a i ≤ 1 e 9 ) a_0,\cdots,a_n\ \ (0\leq a_i\leq 1\mathrm{e}9) a0,⋯,an (0≤ai≤1e9).若有多组解,输出任一组.
①若 y ∣ x y\mid x y∣x,即 x y \dfrac{x}{y} yx是整数时,它已是连分数.
②若 x y \dfrac{x}{y} yx是假分数,提出 a 0 = ⌊ x y ⌋ a_0=\left\lfloor\dfrac{x}{y}\right\rfloor a0=⌊yx⌋即可变为真分数,故只需考虑真分数的情况.
③若 x y \dfrac{x}{y} yx是真分数,注意到 x y = 1 y x \dfrac{x}{y}=\dfrac{1}{\dfrac{y}{x}} yx=xy1,其中 y x \dfrac{y}{x} xy是假分数,递归处理即可.递归终止条件: x y \dfrac{x}{y} yx是整数.
事实上,②和③可统一为 x y = ⌊ x y ⌋ + x m o d y y = ⌊ x y ⌋ + 1 y x m o d y \dfrac{x}{y}=\left\lfloor\dfrac{x}{y}\right\rfloor+\dfrac{x\ \mathrm{mod}\ y}{y}=\left\lfloor\dfrac{x}{y}\right\rfloor+\dfrac{1}{\dfrac{y}{x\ \mathrm{mod}\ y}} yx=⌊yx⌋+yx mod y=⌊yx⌋+x mod yy1.
该过程类似于辗转相除法,时间复杂度 O ( log max { x , y } ) O(\log\max\{x,y\}) O(logmax{x,y}).
void cal(int x, int y, vi& res) {
res.push_back(x / y);
if (x % y == 0) return; // 递归终止条件
cal(y, x % y, res);
}
void solve() {
int x, y; cin >> x >> y;
vi ans;
cal(x, y, ans);
cout << ans.size() - 1 << ' '; // 注意-1
for (auto i : ans) cout << i << ' ';
cout << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
在平面直角坐标系 x O y xOy xOy中, x x x轴代表地面.平面上有 n n n个不考虑厚度的挡雨板,每个挡雨板可视为一条线段.现从无限高的地方开始下雨,雨只能竖直下落,问 x x x轴上有多少地方不会被雨淋到.
第第一行输入一个整数 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5).接下来 n n n行每行输入四个整数 x 1 , y 1 , x 2 , y 2 ( 1 ≤ x 1 < x 2 ≤ 1 e 5 , 1 ≤ y 1 , y 2 ≤ 1 e 5 ) x_1,y_1,x_2,y_2\ \ (1\leq x_1
显然只需考虑每个挡雨板在 x x x轴上的投影,转化为求 n n n个区间 [ x 1 , x 2 ] [x_1,x_2] [x1,x2]的并的长度之和.
void solve() {
int n; cin >> n;
vii segs(n);
for (int i = 0; i < n; i++) {
int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
segs[i] = { min(x1,x2),max(x1,x2) };
}
sort(all(segs));
int ans = 0;
int L = -INF, R = -INF; // 当前区间的左右端点
int i = 0;
while (i < n) {
auto [l, r] = segs[i];
if (l > R) L = l, R = r; // 当前的区间与上一区间无交集
while (i < n && segs[i].first <= R) R = max(R, segs[i++].second);
ans += R - L;
}
cout << ans;
}
int main() {
solve();
}
x , y x,y x,y范围小,可用差分+前缀和求区间并.
const int MAXN = 1e5 + 5;
int diff[MAXN];
void solve() {
int n; cin >> n;
for (int i = 0; i < n; i++) {
int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
diff[x1]++, diff[x2]--;
}
int ans = 0;
for (int i = 1; i <= 1e5; i++) {
diff[i] += diff[i - 1];
if (diff[i]) ans++;
}
cout << ans;
}
int main() {
solve();
}
上为差分+前缀和,下为区间并.反直觉的事情是区间并的做法所需的空间更多.
两玩家玩卡牌游戏,每轮的情况为:玩家 1 1 1抽卡,玩家 1 1 1出牌,玩家 2 2 2抽卡,玩家 2 2 2出牌.对玩家出牌的阶段,可选择令自己的血量 + = k +=k +=k或让对方的血量 − = k -=k −=k,注意两玩家的血量无上限,初始时两玩家的血量都为 n n n,玩家血量 ≤ 0 \leq 0 ≤0时立即失败.若玩家手中无卡会进入疲劳状态,每抽一张卡需增加 1 1 1点自己的疲劳值,且会从生命中扣除等量的疲劳值,初始时两玩家的疲劳值都为 0 0 0.初始时两玩家手中都无卡.现pllj与freesin玩该游戏,pllj先手,两人都采取最优策略,问最后谁获胜.
有 t ( 1 ≤ t ≤ 1 e 5 ) t \ \ (1\leq t\leq 1\mathrm{e}5) t (1≤t≤1e5)组测试数据.每组测试数据输入两个整数 n , k ( 1 ≤ n , k ≤ 1 e 9 ) n,k\ \ (1\leq n,k\leq 1\mathrm{e}9) n,k (1≤n,k≤1e9).
显然 n = 1 n=1 n=1时pllj抽一张卡后即失败.
对 n ≥ 2 n\geq 2 n≥2的情况,显然两玩家都是抽一张卡打一张卡,则pllj若在第一轮无法打败freesin,则后续的轮中freesin每次都回血,因pllj先扣疲劳值,故最后freesin胜.故先手必胜的充要条件是: n ≤ k + 1 n\leq k+1 n≤k+1,其中 k + 1 k+1 k+1是因为两人血量都为 ( k + 1 ) (k+1) (k+1)时,pllj第一轮先攻击,第二轮freesin抽卡后失败.
void solve() {
int n, k; cin >> n >> k;
if (n > 1 && n <= k + 1) cout << "pllj" << endl;
else cout << "freesin" << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
给定一个 n × m n\times m n×m的 0 − 1 0-1 0−1矩阵,从左上角 ( 1 , 1 ) (1,1) (1,1)出发,到达右下角 ( n , m ) (n,m) (n,m),每次只能向右或向下移动到相邻的各自.问有几条不同的路径使得路径至少经过 p p p个 0 0 0和 q q q个 1 1 1,答案对 998244353 998244353 998244353取模.
第一行输入四个整数 n , m , p , q ( 1 ≤ n , m ≤ 500 , 0 ≤ p , q ≤ 1 e 4 ) n,m,p,q\ \ (1\leq n,m\leq 500,0\leq p,q\leq 1\mathrm{e}4) n,m,p,q (1≤n,m≤500,0≤p,q≤1e4).接下来输入一个 n × m n\times m n×m的 0 − 1 0-1 0−1矩阵.
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示从 ( 1 , 1 ) (1,1) (1,1)到 ( i , j ) (i,j) (i,j)恰经过 k k k个 0 0 0的路径数,最终答案为 ∑ k = p n + m − 1 − q d p [ n ] [ m ] [ k ] \displaystyle\sum_{k=p}^{n+m-1-q}dp[n][m][k] k=p∑n+m−1−qdp[n][m][k].初始条件 d p [ 1 ] [ 1 ] [ ! a [ 1 ] [ 1 ] ] = 1 dp[1][1][!a[1][1]]=1 dp[1][1][!a[1][1]]=1,注意不是 d p [ 1 ] [ 1 ] [ a [ 1 ] [ 1 ] ] = 1 dp[1][1][a[1][1]]=1 dp[1][1][a[1][1]]=1.
一个int类型的该数组所需空间 4 × 500 × 500 × 1 e 4 1024 × 1024 ≈ 1 e 4 M B > 256 M B \dfrac{4\times 500\times 500\times 1\mathrm{e}4}{1024\times 1024}\approx 1\mathrm{e}4\ \mathrm{MB}>256\ \mathrm{MB} 1024×10244×500×500×1e4≈1e4 MB>256 MB, d p dp dp数组的第一维滚动即可.
const int MAXN = 505, MAXM = 1e4 + 5;
const int MOD = 998244353;
int n, m, p, q;
int a[MAXN][MAXN];
int dp[2][MAXN][MAXM]; // dp[i][j][k]表示从(1,1)到(i,j)恰经过k个0的路径数
void solve() {
cin >> n >> m >> p >> q;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) cin >> a[i][j];
dp[1][1][!a[1][1]] = 1; // 初始条件
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
for (int k = 0; k <= i + j - 1; k++) { // 枚举0的个数
if (a[i][j]) dp[i & 1][j][k] = ((ll)dp[i & 1][j - 1][k] + dp[i - 1 & 1][j][k]) % MOD;
else {
dp[i & 1][j][k + 1] = ((ll)dp[i & 1][j - 1][k] + dp[i - 1 & 1][j][k]) % MOD;
dp[i & 1][j][0] = 0; // 清空非法状态
}
}
}
}
int ans = 0;
for (int k = p; k <= n + m - 1 - q; k++) ans = ((ll)ans + dp[n & 1][m][k]) % MOD;
cout << ans;
}
int main() {
solve();
}
称一个cache的容量为 k k k,如果它至多能同时存放 k k k个单位的数据.现有规则:①若要访问的数据在cache中,则发生cache hit,否则发生cache miss;②若要访问的数据不在cache中,且cache未满,将其加入cache中;③若要访问的数据不在cache中,且cache已满,则将cache中距离访问时间最久的数据替换为当前的要访问的数据.
给定 n n n个要访问的数据的编号 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9) a1,⋯,an (1≤ai≤1e9),问cache的容量至少为多少时能保证至少发生 k ( 1 ≤ k ≤ n ≤ 1 e 5 ) k\ \ (1\leq k\leq n\leq 1\mathrm{e}5) k (1≤k≤n≤1e5)次cache hit,若无法发发生,输出"cbddl".
显然二分,考虑如何check.用一个set
注意二分右边界取 n + 1 n+1 n+1,防止结果为 n n n时无法判断是否有解.
const int MAXN = 1e5 + 5;
int n, k;
int a[MAXN];
bool check(int capa) {
map mp; // mp[i]=j表示数据i的访问时间为j
set s; // first表示数据的访问时间,second表示数据的编号
int cnt = 0; // cache hit数
for (int i = 1; i <= n; i++) {
if (mp[a[i]]) { // cache hit
cnt++;
// 更新该数据的访问时间
s.erase({ mp[a[i]],a[i] });
mp[a[i]] = i;
s.insert({ mp[a[i]],a[i] });
}
else { // cache miss
if (s.size() < capa) { // cache未满
mp[a[i]] = i;
s.insert({ mp[a[i]],a[i] });
}
else { // cache已满
auto tmp = *s.begin(); // 距离访问时间最久的数据
s.erase(s.begin());
mp[tmp.second] = 0; // 将该数据移出cache
mp[a[i]] = i;
s.insert({ mp[a[i]],a[i] });
}
}
}
return cnt >= k;
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
int l = 1, r = n + 1;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << (l == n + 1 ? "cbddl" : to_string(l));
}
int main() {
solve();
}
给定一个长度为 n n n的正整数序列 a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an.现有询问:指定一个区间 [ l , r ] [l,r] [l,r],选定一个正整数 p ≥ 2 p\geq 2 p≥2,求 a [ l ⋯ r ] a[l\cdots r] a[l⋯r]中能被 p p p整除的数的个数的最大值.
有 t ( 1 ≤ t ≤ 5 e 4 ) t\ \ (1\leq t\leq 5\mathrm{e}4) t (1≤t≤5e4)组测试数据.每组测试数据第一行输入两个整数 n , q ( 1 ≤ n , q ≤ 5 e 4 ) n,q\ \ (1\leq n,q\leq 5\mathrm{e}4) n,q (1≤n,q≤5e4),分别表示序列长度和询问书.第二行输入 n n n个整数 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 6 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}6) a1,⋯,an (1≤ai≤1e6).接下来 q q q行每行输入两个整数 l , r ( 1 ≤ l , r ≤ n ) l,r \ \ (1\leq l,r\leq n) l,r (1≤l,r≤n),表示一个询问.数据保证所有测试数据的 n n n之和、 q q q之和都不超过 5 e 4 5\mathrm{e}4 5e4.
显然 p p p取素数最优.注意到 2 × 3 × 5 × 7 × 11 × 13 < 1 e 6 , 2 × 3 × 5 × 7 × 11 × 13 × 17 > 1 e 6 2\times 3\times 5\times 7\times 11\times 13<1\mathrm{e}6,2\times 3\times 5\times 7\times 11\times 13\times 17>1\mathrm{e}6 2×3×5×7×11×13<1e6,2×3×5×7×11×13×17>1e6,故 1 e 6 1\mathrm{e}6 1e6内的数至多有 7 7 7个不同的素因子,可用朴素的筛法预处理出,注意不能用线性筛,因为线性筛中每个数只会被其最小素因子筛掉.
类似于莫队维护区间众数, c n t [ p ] cnt[p] cnt[p]表示素因子 p p p出现的次数, s u m [ i ] sum[i] sum[i]表示出现次数为 i i i的素因子数,用莫队维护区间素因子出现次数最大值即可.设 1 e 6 1\mathrm{e}6 1e6内的数的素因子个数最大值为 k k k,因 n n n与 q q q同数量级,时间复杂度 O ( k n n ) O(kn\sqrt{n}) O(knn).
const int MAXN = 1e6 + 5;
int n, q;
int len; // 分块长度
int a[MAXN];
int block[MAXN]; // block[i]表示下标i所在分块的编号
struct Query {
int l, r, id; // 询问区间、编号
bool operator<(const Query& B)const {
return block[l] ^ block[B.l] ? l < B.l : block[l] & 1 ? r < B.r : r > B.r;
}
}ques[MAXN];
int cnt[MAXN]; // cnt[p]表示素因子p出现的次数
int sum[MAXN]; // sum[i]表示出现次数为i的素因子数
int res; // 当前答案
int ans[MAXN];
bool vis[MAXN];
vi fac[MAXN]; // 存每个数的素因子
void init() {
for (int i = 2; i < MAXN; i++) {
if (!vis[i]) {
for (int j = i; j < MAXN; j += i) {
vis[j] = true;
fac[j].push_back(i);
}
}
}
}
void insert(int x) {
for (auto p : fac[a[x]]) {
sum[cnt[p]++]--; // 素因子p旧的出现次数--
sum[cnt[p]]++; // 素因子p新的出现次数++
res = max(res, cnt[p]); // 更新区间素因子出现次数最大值
}
}
void remove(int x) {
for (auto p : fac[a[x]]) {
if (--sum[cnt[p]] == 0 && res == cnt[p]) res--; // 更新区间素因子出现次数最大值
sum[--cnt[p]]++;
}
}
void solve() {
init();
CaseT{
cin >> n >> q;
len = sqrt(n);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= q; i++) {
cin >> ques[i].l >> ques[i].r;
ques[i].id = i;
block[i] = (i - 1) / len + 1;
}
sort(ques + 1, ques + q + 1);
int l = 1, r = 0;
for (int i = 1; i <= q; i++) {
while (l > ques[i].l) insert(--l);
while (r < ques[i].r) insert(++r);
while (l < ques[i].l) remove(l++);
while (r > ques[i].r) remove(r--);
ans[ques[i].id] = res;
}
for (int i = 1; i <= q; i++) cout << ans[i] << endl;
while (l <= r) remove(l++); // 清空
}
}
int main() {
solve();
}
有编号 1 ∼ n 1\sim n 1∼n的 n n n个同学在各自家中, ( n − 1 ) (n-1) (n−1)条双向道路连接 n n n个同学的家.每个同学可选择自己独立完成作业,也可选择抄其他同学的作业.若同学 i i i想抄同学 j j j的作业,则他要先等同学 j j j独立完整作业,然后前往他家抄作业,最后回到自己家,此时视为作业完成.同学 i i i到同学 j j j家抄作业再回到自己家所需时间等于两同学家间的距离(注意不是两倍).
第一行输入两个整数 n , q ( 1 ≤ n , q ≤ 1 e 5 ) n,q\ \ (1\leq n,q\leq 1\mathrm{e}5) n,q (1≤n,q≤1e5),分别表示学生数和操作数.第二行输入 n n n个整数 a 1 , ⋯ , a n ( 0 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (0\leq a_i\leq 1\mathrm{e}9) a1,⋯,an (0≤ai≤1e9),表示每个学生独立完成作业的时间.接下来 ( n − 1 ) (n-1) (n−1)行每行输入三个整数 u , v , w ( 1 ≤ u , v ≤ n , 0 ≤ w ≤ 1 e 9 ) u,v,w\ \ \ (1\leq u,v\leq n,0\leq w\leq 1\mathrm{e}9) u,v,w (1≤u,v≤n,0≤w≤1e9),表示有连接同学 u u u家与同学 v v v家的长度为 w w w的双向道路.接下来 q q q行每行输入一个操作,格式如下:① 1 i x ( 1 ≤ i ≤ n , 0 ≤ x ≤ 1 e 9 ) 1\ i\ x\ \ (1\leq i\leq n,0\leq x\leq 1\mathrm{e}9) 1 i x (1≤i≤n,0≤x≤1e9),表示学生 i i i独立完成作业的时间变为 x x x;② 2 i w 2\ i\ w 2 i w,表示第 i i i条道路的长度变为 w w w;③ 3 3 3,表示询问所有学生完成作业的最早时间,设第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个学生完成作业的最早时间为 t i t_i ti,则输出 t 1 x o r t 2 x o r ⋯ x o r t n t_1\ \mathrm{xor}\ t_2\ \mathrm{xor}\ \cdots\ \mathrm{xor}\ t_n t1 xor t2 xor ⋯ xor tn.数据保证所有同学能到达其他同学家里,且询问数不超过 200 200 200.
显然修改操作可 O ( 1 ) O(1) O(1)完成,下面讨论查询操作.
同学 u u u独立完成作业的时间为 a u a_u au,他去抄在 t v t_v tv时刻已完成作业的同学 v v v的作业所需时间 d i s ( u , v ) + t v dis(u,v)+t_v dis(u,v)+tv,则 u u u完成作业的最早时间为 min { a u , d i s ( u , v ) + t v } \min\{a_u,dis(u,v)+t_v\} min{au,dis(u,v)+tv},其中 d i s ( u , v ) dis(u,v) dis(u,v)表示节点 u u u与节点 v v v的最短距离.朴素做法:对每个节点,枚举已完成作业的节点,检查是否能让自己的时间变短;或对每个已完成作业的节点,检查是否能让其他节点的时间变短.直接求两点间的距离时间复杂度 O ( n 3 ) O(n^3) O(n3),用倍增或树剖求两点间的距离时间复杂度 O ( n 2 log n ) O(n^2\log n) O(n2logn),都会TLE.
注意到上述朴素做法的过程与Dijkstra算法的过程类似.因树上两节点间的简单路径唯一,设从节点 u u u经过节点 p p p到达节点 v v v,若 u u u要松弛 v v v,则 u u u会先松弛 p p p,此时 u u u松弛 v v v等价于 u u u先松弛 p p p, p p p再松弛 v v v,故只需考虑树边,用堆优化的Dijkstra算法时间复杂度 O ( n log n ) O(n\log n) O(nlogn),也会TLE.
考虑优化,注意到上述过程中每个节点只会被其父亲节点或儿子节点更新,考虑换根DP. d p [ u ] dp[u] dp[u]表示只考虑节点 u u u的子树的前提下同学 u u u完成作业的最早时间,状态转移方程 d p [ u ] = min v ∈ s o n u { d p [ v ] + w ( u , v ) } \displaystyle dp[u]=\min_{v\in son_u}\{dp[v]+w(u,v)\} dp[u]=v∈sonumin{dp[v]+w(u,v)},其中 w ( u , v ) w(u,v) w(u,v)表示节点 u u u与节点 v v v间的边权,初始条件 d p [ u ] = a [ u ] dp[u]=a[u] dp[u]=a[u].第一次DFS以节点 1 1 1为根节点,求出所有 d p [ u ] dp[u] dp[u]后,第二次DFS换根.设节点 v v v是节点 u u u的一个儿子节点,若能用 d p [ v ] dp[v] dp[v]更新 d p [ u ] dp[u] dp[u],则 d p [ v ] ≥ d p [ u ] + w ( u , v ) dp[v]\geq dp[u]+w(u,v) dp[v]≥dp[u]+w(u,v),即不会出现重复走一条边的情况,故由以节点 u u u为根换为以节点 v v v为根,状态转移方程 d p [ v ] = min v ∈ s o n u { d p [ u ] + w ( u , v ) } \displaystyle dp[v]=\min_{v\in son_u}\{dp[u]+w(u,v)\} dp[v]=v∈sonumin{dp[u]+w(u,v)}.
为方便修改边权,可用链式前向星存图,记下每条道路对应的双向边的编号.但事实上同时加正向边和反向边时第 i i i条道路对应的双向边的编号即 2 ( i − 1 ) 2(i-1) 2(i−1)和 2 ( i − 1 ) + 1 2(i-1)+1 2(i−1)+1.
const int MAXN = 1e5 + 5, MAXM = MAXN << 1;
int n, q;
int a[MAXN]; // 每个同学独立完成作业的时间
int head[MAXN], edge[MAXM], w[MAXM], nxt[MAXM], idx;
umap mp; // 每条道路对应的双向边的编号
int dp[MAXN]; // dp[u]表示只考虑节点u的子树的前提下同学u完成作业的最早时间
void add(int a, int b, int c) {
edge[idx] = b, w[idx] = c, nxt[idx] = head[a], head[a] = idx++;
}
void dfs1(int u, int fa) { // 当前节点、前驱节点
dp[u] = a[u]; // 初始化为独立完成作业的时间
for (int i = head[u]; ~i; i = nxt[i]) {
int v = edge[i];
if (v == fa) continue;
dfs1(v, u); // 递归求子树的信息
dp[u] = min(dp[u], dp[v] + w[i]);
}
}
void dfs2(int u, int fa) { // 当前节点、前驱节点
for (int i = head[u]; ~i; i = nxt[i]) {
int v = edge[i];
if (v == fa) continue;
dp[v] = min(dp[v], dp[u] + w[i]); // 换根
dfs2(v, u); // 递归求子树的信息
}
}
void solve() {
memset(head, -1, so(head));
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
mp[i] = { idx,idx + 1 };
add(u, v, w), add(v, u, w);
}
while (q--) {
int op; cin >> op;
if (op == 1) {
int i, x; cin >> i >> x;
a[i] = x;
}
else if (op == 2) {
int i, x; cin >> i >> x;
w[mp[i].first] = w[mp[i].second] = x;
}
else {
dfs1(1, -1); // 从1号节点开始搜,无前驱节点
dfs2(1, -1); // 从1号节点开始搜,无前驱节点
int ans = 0;
for (int u = 1; u <= n; u++) ans ^= dp[u];
cout << ans << endl;
}
}
}
int main() {
solve();
}
对边权的修改操作可用引用的方式完成,用vector
如下代码无需快读快写能在牛客上通过,但需快读快写才能在CF上通过.
namespace FastIO {
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++) // 重写getchar()
#define pc(ch) (p - buf2 == SIZE ? fwrite(buf2, 1, SIZE, stdout), p = buf2, *p++ = ch : *p++ = ch) // 重写putchar()
char buf[1 << 23], * p1 = buf, * p2 = buf;
template
void read(T& x) { // 数字快读
x = 0;
T sgn = 1;
char ch = gc();
while (ch < '0' || ch > '9') {
if (ch == '-') sgn = -1;
ch = gc();
}
while (ch >= '0' && ch <= '9') {
x = (((x << 2) + x) << 1) + (ch & 15);
ch = gc();
}
x *= sgn;
}
const int SIZE = 1 << 21;
int stk[40], top;
char buf1[SIZE], buf2[SIZE], * p = buf2, * s = buf1, * t = buf1;
template
void print_number(T x) {
p = buf2; // 复位指针p
if (!x) {
pc('0');
return;
}
top = 0; // 栈顶指针
if (x < 0) {
pc('-');
x = ~x + 1; // 取相反数
}
do {
stk[top++] = x % 10;
x /= 10;
} while (x);
while (top) pc(stk[--top] + 48);
}
template
void write(T x) { // 数字快写
print_number(x);
fwrite(buf2, 1, p - buf2, stdout);
}
};
using namespace FastIO;
const int MAXN = 1e5 + 5, MAXM = MAXN << 1;
int n, q;
int a[MAXN]; // 每个同学独立完成作业的时间
int w[MAXN]; // 边权
vector> edges[MAXN];
int dp[MAXN]; // dp[u]表示只考虑节点u的子树的前提下同学u完成作业的最早时间
void dfs1(int u, int fa) { // 当前节点、前驱节点
dp[u] = a[u]; // 初始化为独立完成作业的时间
for (auto& [v, w] : edges[u]) {
if (v == fa) continue;
dfs1(v, u); // 递归求子树的信息
dp[u] = min(dp[u], dp[v] + w);
}
}
void dfs2(int u, int fa) { // 当前节点、前驱节点
for (auto& [v, w] : edges[u]) {
if (v == fa) continue;
dp[v] = min(dp[v], dp[u] + w); // 换根
dfs2(v, u); // 递归求子树的信息
}
}
void solve() {
read(n), read(q);
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i < n; i++) {
int u, v; read(u), read(v), read(w[i]);
edges[u].emplace_back(v,w[i]), edges[v].emplace_back(u,w[i]);
}
while (q--) {
int op; read(op);
if (op == 1) {
int i, x; read(i), read(x);
a[i] = x;
}
else if (op == 2) {
int i, x; read(i), read(x);
w[i] = x;
}
else {
dfs1(1, -1); // 从1号节点开始搜,无前驱节点
dfs2(1, -1); // 从1号节点开始搜,无前驱节点
int ans = 0;
for (int u = 1; u <= n; u++) ans ^= dp[u];
write(ans), putchar('\n');
}
}
}
int main() {
solve();
}