2023ICPC济南站:B. Graph Partitioning 2

题意问你有多少种删边的方法使得树剩下的连通块大小为k或者k+1。

这个题我不太会,但是我凭感觉打了一个暴力,居然也能AC。

不妨设1为根节点,如果某个节点连向父亲的边被删除,那么就可以根据 s i z [ x ] siz[x] siz[x]来判断子树里面分出了多少个 k k k k + 1 k+1 k+1

因为我们要考虑如果当前 x x x连向父亲的边被删除以后的方案数,然后我们记录一个点的子树里面删掉 t t t个点的方案数,这样只需要判断是否有 s i z [ x ] − k siz[x]-k siz[x]k或者 s i z [ x ] − k − 1 siz[x]-k-1 siz[x]k1就可以知道 x x x连向父亲的边可不可以被删除。

这个记录只需要记录删除个数大于等于 s i z [ x ] − k − 1 siz[x]-k-1 siz[x]k1的个数即可,直觉告诉我这个数量不是很多,所以直接把每个节点的儿子用背包合并即可。

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define dwn(i,x,y) for(int i=x;i>=y;i--)
#define ll long long
using namespace std;
template<typename T>inline void qr(T &x){
    x=0;int f=0;char s=getchar();
    while(!isdigit(s))f|=s=='-',s=getchar();
    while(isdigit(s))x=x*10+s-48,s=getchar();
    x=f?-x:x;
}
int cc=0,buf[31];
template<typename T>inline void qw(T x){
    if(x<0)putchar('-'),x=-x;
    do{buf[++cc]=int(x%10);x/=10;}while(x);
    while(cc)putchar(buf[cc--]+'0');
}
const int N=1e5+10,mod=998244353;
int n,k,siz[N];
vector<int>e[N];
struct node{
    int val,num;
    node(int xx=0,int yy=0):val(xx),num(yy){}
};
vector<node>dp[N];
bool cmp(node p1,node p2){return p1.val<p2.val;}
int tp;node sta[N*10];
void dfs(int x,int fa){
    for(int y:e[x]){
        if(y==fa)continue;
        dfs(y,x);
    }
    siz[x]=1;
    dp[x].push_back(node(0,1));
    for(int y:e[x]){
        if(y==fa)continue;
        siz[x]+=siz[y];
        tp=0;
        for(node p1:dp[x])for(node p2:dp[y]){
            if(p1.val+p2.val>=siz[x]-k-1){
                sta[++tp]=node(p1.val+p2.val,1ll*p1.num*p2.num%mod);
            }
        }
        sort(sta+1,sta+tp+1,cmp);
        int t=min(1,tp);
        rep(i,2,tp){
            if(sta[t].val==sta[i].val){
                sta[t].num=(sta[t].num+sta[i].num)%mod;
            }
            else{
                sta[++t]=sta[i];
            }
        }
        vector<node>().swap(dp[x]);
        vector<node>().swap(dp[y]);
        rep(i,1,t)dp[x].push_back(sta[i]);
    }
    int ans=0;
    for(node p:dp[x])if(p.val==siz[x]-k||p.val==siz[x]-k-1)ans=(ans+p.num)%mod;
    if(ans)dp[x].push_back(node(siz[x],ans));
}
void solve(){
    qr(n),qr(k);
    rep(i,1,n){
        e[i].clear();
        dp[i].clear();
    }
    rep(i,1,n-1){
        int x,y;qr(x),qr(y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs(1,0);
    int ans=0;
    for(node p:dp[1])if(p.val==n)ans=(ans+p.num)%mod;
    cout<<ans<<endl;
}
int main(){
    int tt;qr(tt);
    while(tt--)solve();
    return 0;
}

后面题解是根号分治,主要是我一开始不太明白为什么

t=sqrt(n);
for(int y:e[x]){
	rep(i,1,min(t,siz[x]))
		rep(j,1,min(t,siz[y]))
			dp[]...
}

这样不会超时,后面才知道这样的复杂度是 O ( n t ) O(nt) O(nt)的。

考虑树的dfs序,每次操作就是把两个区间合并, 那么我们每次合并就把交界处两边的t个点互相连边,那么任何两个点之间最多1条边,如果两个点距离超过2t,就不会再连边,因此连边的总数是 O ( n t ) O(nt) O(nt)的。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值