20250723 NOIP 模拟赛
20250723 NOIP 模拟赛
Problem B. 最小生成树
Description
给定一张含 \(n\) 个点 \(m\) 条边的无向图 \(G\),边权均为 \(1\),现在给定 \(k\) 个特殊点,在这些特殊点上构造完全图 \(G'\),\(i,j\) 间的边权为 \(G\) 上 \(i,j\) 间的最短路长度。求 \(G'\) 最小生成树边权和。
\(n,m\leq 2\times 10^5\),\(2\leq k\leq n\)。
Solution
先考虑链,那么 MST 一定是将这些特殊点按链上顺序串起来,其余的边是没用的。
这启示我们,\(i,j\) 若隔得太远,\((i,j)\) 这条边就没有用。考虑如何量化 ”隔得太远“。
以每一个特殊点为起点,一圈圈同步向外扩展。若 \(i,j\) 没有碰面,则 $(i,j) $ 没有用。
考虑三个点 \(i,j,k\),\(i,j\) 与 \(j,k\) 先碰面,而 \(i,k\) 没有碰面。那么假如 $(i,k) $ 在 MST 中,我们割掉它,加入 \((j,k)\) 或者 \((i,j)\) 一定更优。
于是有用的边只有 \(O(m)\) 条,时间复杂度 \(O(m\log m)\)。
int n,m;
int head[N],tot;
char a[N];
struct Edge{
int to,nxt;
}edge[N<<1];
struct Edge2{
int u,v,w;
bool operator<(const Edge2& tmp)const{
return w<tmp.w;
}
};
vector<Edge2> e;
void Add(int u,int v){
edge[++tot]={v,head[u]};
head[u]=tot;
}
int fa[N],vis[N];
int Find(int x){
return (fa[x]==x)?(x):(fa[x]=Find(fa[x]));
}
void Merge(int u,int v){
u=Find(u),v=Find(v);
if(u==v) return;
fa[u]=v;
}
void Clear(){
for(int i=1;i<=n;i++) head[i]=0;
tot=0;
e.clear();
}
void Solve(){
read(n),read(m);
scanf("%s",a+1);
Clear();
for(int i=1;i<=m;i++){
int u,v;
read(u),read(v);
Add(u,v),Add(v,u);
}
vector<int> s,t;
for(int i=1;i<=n;i++){
vis[i]=0,fa[i]=i;
if(a[i]=='1') s.push_back(i),vis[i]=1;
}
for(int w=1;w<=n;w++){
if(s.empty()) break;
for(int x:s){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(!vis[y]){
t.push_back(y);
vis[y]=2;
Merge(y,x);
}
else if(vis[y]==1){
int u=Find(x),v=Find(y);
e.push_back(Edge2{u,v,2*w-1});
}
else{
int u=Find(x),v=Find(y);
e.push_back({Edge2{u,v,2*w}});
}
}
}
for(int x:t) vis[x]=1;
s=t; t.clear();
}
sort(e.begin(),e.end());
for(int i=1;i<=n;i++) fa[i]=i;
ll ans=0;
for(Edge2 i:e){
int u=i.u,v=i.v,w=i.w;
u=Find(u),v=Find(v);
if(u!=v) Merge(u,v),ans+=w;
}
printf("%lld\n",ans);
}
signed main(){
int T; read(T);
while(T--) Solve();
return 0;
}
Problem C. 数据结构
Description
给定序列 \(a\),需要支持以下操作:
- 区间覆盖为同一个值;
- 区间对 \(x\) 取 \(\gcd\),然后输出区间和。
\(n,q\leq 2\times 10^5\)。值域 \([0,10^9]\)。
Solution
若没有第 1 种操作,就是简单的势能分析线段树。
维护区间 \({\rm lcm}\)。若区间 \({\rm lcm}\mid x\),则说明区间内所有数都是 \(x\) 的因子,不需要操作,返回。否则暴力向下递归,到叶子结束。
记势能 \(\phi(i)\) 为 \(i\) 时刻 \(\sum (\log a_i\log n)\)。设 \(k\) 位置对应的叶子被修改,那么 \(a_k\) 至少除以 \(2\),于是势能至少减少 \(\log n\),与定位叶子花费的 \(O(\log n)\) 的代价抵消。于是总复杂度 \(O(q\log n\log V)\)。
加上第 1 种操作。上面做法便不适用了,因为一次覆盖操作可以一下子增加很多势能,无法接受。
区间覆盖的性质很好,因为若一段区间内 \(a_i\) 都相同,那么操作 2 中我们可以直接在区间上打标记,不需要向下递归了。
维护区间 \({\rm lcm,max,min}\)。若区间 \({\rm lcm}\mid x\),直接返回;若区间 \({\rm max=min}\),打标记返回;否则暴力向下递归。
记势能 \(\phi(i)\) 为 \(i\) 时刻线段树中区间内全相等,但父亲节点不全相等的 \(\log v\log n\) 之和。
对于操作 1,最坏使得 $O(\log n) $ 个节点的势能都增加 \(O(\log V\log n)\);
对于操作 2,考虑一个被打标记的节点,其 \(v\) 至少除以 \(2\),势能减少 $O(\log n ) $,与定位区间代价抵消;对于两边的 \(O(\log n)\) 个节点,势能最多增加 \(O(\log V\log n)\)。
最终复杂度为 \(O(q\log^2n\log V)\),显然跑不满,可以通过。
int n,Q;
int a[N];
struct SegNode{
ll lcm,sum;
int cov,mx,mn;
bool tag;
}tr[N<<2];
ll LCM(ll x,ll y){
return (ll)min((__int128)x/__gcd(x,y)*(__int128)y,(__int128)LINF);
}
void Pushup(int p){
tr[p].lcm=LCM(tr[p<<1].lcm,tr[p<<1|1].lcm);
tr[p].mx=max(tr[p<<1].mx,tr[p<<1|1].mx);
tr[p].mn=min(tr[p<<1].mn,tr[p<<1|1].mn);
tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
}
void WorkCov(int p,int v,int l,int r){
tr[p].lcm=tr[p].cov=tr[p].mx=tr[p].mn=v;
tr[p].sum=1ll*(r-l+1)*v;
tr[p].tag=1;
}
void Spread(int p,int l,int r){
if(!tr[p].tag) return;
int mid=(l+r)>>1;
WorkCov(p<<1,tr[p].cov,l,mid);
WorkCov(p<<1|1,tr[p].cov,mid+1,r);
tr[p].cov=0,tr[p].tag=0;
}
void Buildtr(int p,int l,int r){
if(l==r){
tr[p].lcm=tr[p].sum=tr[p].mx=tr[p].mn=a[l];
return;
}
int mid=(l+r)>>1;
Buildtr(p<<1,l,mid),Buildtr(p<<1|1,mid+1,r);
Pushup(p);
}
void Cover(int p,int l,int r,int L,int R,int v){
if(L<=l&&r<=R) return WorkCov(p,v,l,r);
int mid=(l+r)>>1; Spread(p,l,r);
if(L<=mid) Cover(p<<1,l,mid,L,R,v);
if(R>mid) Cover(p<<1|1,mid+1,r,L,R,v);
Pushup(p);
}
ll ans;
void GCDUpdate(int p,int l,int r,int L,int R,int v){
if(L<=l&&r<=R){
if(v%tr[p].lcm==0) return ans+=tr[p].sum,void();
if(tr[p].mx==tr[p].mn){
int w=__gcd(tr[p].mx,v);
ans+=1ll*(r-l+1)*w;
return WorkCov(p,w,l,r);
}
}
int mid=(l+r)>>1; Spread(p,l,r);
if(L<=mid) GCDUpdate(p<<1,l,mid,L,R,v);
if(R>mid) GCDUpdate(p<<1|1,mid+1,r,L,R,v);
Pushup(p);
}
signed main(){
read(n),read(Q);
for(int i=1;i<=n;i++) read(a[i]);
Buildtr(1,1,n);
while(Q--){
int op,l,r,v;
read(op),read(l),read(r),read(v);
if(op==1) Cover(1,1,n,l,r,v);
else{
ans=0;
GCDUpdate(1,1,n,l,r,v);
printf("%lld\n",ans);
}
}
return 0;
}

浙公网安备 33010602011771号