1. 程式人生 > >codeforces D.神祕的犯罪 + n個字串的公共子串+技巧

codeforces D.神祕的犯罪 + n個字串的公共子串+技巧

在m組數,每組有n個數(數的範圍1-n)中,找到某些序列 使它是每組數的一個公共子序列,問這樣的某些序列的個數?
1≤n≤100000 , 1≤m≤10
在這裡插入圖片描述
公共子串:1 2 3 23一共4個。

思考:我當時想的是暴力搜尋一遍,找到1,2序列的公共子串(長度>1)在去3~n序列搜尋
例如:
5 2
1 2 3 4 5
1 5 2 3 4
找到了234,因為單獨的一個數組肯定是一個公共子串,所以只考慮>1的公共子串計數
對234長度為2的公共子串為2, 長度為3為1
那麼一共有5 + 2 + 1=8
對長度為n的公共子串有(n-1)+(n-2)+(n-3)+…+1

看複雜度好像是可行的,最大O(nm),但是比較難寫。

看了大佬的題解,果然用了一個方法比較快速又方便的方法找到n個字串的公共子串

創立一個next陣列,使每組中第i個數的next 是第i+1個數,即 nex[ a[i] ] = a[ i+1 ] (實際上設next是二維陣列)。對第一組中的第i個數,如果在其餘每組的next[ a[ i -1] ]都是等於第一組中a[ i ]的,意味著序列 a[ i-1 ],a[ i ]是一個公共子序列

這個方法太神了,因為可以在複雜度為O(1)的情況下,找到其他串應該和這個字元匹配的位置,看不懂的自己慢慢理解。

利用一個數組 d[ ],d[ i ]記做 第 i 個數到第1個數之間滿足條件的子序列的個數 。對 i ,如果滿足條件,是公共子序列的話,d[ i ]=d[ i-1 ] + d[ i ],當然初始的時候 d[ i ] =1 (因為單獨的一個數組肯定是一個公共子串,題目一定滿足)。

這個d[i]可以擴充套件一下:
如果第i個字元匹配假設長度為n
對長度為n的公共子串有(n)+(n-1)+(n-2)+…+1(單個數計入)

那麼如果i+1個字元也匹配,長度為n+1
(n+1)+(n)+(n-1)+…+1
所以增加了(n+1)

因為d[i]=n,上面定義,d[i+1]+=d[i],所以d[i+1]=n+1正好為增加的公共子串,所以把所有的d[]相加就是答案,而且d[]的最大值就為最長的公共公共子串,還可以想想什麼時候d[]初始化為0

思考:用了一節大物課學了個神仙套路,賺了

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N=100005;

int n,m,a[11][N];
int nex[11][N];
ll d[N];

int main()
{
    a[1][0]=0;
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
         for(int j=1;j<=n;j++)
         {
             scanf("%d",&a[i][j]);
             nex[i][a[i][j-1]]=a[i][j];
         }
     }

     d[1]=1;
     for(int i=2;i<=n;i++)
     {
         int x=a[1][i-1];
         int flag=0;
         d[i]=1;
         for(int j=2;j<=m;j++)
         {
             if(a[1][i] != nex[j][x] ){
                 flag=1; break;
             }
         }
         if(!flag) d[i]+= d[i-1];
     }

     ll ans=0;

     for(int i=1;i<=n;i++)
     {
         ans+=d[i];
     }
     cout<<ans<<' '<<*max_element(d+1, d+n+1)<<endl;
}
/*

5 6
1 2 3 4 5
2 3 1 4 5
3 4 5 1 2
3 5 4 2 1
2 3 5 4 1
1 2 3 4 5

*/