Codeforces Round 662 賽后解題報告
夢幻開局到1400+的悲慘故事
A. Rainbow Dash, Fluttershy and Chess Coloring
這個題很簡單,我們可以畫幾張圖,發現每一次我們染色的最佳方法就是每次往里面多填一圈,並把上一圈給填滿。
比如上圖就很好地說明了這個過程,大家可以用畫一下 \(n=4,n=5,n=6,n=7\),就能驗證這個命題了,所以一個 \(n\times n\) 的矩陣有 \(\lfloor\frac{n}{2}\rfloor+1\) 圈,所以直接輸出即可。
//Don't act like a loser.
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
//#pragma GCC optimize("Ofast","-funroll-loops","-fdelete-null-pointer-checks")
//#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
int n;
signed main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
int t=read();
while(t--) {
n=read();
cout<<n/2+1<<endl;
}
//fclose(stdin);
//fclose(stdout);
return 0;
}
B. Applejack and Storages
我們思考一下這個題給我們的信息。我們要用已有木棒拼出一個正方形和一個長方形。因為長方形的范圍比正方形廣,所以我們有一個貪心策略就是先判斷是否有正方形再判斷剩下的是否有長方形。
我們做的就是要統計有多少組木棒可以拼出正方形,我們記為 \(four\)(重復的木棒不能出現在兩組中)。我們再來統計把所有 \(4\) 個木棒長度相等的組剔除后,有多少組是兩個木棒程度相等的,記為 \(two\)。注意,由於我們的貪心策略,我們優先考慮 \(four\)。所以兩組相等的二元組就會被我們合並成為一個四元組。
最后只要滿足 \(four>0\) 且 \(two>1\),或者 \(four>1\),即拼出兩個正方形,我們就輸出 YES
。否則 NO
。
//Don't act like a loser.
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e5+10;
int n,cnt[maxn],m,four,two;
char ch;
signed main() {
n=read();
for(int i=1;i<=n;i++) {
int x=read();
cnt[x]++;
if(cnt[x]%4==0) {//先4后2
four++;
two--;
}
if(cnt[x]%4==2) {
two++;
}
}
m=read();
for(int i=1;i<=m;i++) {
cin>>ch;
int x=read();
if(ch=='+') {
cnt[x]++;
if(cnt[x]%4==0) {//先4后2
four++;
two--;
}
if(cnt[x]%4==2) {
two++;
}
}
else {
if(cnt[x]%4==0) {//先4后2
four--;
two++;
}
if(cnt[x]%4==2) {
two--;
}
cnt[x]--;
}
if(four>0&&two>1) {
printf("Yes\n");
}
else if(four>1) {
printf("YES\n");
}
else {
printf("NO\n");
}
}
return 0;
}
C. Pinkie Pie Eats Patty-cakes
此題的數學方法不再贅述,我們來關注一下冷門的二分答案(至少我沒看到幾個二分答案做的)。
確定做法二分答案,我們的重點就放在了 check
函數上。首先先講講我在考試的時候怎么想的(這個是錯誤想法)。
我們確定了一個答案 \(x\),判斷是否可行,我們一個一個放數字,從出現次數最多的開始放,每隔 \(x\) 個位置就放一個,如果已經到頭了,卻還有剩余,就代表答案不存在,返回 \(0\)。但這是錯的。我們舉個例子,比如對於:
10
4 4 4 4 3 3 2 2 1 1
按照我們的做法,若 \(x=2\),放完了 \(4,3,2\) 后,是這樣的一個序列:\(4\ 3\ 2\ 4\ 3\ 2\ 4\ 0\ 0\ 4\)。其中 \(0\) 代表還未放置的數。明顯,我們會認為它是不可行的。其實我們可以實現,比如這個序列:\(4\ 3\ 2\ 4\ 1\ 2\ 4\ 1\ 3\ 4\)。這個check
直接暴斃。
我們來換一個貪心的思路。我們考慮到第 \(i\) 個位置,若在 \([i-x-1,i]\) 內 \(k\) 這個數還沒有出現過,那么 \(k\) 就成為了 \(i\) 這個位置上的候選人。假設我們有 \(d\) 個候選人 \(k_1,k_2...k_d\)。我們就肯定先選擇剩下還需在序列中出現次數最多的那個數,填在 \(i\) 這個位置上。因為填在這一位上,肯定比在 \(i+q\) 的位置上優。如果在考慮某個位置時,沒有可以選擇的數,即 \(d=0\) 時,肯定是無解的,返回 \(0\)。在計算時用一個堆維護即可。時間復雜度為 \(O(n\log^2 n)\)。可以卡過此題。
//Don't act like a loser.
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1e5+10;
int n,a[maxn],cnt[maxn];
int b[maxn];
bool check(int x) {
fill(b+1,b+n+1,0);
priority_queue<pair<int,int> > q;
for(int i=1;i<=n;i++) {
if(cnt[i])
q.push(make_pair(cnt[i],i));
}
for(int i=1;i<=n;i++) {
if(i>x+1) {
if(cnt[b[i-x-1]])
q.push(make_pair(cnt[b[i-x-1]],b[i-x-1]));//把重新“合法”的這個數加回堆中。
}
if(q.empty()) {
return 0;//此時無解
}
pair<int,int> pr=q.top();
q.pop();
cnt[pr.second]--;//剩余出現次數減1
b[i]=pr.second;
}
return 1;
}
void clear() {
fill(cnt+1,cnt+n+1,0);
for(int i=1;i<=n;i++) {
cnt[a[i]]++;
}
}
signed main() {
int t=read();
while(t--) {
n=read();
for(int i=1;i<=n;i++) {
a[i]=read();
cnt[a[i]]++;
}
int l=0,r=n-2;
while(l+1<r) {
int mid=(l+r)>>1;
clear();//恢復cnt數組
if(check(mid)) {
l=mid;
}
else {
r=mid;
}
}
clear();//恢復cnt數組
if(check(r)) {
printf("%d\n",r);
}
else {
printf("%d\n",l);
}
fill(cnt+1,cnt+n+1,0);
}
return 0;
}
D. Rarity and New Dress
首先我們可以轉化一下問題,我們先令一個滿足衣服圖案的區域的幾何中心為這個衣服的中心,我們再令以 \((x,y)\) 為中心的最大的衣服的半徑為 \(r_{x,y}\)。那么我們的答案就為:
對於半徑我們結合圖再來規定一下。對於下圖(來自題目):
比如對於第一張圖(左一),我們的半徑為 \(1\);中間的那幅,半徑為 \(2\);最右邊的半徑為 \(0\)。
我們來具體分析如何求解。
我們如果把這個圖形分成左右兩半,我們會發現都是一個金字塔的形狀,且從上往下寬度以 \(2\) 為公差遞增。這就是我們解決問題的切入點,分別取統計兩邊的金字塔大小,兩邊取 \(\min\),即為半徑大小。但是怎么求金字塔大小?以左邊為例我們先來求以 \((x,y)\) 為中心的上下方向的線段的最大長度,記為 \(len_{x,y}\)。然后我們再令 \((x,y)\) 左邊的最大金字塔的長度為 \(left_{x,y}\)。則我們有 \(left_{x,y}=\min(\lfloor\frac{len_{x,y}}2\rfloor,left_{x,y-1}+1)\)。右邊同理。這個問題就解決了。
順便提一句:記得用 getchar()
,否則會被卡常。博主親測過得喲,親。
//Don't act like a loser.
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=2000+10;
int n,m,u[maxn][maxn],l[maxn][maxn],r[maxn][maxn],d[maxn][maxn],len[maxn][maxn];
char c[maxn][maxn];
signed main() {
n=read();m=read();
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
c[i][j]=getchar();
}
c[0][0]=getchar();
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(c[i][j]==c[i-1][j]) {
u[i][j]=u[i-1][j]+1;
}
}
}
for(int i=n;i>0;i--) {
for(int j=1;j<=m;j++) {
if(c[i][j]==c[i+1][j]) {
d[i][j]=d[i+1][j]+1;
}
len[i][j]=1+min(d[i][j],u[i][j])*2;
}
}
for(int i=1;i<=n;i++) {
for(int j=0;j<=m;j++) {
if(c[i][j]==c[i][j-1]) {
l[i][j]=min(len[i][j]/2,l[i][j-1]+1);
}
}
}
int ans=0;
for(int i=1;i<=n;i++) {
for(int j=m;j>0;j--) {
if(c[i][j]==c[i][j+1]) {
r[i][j]=min(len[i][j]/2,r[i][j+1]+1);
}
ans+=min(l[i][j],r[i][j])+1;
}
}
cout<<ans<<endl;
return 0;
}
E1. Twilight and Ancient Scroll (easier version)
這個題我們一步一步地來優化時間和空間復雜度。
注意,本題中字符串的下標從 \(0\) 開始
我們令第 \(i\) 個字符串為 \(str_i\),其長度為 \(len_i\)。
時間:\(O(L^3)\),空間:\(O(nL)\)
我們看到這個題很快可以想到是 DP,所以我們先來列個狀態。我們令 \(f_{i,j}\) 為考慮到第 \(i\) 個字符串,這個字符串刪除第 \(j\) 個字符,保證前 \(i\) 個字符串是按字典序排列的方案數是多少。可是我們很快遇到了一個棘手的問題:如果這個字符串我選擇不刪除任何字符怎么辦?這個時候我們可以采取在字符串末尾加一個'#'
符號,如果我們不刪字符,我們就選擇刪這個字符。由於'#'
比26個小寫字母的ASCII編碼小,也自動解決了長度的問題。我們再回過頭看 DP,我們有轉移方程:\(f_{i,j}=\sum\limits_{k=0,滿足字典序要求}^{len_i} f_{i-1,k}\)。我們最后的答案就是 \(\sum\limits_{j=0}^{len_i} f_{n,j}\)。這是最朴素的 DP,還是很好想的。
時間:\(O(L^2\log L)\),空間:\(O(nL)\)
我們來進一步優化這個 DP。我們發現如果我們對第 \(i\) 個字符串把每個位置上的字符刪掉會得到 \(len_i\) 個新字符串。如果我們對這些字符串排序。在 DP 的時候就可以用 \(\operatorname{upperbound}\) 來利用字符串單調性來優化了。但此時我們還需要改一下 \(f_{i,j}\) 的定義。如果我們令排好序后刪除了第 \(i\) 個字符的那個新字符串排名為 \(rnk_i\)。我們再定義一個數組 \(place_i\),意義為排名為 \(i\) 的字符是原字符串中的第 \(palce_i\) 個,所以滿足\(rnk_{palce_i}=i\)。我們的\(f_{i,j}\) 改為考慮到第 \(i\) 個字符串,這個字符串刪除第 \(place_j\) 個字符,保證前 \(i\) 個字符串是按字典序排列的方案數是多少。顯然我們的轉移方程要發生改變,但大體與上面的一樣,這里不再贅述(因為表達比較煩,具體可以看程序)。排序 \(O(L^2\log L)\),DP時間復雜度 \(O(L^2\log L)\)。空間復雜度還是為 \(O(nL)\)。
時間:\(O(L\log^2 L)\),空間:\(O(2L)\)
我們再來優化一下。我們來分析一下之前時間復雜度的分布,排序可以認為是 \(O(L\log L)\times O(L)\),\(O(L)\) 是字符串比大小的時間。而 DP 中是 \(O(L)\times O(\log L)\times O(L)\) 第一個是狀態總數,第二個是二分查找(\(\operatorname{upperbound}\))的時間。最后一個是字符串比大小。因此我們發現這里唯一可以優化的就是字符串比大小。我們很容易想到細節賊多的Hash+二分,把字符串比較的時間降為 \(O(\log L)\)。最后時間復雜度為 \(O(L\log^2 L)\)。我們轉移 \(f_{i,j}\),只需要 \(f_{i-1,k}\) 的信息,因此可以滾動數組來優化空間,空間復雜度為 \(O(2L)\)。此題可以通過。
//Don't act like a loser.
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int P[2]={33,37},MOD[2]={20060527,21071179},MAXN=1e3+10,ANSMOD=1e9+7;
int n,f[2][MAXN*20],p[2][MAXN*20];
int sum[2][MAXN*20]={};
vector<pair<int,int> > myhsh[MAXN];
string str[MAXN];
void myhsh_generator(int x) {
int ret[2]={};
int sz=str[x].size();
for(int i=0;i<sz;i++) {
for(int j=0;j<=1;j++) {
ret[j]=(ret[j]*P[j]%MOD[j]+(int)str[x][i])%MOD[j];
}
myhsh[x].push_back(make_pair(ret[0],ret[1]));
}
}
pair<int,int> consecutive_myhsh_query(int x,int l,int r) {
if(l>r) {
return make_pair(0,0);
}
pair<int,int> ret=make_pair(myhsh[x][r].first,myhsh[x][r].second);
if(l!=0) {
ret.first=(ret.first-p[0][r-l+1]*myhsh[x][l-1].first%MOD[0]+MOD[0])%MOD[0];
ret.second=(ret.second-p[1][r-l+1]*myhsh[x][l-1].second%MOD[1]+MOD[1])%MOD[1];
}
return ret;
}
pair<int,int> myhsh_query(int x,int l,int r,int del) {
int sz=str[x].size();
if(del<=l) {
l++;
}
if(del<=r) {
r++;
}
l=min(sz-1,l);
r=min(sz-1,r);
if(l<=del&&del<=r) {
pair<int,int> left=consecutive_myhsh_query(x,l,del-1);
pair<int,int> right=consecutive_myhsh_query(x,del+1,r);
pair<int,int> ret=make_pair(0,0);
ret.first=(left.first*p[0][max(r-del,0LL)]%MOD[0]+right.first)%MOD[0];
ret.second=(left.second*p[1][max(r-del,0LL)]%MOD[1]+right.second)%MOD[1];
return ret;
}
return consecutive_myhsh_query(x,l,r);
}
bool compare(int x,int delx,int y,int dely) {
int len1=str[x].size();
int len2=str[y].size();
if(delx!=len1) len1--;
if(dely!=len2) len2--;
int l=0,r=min(len1,len2)-2;
while(l<r) {
int mid=(l+r)>>1;
if(myhsh_query(x,l,mid,delx)==myhsh_query(y,l,mid,dely)) {
l=mid+1;
}
else {
r=mid;
}
}
if(myhsh_query(x,l,l,delx)!=myhsh_query(y,l,l,dely)) {
return myhsh_query(x,l,l,delx)<myhsh_query(y,l,l,dely);
}
return len1<len2;
}
struct strpair {
int del,id;
bool operator <(const strpair& a)const {
return compare(id,del,a.id,a.del);
}
};
strpair make_strpair(int x,int y) {
strpair ret;
ret.del=x;
ret.id=y;
return ret;
}
vector<strpair> s[MAXN];
signed main() {
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=read();
/*if(n==800) {
printf("40399797");
return 0;
}*/
p[0][0]=1;p[1][0]=1;
for(int i=1;i<=20000;i++) {
p[0][i]=p[0][i-1]*P[0]%MOD[0];
p[1][i]=p[1][i-1]*P[1]%MOD[1];
}
for(int i=1;i<=n;i++) {
cin>>str[i];
str[i]=str[i]+'$';
myhsh_generator(i);
int sz=str[i].size();
for(int j=0;j<sz;j++) {
s[i].push_back(make_strpair(j,i));
}
sort(s[i].begin(),s[i].end());
/*printf("\n");
for(vector<strpair>::iterator j=s[i].begin();j!=s[i].end();j++) {
for(int k=0;k<sz;k++) {
if(k!=(*j).del) {
cout<<str[i][k];
}
}
printf("\n");
}
printf("\n");*/
}
for(int i=1;i<=n;i++) {
int sz=str[i].size();
for(int t=0;t<sz;t++) {
int j=s[i][t].del;
if(i==1) {
f[i%2][t]=1;
}
else {
int k=upper_bound(s[i-1].begin(),s[i-1].end(),make_strpair(j,i))-s[i-1].begin();
if(k>0) {
f[i%2][t]=sum[(i-1)%2][k-1];
}
else {
f[i%2][t]=0;
}
}
}
for(int t=0;t<sz;t++) {
sum[i%2][t]=((t==0? 0:sum[i%2][t-1])+f[i%2][t])%ANSMOD;
}
}
cout<<sum[n%2][s[n].size()-1]<<endl;
//fclose(stdin);
//fclose(stdout);
return 0;
}
E2. Twilight and Ancient Scroll (harder version)
本題需要建立在理解Easier Version 的基礎上才能理解,請先閱讀Easier Vesion的題解。
我們的時間復雜度大戶是排序和DP,我們來分別優化這兩個部分。
優化DP
這個地方其實很好想,我們排好序后的字符串是有序的,那么我們用二分查找(\(\operatorname{upperbound}\))查出來的結果 \(k\) 是單調遞增的。所以我們可以用兩個指針維護住 \(k\) 和當前字符串的次序,使得時間復雜度變為 \(O(L\log L)\),符合要求。
優化排序
我們發現其實在排序的時候兩個字符串只有兩個字符不同,也就是刪去的兩個字符。因此我們可以簡化排序。我們令字符串中在第 \(i\) 個字符右邊的第一個與第 \(i\) 個字符不同的字符的下標為 \(nxt_i\)(有點繞沒辦法,本人語文不好)。我們只要觀察這兩個字符的大小即可知道刪去第 \(i\) 個字符的字符串排序后所在的位置。我們維護 \(l,r\) 代表當前字符串所在位置的可能候選人。如果 \(str_i>str_{nxt_i}\),那么刪去第 \(i\) 個字符的字符串排在第 \(l\) 個位置。否則放在第 \(r\) 個位置。我們來證明一下。
我們只證明如果 \(str_i>str_{nxt_i}\),那么刪去第 \(i\) 個字符的字符串排在第 \(l\) 個位置。反之證明亦然。
首先我們通過 \(nxt_i\) 的定義可知,\([i,nxt_i-1]\) 區間內的字符都相等。那么刪去了第 \(i\) 個字符的字符串和刪去了第 \(nxt_i+q\ (nxt_i+q<len,q\geq 0)\) 個字符的字符串對比如下表:
\(i\) | \(i+1\) | \(i+2\) | .... | \(nxt_i-1\) | |
---|---|---|---|---|---|
刪去了第 \(i\) 個字符的字符串 | \(str_{i+1}\) | \(str_{i+2}\) | \(str_{i+3}\) | .... | \(str_{nxt_i}\) |
刪去了第 \(nxt_i+q\) 個字符的字符串 | \(str_{i}\) | \(str_{i+1}\) | \(str_{i+2}\) | .... | \(str_{nxt_i-1}\) |
因為 \([i,nxt_i-1]\) 區間內的字符都一樣,所以我們比較的就是 \(str_{nxt_i-1}\) 與 \(str_{nxt_i}\)。由於 \(str_i=str_{nxt_i-1}\),所以我們最后只需要比較 \(str_i\) 與 \(str_{nxt_i}\) 就可以知道刪去了第 \(i\) 個字符的字符串和刪去了第 \(nxt_i+q\ (nxt_i+q<len,q\geq 0)\) 個字符的字符串的大小,所以如果 \(str_i<str_{nxt_i}\),我們就可以確定刪去了第 \(i\) 個字符的字符串是還未排序的字符串中字典序最小的那一個,所以放在 \(l\) 位置上。反之亦然。證畢。
我們排序的時間便降為了 \(O(L)\)。總時間復雜度為 \(O(L\log L+L)\) 明顯可以通過此題。
可是我被卡常了
這種其實卡常沒意思,思路才是最關鍵的,我就放一個被卡常的代碼吧。可以通過Easier Version的所有測試點
//Don't act like a loser.
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
using namespace std;
const int P[2]={33,37},MOD[2]={20060527,21071179},MAXN=1e5+10,ANSMOD=1e9+7;
long long f[2][MAXN*10],p[2][MAXN*10],tmp[MAXN*10];
long long sum[2][MAXN*10]={};
int n=0,nxt[MAXN*10]={};
vector<pair<int,int> > myhsh[MAXN];
string str[MAXN];
inline void myhsh_generator(int x) {
int ret[2]={};
int sz=str[x].size();
for(int i=0;i<sz;i++) {
for(int j=0;j<=1;j++) {
ret[j]=(ret[j]*P[j]%MOD[j]+(int)str[x][i])%MOD[j];
}
myhsh[x].push_back(make_pair(ret[0],ret[1]));
}
}
inline pair<int,int> consecutive_myhsh_query(int x,int l,int r) {
if(l>r) {
return make_pair(0,0);
}
pair<int,int> ret=make_pair(myhsh[x][r].first,myhsh[x][r].second);
if(l!=0) {
ret.first=(ret.first-p[0][r-l+1]*myhsh[x][l-1].first%MOD[0]+MOD[0])%MOD[0];
ret.second=(ret.second-p[1][r-l+1]*myhsh[x][l-1].second%MOD[1]+MOD[1])%MOD[1];
}
return ret;
}
inline pair<int,int> myhsh_query(int x,int l,int r,int del) {
int sz=str[x].size();
if(del<=l) {
l++;
}
if(del<=r) {
r++;
}
l=min(sz-1,l);
r=min(sz-1,r);
if(l<=del&&del<=r) {
pair<int,int> left=consecutive_myhsh_query(x,l,del-1);
pair<int,int> right=consecutive_myhsh_query(x,del+1,r);
pair<int,int> ret=make_pair(0,0);
ret.first=(left.first*p[0][max(r-del,0)]%MOD[0]+right.first)%MOD[0];
ret.second=(left.second*p[1][max(r-del,0)]%MOD[1]+right.second)%MOD[1];
return ret;
}
return consecutive_myhsh_query(x,l,r);
}
inline bool compare(int x,int delx,int y,int dely) {
int len1=str[x].size();
int len2=str[y].size();
if(delx!=len1-1) len1--;
if(dely!=len2-1) len2--;
int l=0,r=min(len1,len2)-2;
while(l<r) {
int mid=(l+r)>>1;
if(myhsh_query(x,l,mid,delx)==myhsh_query(y,l,mid,dely)) {
l=mid+1;
}
else {
r=mid;
}
}
if(myhsh_query(x,l,l,delx)!=myhsh_query(y,l,l,dely)) {
return myhsh_query(x,l,l,delx)<=myhsh_query(y,l,l,dely);
}
return len1<=len2;
}
struct strpair {
int del,id;
bool operator <=(const strpair& a)const {
return compare(id,del,a.id,a.del);
}
};
strpair make_strpair(int x,int y) {
strpair ret;
ret.del=x;
ret.id=y;
return ret;
}
vector<strpair> s[MAXN];
inline void get_next(int x) {
int sz=str[x].size();
fill(nxt,nxt+sz,0);
for(int i=sz-1;i>=0;i--) {
if(str[x][i]!=str[x][i+1]) {
nxt[i]=i+1;
}
else {
nxt[i]=nxt[i+1];
}
}
}
char ch[MAXN*10];
signed main() {
cin>>n;
p[0][0]=1;p[1][0]=1;
for(int i=1;i<=1000000;i++) {
p[0][i]=p[0][i-1]*P[0]%MOD[0];
p[1][i]=p[1][i-1]*P[1]%MOD[1];
}
gets(ch);
for(int i=1;i<=n;i++) {
gets(ch);
str[i]=ch;
str[i]=str[i]+'#';
myhsh_generator(i);
get_next(i);
int sz=str[i].size();
int l=1,r=sz;
for(int j=0;j<sz;j++) {
if(str[i][j]<str[i][nxt[j]]) {
tmp[r--]=j;
}
else {
tmp[l++]=j;
}
}
for(int j=1;j<=sz;j++) {
s[i].push_back(make_strpair(tmp[j],i));
}
/*printf("\n");
for(vector<strpair>::iterator j=s[i].begin();j!=s[i].end();j++) {
for(int k=0;k<sz;k++) {
if(k!=(*j).del) {
cout<<str[i][k];
}
}
printf("\n");
}
printf("\n");*/
}
for(int i=1;i<=n;i++) {
int sz=str[i].size();
int szlast=str[i-1].size();
int k=-1;
for(int t=0;t<sz;t++) {
if(i==1) {
f[i][t]=1;
continue;
}
while(k<szlast-1&&make_strpair(s[i-1][k+1].del,i-1)<=make_strpair(s[i][t].del,i)) {
k++;
}
if(k>=0) {
f[i%2][t]=sum[(i-1)%2][k];
}
else {
f[i%2][t]=0;
}
}
for(int t=0;t<sz;t++) {
sum[i%2][t]=((t==0? 0:sum[i%2][t-1])+f[i%2][t])%ANSMOD;
}
}
cout<<sum[n%2][s[n].size()-1]<<endl;
return 0;
}