• 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
    #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)的。

  • 相关阅读:
    RocketMQ 发送失败重试机制 解析——图解、源码级解析
    Linux C语言:多级指针(void指针和const)
    基于SSM技术的oa办公管理系统的设计与实现毕业设计源码100934
    用递归实现字符串逆序(不使用库函数)
    Tcl语言:基础入门(一)
    Nacos 小bug: application.properties配置未生效,导致端口未生效
    vue项目打包,解决静态资源无法加载和路由加载无效(404)问题
    行业案例|世界 500 强险企如何建设指标驱动的经营分析系统
    2023年北京市安全员-C3证证模拟考试题库及北京市安全员-C3证理论考试试题
    dflow入门5——Big step & Big parameter
  • 原文地址:https://blog.csdn.net/zsyzClb/article/details/140462374