USACO:Broken Necklace

http://ace.delos.com/usacoprob2?a=Y4t2tYgH7TP&S=beads

这一题比较经典,虽然题意很简单就是从某个位置切断,然后向左右不断的搜索相同的珠子,但是解法却有很多,如单纯的暴力搜索,也有比较高效的解法,故而有必要好好对此题进行一个总结归纳。

方法一:暴力搜索。这种方式下时间效率为O(n^2)。可以采用从某个点切断然后从这个点向两边扩展搜,由于是环形,故而采用取模的方式来搜索位置是比较好的办法。每次进行比较大小,不过对于特殊情况的时候需要注意,就是当所有的珠子都是一种颜色的时候,我们需要判断是不是超过了珠子的数目,其余的就没有太大的问题。考虑到是环形的搜索的时候比较麻烦,我们也可以转化为线性的方式进行求解。即对输入的字符串再扩展一倍,这样就不需取模了,直接从某一点向两个方向搜索即可。同样为了解决环的问题,我们有另一种解决办法,即每次都是从字符串的头与尾开始进行搜索,只不过当一次搜索完毕的时候,我们需要将下个要搜索的位置的字符插入到字符串尾部,这样搜索也比较方便。这里我就将第三种方式实现的代码贴上,其余两种不难实现。程序如下:

/*
ID: csuanch1
PROG: beads
LANG: C++
*/

/*
每次将串s的首位移动至末位,每次均能从两头开始搜索,无需考虑环的问题
*/
#include<iostream>
#include<stdio.h>

using namespace std ;

const int maxn = 360 ;
char str[maxn*2] ;

int caculate(int pos) ;

int num ;
int nsum ;

int main()
{
	freopen("beads.in" , "r" , stdin)   ;
	freopen("beads.out" , "w" , stdout) ;
	cin>>num ;
	cin.ignore() ;
	cin.getline(str , sizeof(str)) ;
	
	nsum = 0 ;
	
	for(int i = 0 ; i < num ; i ++)
	{  
		int temp = caculate(i) ;
		
		str[i+num] = str[i] ;
		
		nsum = max(nsum , temp) ;
	}
	
	cout<<nsum<<endl ;
	
	return 0 ;
}

int caculate(int pos)
{
	int  sys1 ;
	char sys2 ;
	
	int i ;
	int temp ;
	
	temp = 1 ;
	
	sys2 = str[pos] ;
	
	i = 1 ;
	
	while(i < num )
	{
		if(sys2 == str[pos+i] || sys2 == 'w' || str[pos + i] == 'w')
		{
			temp ++ ;
			
			if(str[pos + i] != 'w' && sys2 == 'w')
				sys2 = str[pos + i] ;
		} 
		else 
			break ;
			
		i ++ ;
	}
	
	sys1 = i + pos ;
	
	i = num + pos - 1 ;
	
	if(temp == num)
		return temp ;
		
	sys2 = str[i] ;
	
	i = i - 1 ;
	
	temp = temp + 1 ;
	
	while(i >= sys1)
	{
		if(sys2 == str[i] || sys2=='w' || str[i] == 'w')
		{
			temp ++ ;
			if(str[i]!='w' && sys2 == 'w')
				sys2 = str[i] ;
		}
		else
			break ;
		i -- ;
	}
	
	return temp ;
}


方法二:高效的方式。这种算法可以将时间效率提高到O(n)。先说第一种办法,采用一种类似于动规的思想的方式来解决。由于我们每次计算的时候都是从位置i向两边搜索,假设我们已经计算出i两边的值,那么这个问题就很简单了,那么如何求出i两边的值呢?我们采用4个数组来存储,分别表示为br,bl,rr, rl,表示如果珠子是黑色,其右侧连续黑色珠子的个数,如果珠子是黑色的,其左侧连续的黑色珠子个数,红色珠子其右侧连续的红色珠子的个数,红色珠子其左侧连续的红色珠子的个数。如果当前的珠子是黑色的,则以其作为分割点左侧的连续黑色珠子总共的数目就可以表示为bl[i] = bl[i-1] + 1 ,而 rl[i] = 0 ,同样的当为红色珠子的时候就可以表示为:bl[i] = 0 ; rl[i] = rl[i-1] + 1 ,而对于白色珠子其可以转化为黑色也可以转化为红色的,故而, bl[i] = bl[i-1] + 1 ,rl[i] = rl[i-1] + 1。同样可以推出右侧的数值。这样问题就解决了。其最终结果我们就可以表示为:result = max(result , max(rl [i] , bl [i]) + max(rr[i+1] , br [i+1]) )。当然还得注意最后输出的时候要判断特殊情况即。如果珠子颜色全部相同,此时要判断result与珠子数目n的大小关系。程序如下:

/*
ID: csuanch1
PROG: beads
LANG: C++
*/

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>

using namespace std ;
char temp[400] ;
char str[800] ;

int num  ;
int nsum ;

int bl[800] ;
int br[800] ;
int rl[800] ;
int rr[800] ;

int main()
{
	freopen("beads.in" , "r" , stdin)  ;
	freopen("beads.out" ,"w" , stdout) ;
	
	cin>>num ;
	cin.ignore() ;
	cin.getline(temp , sizeof(temp)) ;
	
	strcpy(str , temp) ;
	strcat(str , temp) ;
	
	int i = 0 ;
	bl[i] = rl[i] = 0 ;
	
	for(i = 1 ; i <= 2 * num ; i ++)
	{
		if(str[i - 1] == 'b')
		{
			bl[i] = bl[i - 1] + 1 ;
			rl[i] = 0 ;
		}
		else if(str[i - 1] == 'r')
		{
			bl[i] = 0 ;
			rl[i] = rl[i - 1] + 1 ;
		}
		else
		{
			bl[i] = bl[i - 1] + 1 ;
			rl[i] = rl[i - 1] + 1 ;
		}
	}
	
	br[2*num] = rr[2*num] = 0 ;
	
	for(i = 2 * num - 1 ; i >= 0 ; i --)
	{
		if(str[i] == 'b')
		{
			br[i] = br[i + 1] + 1 ;
			rr[i] = 0 ;
		}
		else if(str[i] == 'r')
		{
			br[i] = 0 ;
			rr[i] = rr[i + 1] + 1 ;
		}
		else 
		{
			br[i] = br[i+1] + 1 ;
			rr[i] = rr[i+1] + 1 ;
		}
	}
	
	nsum = 0 ;
	
	for(i = 2 * num - 1 ; i >= 0 ; i --)
		nsum = max(nsum , max(bl[i], rl[i]) + max(br[i] , rr[i])) ;
		
	cout<<min(nsum , num)<<endl ;	
	return 0 ;
}

 

当然搜索也是可以将效率提高到O(n)的,只不过在搜索的时候我们得注意优化了,不能直接向两边直接搜,我们可以先采用设置两个变量用于表示当前位置的左侧与右侧的数目,但是我们不需要去搜索左侧的数目只需要搜索右侧的数目。理由如下:当切割的点位于红黑交界的时候得到的结果会相对的由于非交界处的情况,例如:对于串rrrrbbbrr,在r和b的交界处进行搜索的结果总是由于连续序列r或者b的值。基于这种情况,我们就可以当一次搜索完成的时候就可以将右侧搜索的结果赋给左侧,同时右侧跳跃到交界处在进行搜索,这样效率就有了提高。但是有一个问题需要注意的就是因为w的存在,我们需要适当的向前回退,然后再向右搜索。这样会有很多因素考虑,需要做好对于特殊情况的充分判断。以前没将代码写下,心里不爽,然后就写了一下,提交过了。代码如下:

/*
ID: csuanch1
PROG: beads
LANG: C++
*/

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<fstream>
#include<string.h>
using namespace std ;

char tmp[400]  ;
char str[800]  ;

int num ;
int nsum ;

int main()
{
	ifstream fin ("beads.in") ;
 	ofstream fout ("beads.out") ;
	
	fin >> num ;
		
	fin.ignore() ;
	
	fin.getline(tmp , sizeof(tmp)) ;
	
	strcpy(str , tmp) ;
	strcat(str , tmp) ;

	int left  ;
	int right ;
	int pos   ;
	int i ; 
	char sys  ;
	
	right = 0 ;
	left = 0  ;
	pos =  0  ;
	nsum = 0 ;
	
	while(pos < num * 2)
	{
		i = 0 ;
		
		sys = str[pos + i] ;
		
		while(pos + i < num * 2)
		{
			if(str[i + pos] == sys || str[i + pos] == 'w' || sys == 'w')
			{
				right ++ ;
				
				if(sys == 'w')
					sys = str[i + pos] ;
			}
			else
				break ;
			i++ ;
		}
		
		nsum = max(nsum , left + right) ;
		
		if(nsum >= num)
			break ;
			
		left = right ;
		right = 0 ;
		pos = i + pos ;
		
		if(pos == 2 * num - 1)
			break ;
			
		//回溯找前几个出现'w'的情况 ,应当注意当回溯后,右到达上次开始搜索的点,
		//即left = 0 ,可以通过left=0来判断,此时说明已经到达搜索完毕,可以直接退出 
		//或者通过pos到达的位置,这个需要在前面先判断,
		while(pos >= 1 && str[pos-1]=='w') 
		{
			left -- ;
			pos -- ;
		}
		//这个与上面的判断二选一即可,不过建议选择上面的判断,减少不必要的回溯判断
		if(left == 0)
			break ;
		
	}
	
	nsum = min(nsum , num) ;
	
	fout<<nsum<<endl ;
	
	return 0 ;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值