題目鏈接 試題 算法訓練 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的括號序列的做法。
- 我們設狀態 f(i, j) 表示已經考慮了前i個數,現在還有j個左括號沒有匹配的方案數.
- 則可以有兩種轉移情況: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;
}