HDU 3461 Code Lock(并查集,合并区间,思路太难想了啊)

时间:2022-03-25 03:37:52

完全没思路,题目也没看懂,直接参考大牛们的解法。

http://www.myexception.cn/program/723825.html

题意是说有N个字母组成的密码锁,如[wersdfj],每一位上的字母可以转动,变成字母表中的下一位。 如w可转动变成x,z变成a。但是题目规定,只能同时转动某个区间上的所有字母,如[1,3], 那么第1到第3个的所有字母要同时转动, 那么[wersdfj]经过一次操作就变成[xfssdfj].一共有M个区间是可以操作的。

经过可操作区间进行的操作得到的所有情况,都是同一个的。 也就是指不管我在题目给出的某一个区间怎么转动,其它位上的字母都不变,它仍视为同一种。

假如原本是[abc],给出可操作区间[1,2],即我可以转动第一个、第二个字母,当然是同时转动的。 由于[1,2]区间情况所能变化得到的,都视为同一种。我不妨固定第一个字母,就为a,其余25个字母可有a变化得到。 那么第二位上的字母,就可以有26种可能,也就是[1,2]区间可能的情况有26种,再加上独立的第3个字母有26种可能,总共有26^2种。

也就是说,有n个字母组成的密码锁,原本会有26^n种。如果有一个可操作区间,那么会有26次变换。 相当于这个操作区间就减少了原来的26种,总的也就变为26^(n-1)。 也就是现在我们要统计有多少个可操作区间,若为cnt,则答案即为26^(n-cnt),这当然要用到快速幂。

至于怎么求区间个数,采用的是区间合并的方法,给出一个区间[L,R],若可以合并,则cnt++。

不过这里有一个情况要注意:

假设有区间[1,3],[3,5],[1,5],如果按照Union(L, R)来算, 那么[1,3],[3,5]可以合并,cnt为2, 但[1,5]因为已经属于同一个集合,则不合并。那么得到两种可操作区间。 但实际上是有3中可操作区间的,因为1~3, 3~5是有重叠了3的两个区间,所以1~5区间的情况不包括在前两个区间的情况内。

如果是[1,3],[4,5],[1,5],如果合并的话,cnt为3,但实际就只有两种情况,因为最后一种的转动情况包含在了前两种转动的情况之内了。

这里大牛们就想出了一个解决办法:

合并[L-1,R]或者[L,R+1]。

如上述区间[1,3],[3,5],[1,5],[1,3]合并的是[0,3],[3,5]合并的是[2,5],[1,5]合并的是[0,5],这样cnt=3;

如果是[1,3],[4,5],[1,5],那么cnt=2;

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm> using namespace std; const int maxn=;
const int mod=+;
int father[maxn];
int n,m; void init(){
for(int i=;i<=n;i++)
father[i]=i;
} int find_root(int x){
if(father[x]!=x)
father[x]=find_root(father[x]);
return father[x];
} bool Union(int a,int b){
int x=find_root(a);
int y=find_root(b);
if(x==y)
return false;
father[y]=x;
return true;
} //注意要long long ,即使a(26)也要定义成long long
long long quickPow(long long a,int b){
long long ans=;
while(b>){
if(b&){
ans=(ans*a)%mod;
}
a=a*a%mod;
b=b>>;
}
return ans;
} int main()
{
int l,r;
while(scanf("%d%d",&n,&m)!=EOF){
int cnt=;
init();
for(int i=;i<=m;i++){
scanf("%d%d",&l,&r);
l--;
if(Union(l,r))
cnt++;
}
long long ans=quickPow(,n-cnt);
printf("%I64d\n",ans);
}
return ;
}