【学习笔记】[省选联考 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;
}