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\),需要支持以下操作:

  1. 区间覆盖为同一个值;
  2. 区间对 \(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;
}
posted @ 2025-07-29 18:44  XP3301_Pipi  阅读(14)  评论(0)    收藏  举报
Title