HDU 2609 How many(最小表示法)

题目链接:HDU 2609 How many

题目大意:

有n个有01组成的字符串,每个字符串都代表一个项链,那么该字符串就是一个环状的结构,求可以经过循环旋转,最后不同的串有多少个。

算法思想:

将每个字符串转换成最小串,然后放在set里面去重。

最小表示法:

循环字符串的最小表示法的问题可以这样描述:

对于一个字符串S,求S的循环的同构字符串S’中字典序最小的一个。

由于语言能力有限,还是用实际例子来解释比较容易:

设S=bcad,且S’是S的循环同构的串。S’可以是bcad或者cadb,adbc,dbca。而且最小表示的S’是adbc。

对于字符串循环同构的最小表示法,其问题实质是求S串的一个位置,从这个位置开始循环输出S,得到的S’字典序最小。

一种朴素的方法是设计i,j两个指针。其中i指向最小表示的位置,j作为比较指针。

令i=0,j=1

如果S[i] > S[j] i=j, j=i+1

如果S[i] < S[j] j++

如果S[i]==S[j] 设指针k,分别从i和j位置向下比较,直到S[i] != S[j]
         如果S[i+k] > S[j+k] i=j,j=i+1

         否则j++
返回i


实际上,如果S[i+k] > S[j+k] ,那么S[i + k]就不可能是最小表示的下标,所以此时直接令i = i + k +1;


优化后的算法,也就是说最小表示法,实际上是用 i, j两个指针去找最小的位置。

1)  利用两个指针i, j。初始化时i指向0, j指向1。

(2)  k = 0开始,检验s[i+k] 与 s[j+k] 对应的字符是否相等,如果相等则k++,一直下去,直到找到第一个不同,(若k试了一个字符串的长度也没找到不同,则那个位置就是最小表示位置,算法终止并返回)。则该过程中,s[i+k] 与 s[j+k]的大小关系,有三种情况:

  证明的时候假设(i<j)的,无伤大雅 ;

     (A). s[i+k] > s[j+k],则i滑动到i+k+1处 --- 即s1[i->i+k]不会是该循环字符串的“最小表示”的前缀。

    (B). s[i+k] < s[j+k],则j滑动到 j+k+1处,原因同上。

     (C). s[i+k] = s[j+k],则 k++; if (k == len) 返回结果。 

     注:这里滑动方式有个小细节,若滑动后i == j,将正在变化的那个指针再+1。直到p1、p2把整个字符串都检验完毕,返回两者中小于 len 的值。

    (4)   进一步的优化,例如:i要移到i+k+1时,如果i+k+1 <= p2的话,可以直接把i移到 j+1,因为,j到j+k已经检验过了该前缀比以i到i+k之间任何一个位前缀都小;j时的类似,移动到i+1。


第4步暂时还没有看懂,先给出模板吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
int len;
const int maxn = 10000 + 10;
char s[maxn];
char t[maxn];
set<string>st;

int minRepresentation(char *s){
	int i=0,j=1,k=0;
	while(i<len && j<len && k<len){
		int tmp = s[(i + k)%len] - s[(j + k)%len];
		if(tmp == 0) k++; //相等
		else{
			if(tmp > 0)
				i += k+1; // i = i + k + 1;
			else
				j += k+1;
			if(i == j) j++;
			k = 0;
		}
	}
	return min(i,j);
}
void getSet(char *s){
	s[len/2] = '\0';
	st.insert(s);
}
int main(){
	int n;
	while(scanf("%d",&n)!=EOF ){
		st.clear();
		for(int i=0;i<n;i++){
			scanf("%s",t);
			strcpy(s,t); //因为最后还要获得最小表示的串,所以这里将两个串合并,最后在len/2的位置处加上'\0'即可
			strcat(s,t);
			len = strlen(s);
			int k = minRepresentation(s);
			getSet(s+k);
		}
		cout<<st.size()<<endl;
	}
	return 0;
}






转载于:https://www.cnblogs.com/chaiwenjun000/p/5321124.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值