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

  • 相关阅读:
    ESP8266-Arduino编程实例-LSM303 3D加速度计/磁力计驱动
    猿创征文|云原生|minikube的部署安装完全手册
    C++实现一个线程池
    宁波大学计算机考研资料汇总
    【halcon特征点专题系列】01/4--Harris角点检测
    华为认证考试HCIA H12-811 Datacom数通考试真题题库【带答案刷题必过】【第二部分】
    Jaya算法在电力系统最优潮流计算中的应用(创新点)【Matlab代码实现】
    python---进程池与线程池
    Java方法与方法重载
    vue 鼠标划入划出多传一个参数
  • 原文地址:https://blog.csdn.net/zsyzClb/article/details/140462374