LGP12579 [UOI 2021] 哥萨克与GCD 学习笔记
LGP12579 [UOI 2021] 哥萨克与GCD 学习笔记
前言
加强库班哥萨克第四团!
\(\texttt{2024}\) 闪击双倍受伤是铁卡,怎么 \(\texttt{3033}\) 闪击烟幕就成了金卡?
你告诉我烟幕对于这种落地要么抢线要么交换单位的东西有什么用?三费苏联金就给这点数值和机制?
把烟幕去了,身板加强到 \(\texttt{3-4}\),给一个可在同一回合内移动并攻击,再给个伏击,这才像话吧。
题意简述
有两个长为 \(n\) 的数组 \(A,B\)。可以无限次使用以下操作:
- 选择一个区间 \([l,r]\),以 \(\gcd_{i=l}^r a_i\) 的代价查询 \(\sum_{i=l}^r b_i\)。
最少付出多少代价才能确定 \(B\) 的所有元素?
哦对了还有 \(q\) 次对 \(a_i\) 的单点修。每次修改后也要求当前答案。
\(n,q\le 10^5\),\(a_i\le 10^9\)。
做法解析
查询 \(\sum_{i=l}^r b_i\) 怎么得出 \(B\) 数组的所有元素呢?其实,你的每次询问都可以被视作一个方程,你最后就是要通过恰当的询问拿到足够的方程,组成一个有唯一一组解的 \(n\) 元一次方程组并把它解了。
可是我们要怎样构造这个方程组呢?别忘了,你需要支付的代价是那段区间的 \(\gcd\)。如果查询一个区间的代价是它的 \(\gcd\),显然区间越长越省钱。然后呢,方程组能把 \(b_i\) 求出来的条件是不存在 \(j\neq i\) 使得 \(b_i\) 和 \(b_j\) 在所有方程中的系数都相同,再加上每个方程出现的都是一段区间里的 \(b_i\)……
你想到了“前后缀”与“作差”。
如果我对于每个 \(i\) 我都去求它一段前缀(或者后缀)的和,最后就肯定能作差解出所有 \(b_i\)。
最简单的一种构造是,对于每个 \(i\) 都询问 \([1,i]\) 的和。但是这太蠢了。
你发现,按照上面那种策略,你必定会问到 \([1,n]\) 的和。在这种情况下,对于其它询问,你问 \([1,i]\) 等效于问 \((i+1,n]\)。所以你选那个代价小的问即可。
证明这个方案最优也应该是容易的,你考虑对于其它的构造,把它的询问区间扩展到边界并不劣,差不多就是这样。
:静态讨论完了,修改呢?
:上线段树。
另外,\(\gcd\) 随区间扩展而单调不增的性质确保了这题可以简单带修,因为这等价于存在 \(p\) 满足:\(i\in [1,p)\) 询问 \((i,n]\) 更优,\(i\in [p,n]\) 询问 \([1,i]\) 更优。
所以你最后要做的就是线段树维护 \(\gcd\),然后写个线段树二分。
额等下单点修之后答案怎么实时维护?
经典结论:对于一组不断延长的缀,\(\gcd\) 只有 \(\log V\) 种可能取值。所以就是双 \(\log\) 一种一种取值地直接做。听起来很屎,但就是这样。
(其实如果你把 \(\gcd\) 的 \(\log\) 也算上,这题就是三老葛。但它就是跑过去了。因为 \(\texttt{CPP}\) 高版本的 \(\gcd\) 函数实现非常好)
代码实现
写起来也确实是一坨。代码量不多,但是一个事实就是如果两只 \(\log\) 在极短的代码片段里同时出现,这确实有可能关联着一个让人要多想一会(或者亿会)的实现。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5;
int N,Q,A[MaxN],X,Y,bkp;
struct SegTree{
int t[MaxN<<2];
int ls(int u){return u<<1;}
int rs(int u){return (u<<1)|1;}
void pushup(int u){t[u]=gcd(t[ls(u)],t[rs(u)]);}
void build(int u,int cl,int cr){
if(cl==cr){t[u]=A[cl];return;}int cmid=(cl+cr)>>1;
build(ls(u),cl,cmid),build(rs(u),cmid+1,cr),pushup(u);
}
void update(int u,int cl,int cr,int dd,int x){
if(cl==cr){t[u]=x;return;}int cmid=(cl+cr)>>1;
dd<=cmid?update(ls(u),cl,cmid,dd,x):update(rs(u),cmid+1,cr,dd,x);
pushup(u);
}
int binser(int u,int cl,int cr,int lg,int rg){
if(cl==cr)return cl;int cmid=(cl+cr)>>1;
int lp=gcd(lg,t[ls(u)]),rp=gcd(rg,t[rs(u)]);
return lp<=rp?binser(ls(u),cl,cmid,lg,rp):binser(rs(u),cmid+1,cr,lp,rg);
}
lolo qlcalc(int u,int cl,int cr,int dd,lolo pg){
if(cl==cr)return gcd(t[u],pg);int cmid=(cl+cr)>>1;
lolo lg=gcd(pg,t[ls(u)]),rg=gcd(lg,t[rs(u)]),res=0;
if(dd>cmid)return qlcalc(rs(u),cmid+1,cr,dd,lg);
res+=(lg==pg?(cmid-(dd>cl?dd:cl)+1)*lg:qlcalc(ls(u),cl,cmid,dd,pg));
res+=(rg==lg?(cr-cmid)*lg:qlcalc(rs(u),cmid+1,cr,dd,lg));
return res;
}
lolo qrcalc(int u,int cl,int cr,int dd,lolo pg){
if(cl==cr)return gcd(t[u],pg);int cmid=(cl+cr)>>1;
lolo rg=gcd(pg,t[rs(u)]),lg=gcd(rg,t[ls(u)]),res=0;
if(dd<=cmid)return qrcalc(ls(u),cl,cmid,dd,rg);
res+=(rg==pg?((dd<cr?dd:cr)-cmid)*rg:qrcalc(rs(u),cmid+1,cr,dd,pg));
res+=(lg==rg?(cmid-cl+1)*rg:qrcalc(ls(u),cl,cmid,dd,rg));
return res;
}
}SgT;
lolo solve(int p){return SgT.qlcalc(1,1,N,p,0)+SgT.qrcalc(1,1,N,p,0)-SgT.t[1];}
int main(){
readis(N,Q);
for(int i=1;i<=N;i++)readi(A[i]);
SgT.build(1,1,N);bkp=SgT.binser(1,1,N,0,0);
writil(solve(bkp));
for(int i=1;i<=Q;i++){
readis(X,Y);SgT.update(1,1,N,X,Y);
bkp=SgT.binser(1,1,N,0,0);writil(solve(bkp));
}
return 0;
}
后记
洛谷现有的两篇题解(以及,后来又加入的一篇)为什么都要把这个问题从最小生成树那转化过来?这个思路很必然或者自然吗?我不知道。不过最后代码都差不多就是了。
后记的后记
方格染色的升紫被驳了,这题的升紫反而过了。锣鼓管理员的判断标准我也不得而知。
浙公网安备 33010602011771号