題目分享H 二代目

lin4xu發表於2020-05-04

題意:有m個限制,每個限制l1,r1,l2,r2四個數,限制了一個長度為n的數第l1到r1位要與第l2到r2相同,保證r1-l1=r2-l2,求在限制下一共有多少種數

分析:

暴力的話肯定是從l1-r1掃一遍用並查集,但顯然時間和空間都是不允許的

但再一想,這是不是相當於區間並?操作

看到區間的東西,我直接就往線段樹去想了

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;

#define pii pair<int,int>
#define ll long long

const int mod=1e9+7;

map<pii,pii> fa;
int n;

pii find(pii x)
{
    while(x!=fa[x]) x=fa[x];
    return x;
}

pii ya(pii x)
{
    pii fax=find(x),nowfa;
    while(x!=fax)
    {
        nowfa=fa[x];
        fa[x]=fax;
        x=nowfa;
    }
    return fax;
}

void bing(pii x,pii y)
{
    pii fax=ya(x),fay=ya(y);
    fa[fay]=fax;
}

void down(int l,int r)
{
    int mid=l+r>>1;
    int p=ya(make_pair(l,r)).first-l;
    bing(make_pair(l,mid),make_pair(l+p,mid+p));
    bing(make_pair(mid+1,r),make_pair(mid+1+p,r+p));
}

void dfs(int l,int r,int left,int right,int p)
{
    if(l>=left&&r<=right)
    {
        bing(make_pair(l,r),make_pair(l+p,r+p));
        return;
    }
    down(l,r);
    int mid=l+r>>1;
    if(left<=mid) dfs(l,mid,left,right,p);
    if(mid>right) dfs(mid+1,r,left,right,p);
}

void mem(int l,int r)
{
    fa[make_pair(l,r)]=make_pair(l,r);
    if(l==r) return;
    int mid=l+r>>1;
    mem(l,mid);mem(mid+1,r);
}

void get_ans(int l,int r)
{
    if(l==r) return;
    down(l,r);
    int mid=l+r>>1;
    get_ans(l,mid);get_ans(mid+1,r);
}

ll pow(int x,int y)
{
    ll now=(ll)x,ans=1ll;
    while(y)
    {
        if(y&1) ans*=now,ans%=(ll)mod;
        y>>=1;now*=now,now%=(ll)mod;
    }
    return ans;
}

int main()
{
    int m,l1,r1,l2,r2;
    scanf("%d%d",&n,&m);
    mem(1,n);
    while(m--)
    {
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        dfs(1,n,l1,r1,l2-l1);
    }
    get_ans(1,n);
    int ans=-1;
    for(int i=1;i<=n;i++) if(fa[make_pair(i,i)]==make_pair(i,i)) ans++;
    printf("%d",(int)(pow(10,ans)*9%mod));
    return 0;
}

然鵝,這個pair可能有點不好開,導致全員MLE

不過處理區間的東西,ST表也是有地位的

於是就能往ST表那裡去想

首先ST表傳統藝能肯定是f[x][k]表示x到x+(1<<k)左閉右開怎麼怎麼樣,

那麼這裡就應該表示x到x+(1<<k)左閉右開的父親是誰(因為要搞並查集)

而這裡的父親應該對應另一個y到y+(1<<k)的區間,因為我們的k是已知的,所以其實f[x][k]只需要記錄這個y即可,也就是對應區間的左端點

而這m組將兩個區間並起來的操作時,也應該像lca向上跳一樣,從maxlg往下慢慢找符合的k,然後再講x跳到其對應的x+(1<<k)位置上

這裡有一點值得注意,其實我們這裡也可以按照平日ST表那樣只將l到l+(1<<k)與 r-(1<<k)+1,r+1這兩個區間與對應的區間合併(這裡k應該是小於等於r-l的最大的k)

但我認為這樣不如像lca那樣快,如果有和我意見不相同的可以討論一波

但你肯定馬上就會想,這隻合併兩個,而lca合併很多個區間,但這些合併其實都要下放的

為了方便統計,最後要乾的是將所有的結果下傳,達到每個單個的位置對應一個位置的效果,

這又類似線段樹了,下傳標記

 

 這應該很好理解,ya()後得到的是f[x][y]所對應的根節點區間的左端點,這裡bing傳的三個引數分別為l1,l2,k,也就是把l1到l1+(1<<k)左閉右開與l2到l2+(1<<k)左閉右開合併

最後得到每個單個位置對應的位置,只需要數數有多少個f[x][0]=x即可(f[x][0]=x表示這個x是這個並查集的根節點)

對了,還有一個小問題,你統計出來的這個值是指能自由取數的個數,但顯然這其中一定有一個位置與首位相關,這個位置只能取1-9,別的位置則能取0-9

所以最後結果應該是9*10^(count-1)  

程式碼:

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;

#define ll long long

const int mod=1e9+7;
const int maxn=1e5+1;
const int maxlg=2e1+1;

int fa[maxn][maxlg];
int n;

int find(int x,int y)
{
    while(x!=fa[x][y]) x=fa[x][y];
    return x;
}

int ya(int x,int y)
{
    int fax=find(x,y),nowfa;
    while(x!=fax)
    {
        nowfa=fa[x][y];
        fa[x][y]=fax;
        x=nowfa;
    }
    return fax;
}

void bing(int x1,int x2,int y)
{
    int fax=ya(x1,y),fay=ya(x2,y);
    fa[fay][y]=fax;
}

void down(int x,int y)
{
    int nowx=ya(x,y);
    bing(x,nowx,y-1);
    bing(x+(1<<y-1),nowx+(1<<y-1),y-1);
}

void get_ans()
{
    for(int i=maxlg-1;i;i--) for(int j=1;j<=n;j++) if(j+(1<<i)<=n+1) down(j,i);
}

void mem()
{
    for(int i=0;i<maxlg;i++) for(int j=1;j<=n;j++) if(j+(1<<i)<=n+1) fa[j][i]=j;
}

ll pow(int x,int y)
{
    ll now=(ll)x,ans=1ll;
    while(y)
    {
        if(y&1) ans*=now,ans%=(ll)mod;
        y>>=1;now*=now,now%=(ll)mod;
    }
    return ans;
}

int main()
{
    int m,l1,r1,l2,r2;
    scanf("%d%d",&n,&m);
    mem();
    while(m--)
    {
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        int now=r1-l1+1;
        for(int i=maxlg-1;i>=0;i--)
        {
            if((1<<i)<=now)
            {
                bing(l1,l2,i);now-=(1<<i);
                l1+=(1<<i);l2+=(1<<i);
            }
        }
    }
    get_ans();
    int ans=-1;
    for(int i=1;i<=n;i++) if(fa[i][0]==i) ans++;
    printf("%d",(int)(pow(10,ans)*9%mod));
    return 0;
}