首页 > 代码库 > HDU 3461 Code Lock(并查集的应用+快速幂)

HDU 3461 Code Lock(并查集的应用+快速幂)

* 65536kb,只能开到1.76*10^7大小的数组。而题目的N取到了10^7,我开始做的时候没注意,用了按秩合并,uset+rank达到了2*10^7所以MLE,所以貌似不能用按秩合并。
其实路径压缩也可以不用.............害羞


题目的大意:

    一个密码锁上有编号为1到N的N个字母,每个字母可以取26个小写英文字母中的一个。再给你M个区间[L,M],表示该区间的字母可以一起同步“增加”(从‘a‘变为‘b‘为增1,‘z‘增1为‘a‘)。假如一组密码按照给定的区间进行有限次的“增加”操作后可以变成另一组密码,那么我们认为这两组密码是相同的。该题的目标就是在给定N、M和M个区间的前提下计算有多少种不同的密码。

     根据题意,如果一个可调整的区间都没有的话,答案应该是26的N次方。每当加入一个区间的时候,答案就减少为之前的26分之1(因为该区间的加入使得原本不同的26种情况变得等价了)。因此当有x个“不同的”区间加入进来之后,答案应该为26^(N-x)。

    但是还有一些特殊情况需要考虑,一个是同样的区间重复加入是不会改变答案的,这点比较好理解。另一点是如果一个区间可以由其他若干个区间“拼接”而得到,那么它的加入不能改变答案。例如如果已经有了区间[1,3]和[4,5],那么再加入区间[1,5]答案也不会改变,因为[1,5]所能实现的密码变化全都可以由同步执行[1,3]与[4,5]来实现,也就是[1,3]+[4,5]等价于[1,5]。特别要注意的是[1,3]+[3,5]这种情况并不等价于[1,5]。


如何求区间:

  并查集, merge_set(l-1,r) or  merge_set( l,r+1),这里要好好想一下。通过+1,-1刚好连接上了端点,不是吗?


代码:


#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 1000000007
#define N 10000000+10
using namespace std;

typedef long long int64;
int uset[N];
//int rank[N];
int cnt;


void make_set(int n)
{
    for(int i=0;i<=n;i++)
    {
        uset[i]=i;
        //rank[i]=1;
    }
}

int find_set(int x)
{
    if(uset[x]!=x)
        uset[x]=find_set(uset[x]);
    return uset[x];
}

void merge_set(int x,int y)
{
    int fx=find_set(x);
    int fy=find_set(y);
    if(fx==fy)
        return;
    else
    {
        uset[fx]=fy;
        cnt++;
    }
}

int64 exp(int n)
{
    int64 res=1;
    int64 tmp=26;
    while(n)
    {
        if(n&1)
            res=(res*tmp)%mod;
        tmp=tmp*tmp%mod;
        n>>=1;
    }

    return res;
}

int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        make_set(n);
        cnt=0;
        for(int i=0;i<m;i++)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            merge_set(l-1,r);
        }
        //printf("-->%d\n",n-cnt);
        printf("%I64d\n",exp(n-cnt));
    }

    return 0;

}