树链剖分、线段树、贪心

Solution 1:有显然的树剖加线段树做法,不知道 $O(n \log ^2 n)$ 能不能在时限内跑过 $n=5 \times 10^5$,算一下计算量大概是 $2 \times 10^8$ 等级,三秒是较为宽裕的(真的吗?)。

时间复杂度 $O(n \log^2 n)$。

需要卡常,应用的手法有两倍空间线段树,随机树剖根节点,快读。保留的手段是换用链式前向星建图,毕竟有点麻烦。

#include<bits/stdc++.h>
namespace sun{
    constexpr int RIN=1<<15;
    char quickchar(){
        static char buf[RIN],*be=buf,*ed=buf;
        if(be==ed) be=buf,ed=buf+fread(buf,1,RIN,stdin);
        return be==ed ? EOF:*be++;
    }
    template<typename T=int>
    T read(){
        T x=0;char ch=quickchar();
        while(!isdigit(ch)) ch=quickchar();
        while(isdigit(ch))
            x=x*10+ch-'0',ch=quickchar();
        return x;
    }
}
using namespace std;
using sun::read;
struct Segment{int w,m;}s[1'000'005];
int n,ans,pos,lim,d[500'005],f[500'005],w[500'005],dfn[500'005],eds[500'005],top[500'005],wson[500'005];
vector<int> e[500'005],v[500'005];
void fisdfs(int x,int las){
    w[x]=1,d[x]=d[las]+1,f[x]=las,wson[x]=0;
    for(int i:v[x]) if(i!=las){
        fisdfs(i,x),w[x]+=w[i];
        if(w[i]>w[wson[x]]) wson[x]=i;
    }
}
void secdfs(int x,int las){
    dfn[x]=++pos,top[x]=las;
    if(!wson[x]) return;
    secdfs(wson[x],las);
    for(int i:v[x]) if(i!=wson[x]&&i!=f[x]) secdfs(i,i);
}
void finddep(int x,int las){
    eds[x]=eds[las]+1,lim=max(lim,eds[x]);
    for(int i:v[x]) if(i!=las) finddep(i,x);
}
inline void mark(int u,int l,int r,int mid,int k){
    if(s[u].m+=k) s[u].w=r-l+1;
    else if(l==r) s[u].w=0;
    else s[u].w=s[mid*2].w+s[mid*2+1].w;
}
void update(int u,int l,int r,int ql,int qr,int k){
    int mid=(l+r)>>1;
    if(l>=ql&&r<=qr) return mark(u,l,r,mid,k);
    if(ql<=mid) update(mid*2,l,mid,ql,qr,k);
    if(qr>mid) update(mid*2+1,mid+1,r,ql,qr,k);
    mark(u,l,r,mid,0);
}
void updatepath(int x,int y,int k){
    while(top[x]!=top[y]){
        if(d[top[x]]<d[top[y]]) swap(x,y);
        update(1,1,n,dfn[top[x]],dfn[x],k);
        x=f[top[x]];
    }
    if(d[x]<d[y]) swap(x,y);
    update(1,1,n,dfn[y],dfn[x],k);
}
mt19937 sky(chrono::steady_clock::now().time_since_epoch().count());
void solution(){
    n=read(),ans=0,pos=0,lim=0;
    for(int i=1;i<=n-1;i++){
        int x=read(),y=read();
        v[x].push_back(y);
        v[y].push_back(x);
    }
    int ro=sky()%n+1;
    fisdfs(ro,0),secdfs(ro,ro),finddep(1,0);
    for(int i=1;i<=n;i++) e[eds[i]].push_back(i);
    for(int i=lim;i>=1;i--){
        for(int j:e[i]) updatepath(1,j,1);
        ans=max(ans,s[1].w);
        for(int j:e[i]) updatepath(1,j,-1);
    }
    ans=n-ans;
    cout<<ans<<'\n';
    for(int i=1;i<=n;i++) e[i].clear(),v[i].clear();
    for(int i=1;i<=n*2;i++) s[i].w=0,s[i].m=0;
}
int main(){
    for(int i=read();i;i--) solution();
    return 0;
}

Solution 2:优先考虑保留深度更大的叶子,每次加入深度等于当前指定深度 $d$ 的节点。

具体地,遍历所有深度等于 $d$ 的节点,如果节点未被加入就标记该节点为已经被加入,标记后后跳到该节点的父亲重复操作,如果当前节点是根或者已经被加入则终止加点操作。

维护当前已经被加入的节点数量 $sum$,保留的叶子深度从 $d$ 变为 $d-1$ 时加入所有深度为 $d-1$ 的节点,然后将 $sum$ 减去深度为 $d$ 的节点数量。

每个节点至多被加入一次,时间复杂度 $O(n)$。

#include<bits/stdc++.h>
using namespace std;
vector<int> v[500'005],w[500'005];
int n,ans,cnt,lim,f[500'005];
bool on[500'005];
void dfs(int x,int las,int dep){
    f[x]=las,lim=max(lim,dep),w[dep].push_back(x);
    for(int i:v[x]) if(i!=las) dfs(i,x,dep+1);
}
void solution(){
    cin>>n,ans=0,cnt=0,lim=0;
    for(int i=1;i<=n-1;i++){
        static int x,y;
        cin>>x>>y;
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs(1,0,1);
    for(int i=lim;i>=1;i--){
        cnt-=w[i+1].size();
        for(int j:w[i]) while(j!=0&&!on[j]) ++cnt,on[j]=1,j=f[j];
        ans=max(ans,cnt);
    }
    ans=n-ans;
    cout<<ans<<'\n';
    for(int i=1;i<=n;i++) on[i]=0,v[i].clear(),w[i].clear();
}
int T;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>T;
    while(T--) solution();
    return 0;
}
最后修改:2024 年 11 月 12 日
如果觉得我的文章对你有用,请随意赞赏