【学习笔记】[省选联考 2023] 城市建造

我觉得很厉害。因为考场上完全没有想到按子树大小讨论。

首先考虑树的情况。其实也没有那么复杂:我们找到树的重心,那么这个点一定会被删除,否则存在分出来的连通块大小 < n 2 <\frac{n}{2} <2n,又因为重心没有被删掉所以存在连通块大小 > n 2 >\frac{n}{2} >2n,这样连通块大小之差显然就大于 1 1 1了。因此,不妨将重心作为树根。

先考虑 k = 0 k=0 k=0的情况。假设我们已经知道了连通块的大小 t t t,那么我们通过比较子树和 t t t的大小关系就能知道那些点被删除了;应当注意到, t t t应该是子树大小的因数,因此直接暴力跑就做完了。

我们考虑把这个做法搬到圆方树上面去。显然对于一个点双内的点,要么全部删掉,要么删掉的点的数目 ≤ 1 \le 1 1。不妨将问题做一些泛化 :设方点的点权为 0 0 0,原点的点权为 1 1 1,找到树的带权重心,显然在树上的推论仍然成立(这里有一个小小的推导过程:如果是圆点,那么可以看成这个原点向其他相邻的圆点连边,并且满足约束关系,显然这个点还是重心,因此这个点一定会被删掉;如果是方点,那么这个点双一定会被删掉,可以规约到以圆点为根的子树),总之套用树形 d p dp dp即可解决。因此可以做到 O ( n n ) O(n\sqrt{n}) O(nn )

那么 k = 1 k=1 k=1呢?应当注意到,当连通块数目为 x x x时,每个连通块的大小只能是 ⌊ n x ⌋ , ⌈ n x ⌉ \lfloor\frac{n}{x}\rfloor,\lceil\frac{n}{x}\rceil xn,xn两种情况,因此也不超过 n \sqrt{n} n

还是考虑树的情况,因为我们已经枚举了 t t t因此事实上没有那么复杂,将子树大小和 t t t简单比较一下即可。

最后简单容斥一下,可以做到复杂度 O ( n n ) O(n\sqrt{n}) O(nn )

原来代码这么好写,我是纯纯的丝薄。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define inf 0x3f3f3f3f
using namespace std;
const int mod=998244353;
const int N=2e5+5;
int n,m,K;
int dfn[N],low[N],vis[N],num,cnt,in[N];
ll res;
stack<int>s;
vector<int>g[N];
//fixed
vector<int>G[N];
void add(ll &x,ll y){
    x=(x+y)%mod;
}
void addedge(int x,int y){
    G[x].pb(y),G[y].pb(x);
    in[max(x,y)]++;
}
void tarjan(int u){
    dfn[u]=low[u]=++num,vis[u]=1,s.push(u);
    for(auto v:g[u]){
        if(!dfn[v]){
            tarjan(v),low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]){
                cnt++;
                int tmp=0;
                do{
                    tmp=s.top();s.pop();
                    vis[tmp]=0;
                    addedge(tmp,cnt);
                }while(tmp!=v);
                addedge(u,cnt);
            }
        }
        else if(vis[v])low[u]=min(low[u],dfn[v]);
    }
}
int sz[N],root;
void findroot(int u,int topf){
    sz[u]=(u<=n);
    for(auto v:G[u]){
        if(v!=topf){
            findroot(v,u),sz[u]+=sz[v];
        }
    }
    if(!root&&sz[u]>=(n+1)/2)root=u;
}
//fixed
ll dfs(int u,int topf,int X){
    if(u<=n&&sz[u]%X)return 0;
    if(u>n){
        ll res=1;
        for(auto v:G[u]){
            if(v!=topf){
                res=res*dfs(v,u,X)%mod;
            }
            if(res==0)return 0;
        }
        return res;
    }
    ll res=1;int tot=1;
    for(auto v:G[u]){
        if(v!=topf){
            if(sz[v]>=X){
                res=res*dfs(v,u,X)%mod;
            }
            else{
                tot+=sz[v];
            }
            if(res==0||tot>X)return 0;
        }
    }
    if(tot!=X)return 0;
    return res;
}
int vs[N];
ll dfs2(int u,int topf,int X){
    if(u>n){
        ll res=1;
        for(auto v:G[u]){
            if(v!=topf){
                res=res*dfs2(v,u,X)%mod;
            }
            if(res==0)return 0;
        }
        return res;
    }
    ll res=1,ans=0;int tot=1,tot2=0;
    for(auto v:G[u]){
        if(v!=topf){
            if(sz[v]>=X+1){
                res=res*dfs2(v,u,X)%mod;
            }
            else if(sz[v]==X&&in[v]==2)tot2++;
            else tot+=sz[v];
        }
    }
    if(tot>=X&&tot<=X+1){
        add(ans,res);
    }
    if(tot==1){
        add(ans,tot2*res);
    }
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>K,cnt=n;
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        g[x].pb(y),g[y].pb(x);
    }
    tarjan(1);
    findroot(1,0);
    findroot(root,0);
    //fixed
    if(K==0){
        for(int i=1;i<n;i++){
            add(res,dfs(root,0,i));
        }
    }
    //fixed
    else{
        for(int i=2;i<=n;i++){
            vs[n/i]=1;
        }
        for(int i=1;i<n;i++){
            if(vs[i]){
                add(res,dfs2(root,0,i));
                add(res,-dfs(root,0,i+1));
            }
            else{
                add(res,dfs(root,0,i));
            }
        }
    }
    cout<<(res+mod)%mod;
}
posted @ 2023-04-08 10:50  仰望星空的蚂蚁  阅读(24)  评论(0)    收藏  举报  来源