题解:P7422 「PMOI-2」城市

本文同步自洛谷专栏,题目传送门

思路

初步观察

对于 \(c\le 10^9\),并发现具体答案与原材料种类(下文称颜色)无关,离散化。

对于必经之路,容易联想到割点。

分析

用 tarjan 求点双建出圆方树,一个点双的为靠近 \(1\) 的圆点。现对圆点方点分别考虑如何计数。

此时 \(f(A,k)\) 的第一条限制表明,\(B_i\) 都必须是是 \(A\) 的子树内颜色不同的结点。

第二条限制中,好心的出题人加粗了任意简单路径,则说明 \(B_i\) 两两之间的 LCA 都必须是圆点。如果是方点,则在该点双内可以找到对应的路径违反限制二。

简证:

如果 \(B_i,B_j\) 的 LCA 为方点 \(X\),若 \(X\) 所代表的点双的根不为 \(A\) 显然不成立。

若为 \(A\),则设点双 \(X\) 中离 \(B_i,B_j\) 最近的分别为点 \(Y,Z\)

则根据点双的性质,一定存在一条从 \(Z\) 出发,经过 \(Y\) 到达 \(A\) 的简单路径,否则说明点双内有割点。

有了公共点后,公共边自然是容易找到的。

方点

方点处只需要将子节点的信息合并上来即可,合并如果使用 dsu on tree,时间复杂度将达到 \(O(n\log^2{n})\),不能通过。

如果采用开桶的方式维护,即是可以做到 \(O(n\log{n})\),在圆点处维护难度可能较大,所以推荐使用线段树合并维护。

对颜色开线段树,非叶结点维护左右儿子,叶节点维护出现次数即可,全局再维护一个子树大小(方点不计大小)。

圆点

基于第二条限制,可以推导出每个方点中仅能选择一个。

圆点处需要统计答案,对于 \(K=1\) 的答案,利用子树大小 \(-\) 子树内相同颜色数容易维护。

则在线段树合并过程中,如果没有发现相同颜色,则仅有 \(K=1\) 的情况,已经统计,可以略过。

发现的相同颜色,则存入一个数组中(还要存下合并前的出现次数),合并完所有子节点后统一处理。

对于同一种颜色,问题转化为:有 \(x\) 类物品,每类物品有 \(w_i\) 个,现要选出至多 \(K\) 类物品,每类中恰取一个,计数。

由于 \(K\) 很小,直接令 \(dp_{i,j}\) 表示考虑了前 \(i\) 类物品,恰选了 \(j\) 类的方案数,边界 \(dp_{0,0}=1\),转移是简单的,可以见代码。

注意到可以使用滚动数组优化。

dfs 退出前,将自身的颜色插入线段树即可。

复杂度分析

线段树合并空间复杂度 \(O(n\log{n})\),此部分时间复杂度 \(O(n\log{n})\)

每个圆点处理 \(K=1\) 和插入操作,单次 \(O(\log{n})\)

每次合并两个相同颜色,需要 \(O(K)\) 的 dp,此部分复杂度 \(O(nK)\)

总的时间复杂度即为 \(O(n(K+\log{n}))\),足以通过。

代码实现

一些细节写在注释里了。

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,IO,stdin)),*rp1++)
#define N 500005
#define mod 998244353
#define ls(a) tr[a].lson
#define rs(a) tr[a].rson
#define mid ((l+r)>>1)

struct edge{
    int v,nxt;
}E[N*6];

struct node{
    int lson,rson;
}tr[N*20];

const int IO=1<<22;
char buf[IO+2],*rp1,*rp2;
int n,m,K,scc,esum,cnt,tot,TOP;
int head[N<<1],fa[N<<1],dfn[N],low[N];
int C[N],stk[N],rt[N<<1],siz[N<<1],dp[25];
vector<int>G[N];//待处理序列

inline int read(){
    int a=0,c=gc();
    while(!isdigit(c)) c=gc();
    while(isdigit(c)) a=10*a+c-'0',c=gc();
    return a;
}

inline void inc(int &a,int b){a+=b,(a>=mod)&&(a-=mod);}
inline void cmin(int &a,int b){(a>b)&&(a=b);}
void dfs1(int u);

void tarjan(int u,int pre){
    dfn[u]=low[u]=++tot,stk[++TOP]=u;
    for(int i=head[u],v;i;i=E[i].nxt){
        if((v=E[i].v)==pre) continue;
        if(dfn[v]){cmin(low[u],dfn[v]);continue;}
        tarjan(v,u),cmin(low[u],low[v]);
        if(low[v]<dfn[u]) continue;
        scc++,fa[scc]=u;int H;
        do{
            E[++esum]={H=stk[TOP--],head[scc]};
            head[scc]=esum;
        }while(H!=v);
    }
}

int merge(int u,int v,int l,int r){
    if(!u||!v) return u|v;
    if(l==r){inc(rs(u),rs(v));return u;}
    ls(u)=merge(ls(u),ls(v),l,mid);
    rs(u)=merge(rs(u),rs(v),mid+1,r);
    return u;
}

void dfs2(int u){//考虑方点,暴力合并即可
    for(int i=head[u];i;i=E[i].nxt){
        dfs1(E[i].v),siz[u]+=siz[E[i].v];
        rt[u]=merge(rt[u],rt[E[i].v],1,cnt);
    }
}

int Merge(int u,int v,int l,int r,int tim){//tim 记录某个颜色的修改时间
    if(!u||!v) return u|v;
    if(l==r){//!= 说明本轮第一次
        if(fa[l]!=tim) stk[++TOP]=l,G[l].emplace_back(rs(u));
        G[l].emplace_back(rs(v)),inc(rs(u),rs(v)),fa[l]=tim;
        return u;
    }
    ls(u)=Merge(ls(u),ls(v),l,mid,tim);
    rs(u)=Merge(rs(u),rs(v),mid+1,r,tim);
    return u;
}

void insert(int &p,int l,int r,int q){
    (!p)&&(p=++tot);
    if(l==r){//单独减去单点贡献
        inc(rs(p),1),inc(scc,mod-rs(p));return;
    }
    if(q<=mid) insert(ls(p),l,mid,q);
    else insert(rs(p),mid+1,r,q);
}

void dfs1(int u){//圆点
    siz[u]=1;
    for(int i=head[u];i;i=E[i].nxt){
        dfs2(E[i].v),siz[u]+=siz[E[i].v];
    }
    for(int i=head[u];i;i=E[i].nxt){//merge 不和 dfs2 同时运行,否则出错
        rt[u]=Merge(rt[u],rt[E[i].v],1,cnt,u);
    }
    for(int c;TOP;TOP--){
        c=stk[TOP],dp[0]=1;
        if(c==C[u]){G[c].clear();continue;}
        for(int i=1;i<=K;i++) dp[i]=0; 
        for(auto &v:G[c]){
            for(int i=K-1;i>=0;i--){
                dp[i+1]=(dp[i+1]+1ull*v*dp[i])%mod;
            }
        }//i=0,i=1 不计入
        for(int i=2;i<=K;i++) inc(scc,dp[i]);
        G[c].clear();
    }//利用 siz 统计 i=1
    inc(scc,siz[u]),insert(rt[u],1,cnt,C[u]);
}

int main(){
    n=read(),m=read(),K=read(),scc=n,esum=m<<1|1;
    for(int i=1;i<=n;i++) C[i]=fa[i]=read();
    sort(fa+1,fa+1+n),cnt=unique(fa+1,fa+1+n)-fa-1;
    for(int i=1;i<=n;i++){
        C[i]=lower_bound(fa+1,fa+cnt+1,C[i])-fa;
    }//离散化
    for(int i=1;i<=m;i++){
        int u=read(),v=read();
        E[i<<1]={v,head[u]},head[u]=i<<1;
        E[i<<1|1]={u,head[v]},head[v]=i<<1|1;
    }
    tarjan(1,0),tot=0;
    for(int i=1;i<=n;i++) head[i]=0;
    for(int i=n+1;i<=scc;i++){
        E[++esum]={i,head[fa[i]]};
        head[fa[i]]=esum,fa[i]=0;
    }//scc 改用作为 ans
    scc=0,TOP=0,dfs1(1),printf("%d",scc);
    return 0;
}

posted @ 2026-06-05 11:11  Wxb2010  阅读(4)  评论(0)    收藏  举报