zoukankan      html  css  js  c++  java
  • 暑假D14 T2 monokuma(并查集+ST表)[SCOI2016]萌萌哒

    题目描述

    求一个长为n的数(不含前导零),使得它满足m个限制,每个限制为[l,r]位和[L,R]位对应相同。

    对于100%的数据,1≤n,m≤100000,1≤Li≤Ri≤n,1≤li≤ri≤n,且保证Ri-Li=ri-li。

    题解

    可以想到如果给出两个区间,就可以在对应的数之间连一条边,只要一个确定了,与他连边的就确定了,最后看有多少联通块就相当于我们需要确定多少位置。

    这不就是并查集的操作吗,不过显然暴力合并对应点会爆炸。

    所以来了一种奇妙的做法,ST表+并查集。

    首先按照ST表的套路划分出nlgn个块,把每个块看成一个点,并给出编号id[i][j]表示区间为i到i+2j-1的块。

    在给出限制时,把区间分成按二进制分成小区间,因为两个区间长度相同,所以拆分相同。

    对于每个拆出来的小区间进行合并。

    最后再枚举每个块,如果他与其他块进行了合并,那么就将他的两个小区间也对应去合并。

    最后看有多少长度为1的块fa是自己即可。

    简直神仙。。。。。

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn=1000005;
    const int mod=998244353;
    const ll inv9=443664157;
    const ll inv10=299473306;
    int fa[maxn*20],p[20];
    int id[maxn][20],mp[maxn*20];//mp:编号为i的块开头的位置 
    int n,m,num;
    
    template<class T>inline void read(T &x){
        x=0;char ch=getchar();
        while(!isdigit(ch)) ch=getchar();
        while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    }
    
    int find(int x){
        if(x==fa[x]) return x;
        return fa[x]=find(fa[x]);
    }
    
    ll fpow(ll a,ll b){
        ll ret=1;
        while(b){
            if(b&1) ret=ret*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return ret;
    }
    
    void init(){
        p[0]=1;
        for(int i=1;i<=18;i++) p[i]=(p[i-1]<<1)%mod;
        for(int i=1;i<=n;i++)
         for(int j=0;j<=18&&i+p[j]-1<=n;j++)
          id[i][j]=++num,mp[num]=i;//给每个块一个编号 
        for(int i=1;i<=num;i++) fa[i]=i;
    }
    
    int main(){
        freopen("monokuma.in","r",stdin);
        freopen("monokuma.out","w",stdout);
        read(n);read(m);
        init();
        for(int i=1;i<=m;i++){
            int L,R,l,r;
            read(L);read(R);read(l);read(r);
            if(l==L) continue;
            for(int j=18;~j;j--)//将整个区间拆成小区间,分别合并 
             if(l+p[j]-1<=r){
                 fa[find(id[l][j])]=fa[find(id[L][j])];
                 l+=p[j];
                 L+=p[j];
             }
        }
        for(int j=18;j;j--)
         for(int i=1;i+p[j]-1<=n;i++){
             int dx=find(id[i][j]),dy=mp[dx];//这个块的父亲,和父亲的开头位置 
             if(i==dy) continue;//这个区间没有改变
            fa[find(id[i][j-1])]=fa[find(id[dy][j-1])];
            fa[find(id[i+p[j-1]][j-1])]=fa[find(id[dy+p[j-1]][j-1])];//将两个小区间合并 
         }
        num=0;
        for(int i=1;i<=n;i++)
         if(find(id[i][0])==id[i][0]) num++;
        printf("%lld",inv9*fpow(inv10,num-1)%mod);
    }
    View Code
  • 相关阅读:
    打造分布式爬虫
    vue入门-常用指令操作
    爬虫练习-爬取小说
    爬虫项目-爬取亚马逊商品信息
    爬虫框架_scrapy1
    CIE-LUV是什么颜色特征
    多目标跟踪baseline methods
    时间序列识别代码调试版本1
    拓扑空间1
    ps cs6破解
  • 原文地址:https://www.cnblogs.com/sto324/p/11253027.html
Copyright © 2011-2022 走看看