试题 算法训练 Sereja and Squares

题目链接 试题 算法训练 Sereja and Squares
参考博客 https://www.luogu.com.cn/problemnew/solution/CF314E
资源限制
时间限制:4.0s 内存限制:256.0MB

问题描述
  Sereja在平面上画了n个点,点i在坐标(i,0)。然后,Sereja给每个点标上了一个小写或大写英文字母。Sereja不喜欢字母"x",所以他不用它标记点。Sereja认为这些点是漂亮的,当且仅当:
  ·所有的点可以被分成若干对,使得每个点恰好属于一一对之中。
  ·在每对点中,横坐标较小的会被标上小写字母,较大的会被标上对应的大写字母。
  ·如果我们在每对点上画一个正方形,其中已知的一对点会作为正方形的相对的顶点,它们间的线段会成为正方形的对角线,那么在所有画出的正方形中不会有相交或触碰的情况。
  小Petya擦掉了一些小写字母和所有大写字母,现在Sereja想知道有多少种方法来还原每个点上的字母,使得还原后这些点是漂亮的。

输入格式
  第一行是一个整数n,表示点的个数。
  第二行是一个长度为n的字符串,包含小写字母和问号"?",是按照横坐标递增的顺序的每个点的描述。问号表示这个点的字母被Petya擦掉了。保证输入串不含字母"x"。

输出格式
  输出答案对4294967296取模的值。如果没有可行的方案,输出0。

样例输入
4
a???

样例输出
50

样例输入
4
abc?

样例输出
0

样例输入
6
abc???

样例输出
1

数据规模和约定
  20个测试点的n分别为:
  5,10,20,50,100,
  200,500,1000,2000,5000,
  10000,20000,30000,40000,50000,
  60000,70000,80000,90000,100000.

解题思路

左括号与右括号的个数都应该是 n / 2.若n为奇数,则无解.

取模:因为对 4294967296 即 2 ^ 32 取模,我们可以用 unsigned int,但不要所有数都开这个东西,常数太大会TLE.

问题转换,我们可以把小写字母看成左括号,大写字母看成右括号。总共有25种(除开x、X)。

首先我们来看n^2的括号序列的做法。

  1. 我们设状态 f(i, j) 表示已经考虑了前i个数,现在还有j个左括号没有匹配的方案数.
  2. 则可以有两种转移情况:1.第 i + 1 个数是左括号,则可以转移为 f[i+1][j+1]. 2.第 i + 1 个数是问号,则可以转移为 f[i+1][j-1]与f[i+1][j+1] .

我们再来看这道题的情况.这与上面传统情况的不同之处是他擦去了所有的右括号以及一部分的左括号.
令 f(i, j) 表示 假设括号只有一种,前 i 个数里面,填了 j 个右括号的方案数.
第i个数是问号,当前位置填左括号 f[i][j]+=f[i-1][j],当前位置填右括号f[i][j]+=f[i-1][j-1]
f[n][n/2]即为答案

利用一种情况的方案数计算多种括号的方案数.

我们开文章一开始就说了,我们有25种括号,且左括号个数是 n / 2 个.

假设序列中已有 p 个左括号,那么 ?(即要填的数的个数) 里面就有 n / 2 - p 个左括号.

每一个左括号有 25 种选择,那么 ans = 25 ^ (n / 2 - q) * f(n, n / 2).

程序代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL unsigned int
using namespace std;
int n,p;
LL f[200005]={1},ans=1;
char c[100005];
int main() {
	cin>>n;
	cin>>c+1;
	if(n&1) cout<<"0"<<endl;
	else {
		int m=n>>1;
		for(int i=1;i<=n;++i){
			if(c[i]=='?') 
				for(int j=i>>1;j>=i-m&&j;j--){
					f[j]+=f[j-1];//统计各种填右括号的方法个数
					//并且把f[i]这一层去掉了,滚动着来的。
				}
			else p++;//计算已经填写的小写字母的个数
		}
		for(int i=1;i<=m-p;i++) {
			ans=ans*25;//每个能够填写小写字母的地方都有25种填法
		}
		ans=(ans*f[m]);//将结果相乘
		cout<<(LL)ans<<endl;
	}
	return 0;
}
  • 10
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值