(Codeforces800Div2)B. Paranoid String(思维/动态规划)

题目链接:Problem - B - Codeforces

样例输入:

5
1
1
2
01
3
100
4
1001
5
11111

 样例输出:

1
3
4
8
5

题意:多组测试,每次给定一个n,代表字符串的长度,然后给定一个长度为n的字符串,字符串中的每个字符为0或者1,我们可以对一个字符串进行操作,每次可以把01变成1或者把10变成0,然后问我们在这个字符串中有多少个子串可以经过若干次操作变为一个数。

举个例子比如100,首先1,0,0三个单独可以作为一个子串,其次100整体可以作为一个子串,因为100->10->0,所以对于100共有4个子串可以经过若干次操作变为一个数,而对于1001的话就有

1,0,0,1,10,01,001,1001这8个子串满足题意

这个题有两种做法,先来说一下比较简单的做法:

假如第i个数字与第i-1个数字不同,那么我们直接把答案+i就行,因为以第i个数字结尾向前扩展任意长度的子串都是满足题意的,为什么会这样呢?给大家举个例子大家应该就明白了:

比如序列是xxxxxxxxxxxx01,我们可以通过若干次操作把前面的x全部变成0,因为假如前面是0那就不用操作,如果前面是1,那我们可以用倒数第二个数0和前面的数组成10然后合成0重复这样的操作就可以使得所有的x变为0,那么就成为了00……01的这种形式,然后就可以直接合并成1

同理假如序列是xxxxxxxxxxxx10,我们可以通过若干次操作把前面的x全部变成1,因为假如前面是1那就不用操作,如果前面是0,那我们可以用倒数第二个数1和前面的数组成01然后合成1重复这样的操作就可以使得所有的x变为1,那么就成为了11……10的这种形式,然后就可以直接合并成0

如果要是当前位和上一位是相同的,因为01->1,10->0所以我们无论怎样操作最后都无法消成一个数,所以只需要把答案+1即可,因为只有把当前这一个字符作为子串才是满足题意的。

代码实现很简单,注意ans开long long:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;
const int N=2e5+10;
char s[N];
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d%s",&n,s+1);
		long long ans=1;
		for(int i=2;i<=n;i++)
		{
			if((s[i]-'0')^(s[i-1]-'0')) ans+=i;
			else ans+=1;
		}
		printf("%lld\n",ans);
	}
	return 0;
} 

下面来说一种动态规划的解决方法,因为一开始没有想到上面的解法,就用动态规划求解的。

设:

f[i][0]代表以第i个数结尾形成一个0的子串数目

f[i][1]代表以第i个数结尾形成一个1的子串数目

f[i][2]代表以第i个数结尾形成连续两个及以上0的子串数目

f[i][3]代表以第i个数结尾形成连续两个及以上1的子串数目

知道了状态表示,状态转移方程就比较容易推导了,下面是分析思路:

假如当前位是0,那么一定有f[i][1]=f[i][3]=0,当前位为0就代表着以当前位作为结尾形成的子串的末尾一定不可能是1,那么f[i][0]=1+f[i-1][1]+f[i-1][3]就是前面的1个1或者若干个1加上当前位的0组成一个0,f[i][2]=f[i-1][2]+f[i-1][0],当前的若干个0是由前i-1位中的若干个0或者1个0来组成。

同理,假如当前位是1,那么一定有f[i][0]=f[i][2]=0,当前位为1就代表着以当前位作为结尾形成的子串的末尾一定不可能是0,那么f[i][1]=1+f[i-1][0]+f[i-1][2]就是前面的1个0或者若干个0加上当前位的1组成一个1,f[i][3]=f[i-1][3]+f[i-1][1],当前的若干个1是由前i-1位中的若干个1或者1个1来组成。

分析到这,状态转移方程就推导出来了,结果就是每一位字符作为结尾的子串经过操作后能够形成一个数的数目和

下面是代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;
const int N=3e5+10;
char s[N];
int f[N][4];//f[i][0/1/2/3]代表以第i个数结尾形成一个0/1/0……0/1……1的方案数
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		int n;
		scanf("%d%s",&n,s+1);
		long long ans=0;
		for(int i=1;i<=n;i++)
		{
			f[i][0]=f[i][1]=f[i][2]=f[i][3]=0;
			if(s[i]=='0')
			{
				f[i][0]=1+f[i-1][1]+f[i-1][3];
				f[i][2]=f[i-1][2]+f[i-1][0];
			}
			else
			{
				f[i][1]=1+f[i-1][0]+f[i-1][2];
				f[i][3]=f[i-1][3]+f[i-1][1];
			}
			ans+=f[i][0]+f[i][1];
		}
		printf("%lld\n",ans);
	} 
	 return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值