【字符串 】前缀和大法好 CF 1016B 用kmp实现

以cf501的B (可能记错)和百度之星2018 的1002为例 

嘛  在字符串里面有一个kmp很重要(但是我还不太会)

此外就是这个前缀和  我们知道,每个字母只有26个呀

所以即使带上全部的状态存,也不会很大,1e5只有1e5*26

那么如何分析呢?

首先,cf的题是求某个区间里面子串的个数,查询有1e5次,那么直接从1开始往后加到n存下来,用的时候直接减去左边就可以了。

有个小地方就是,减去的不是l,而是l+m(m是子串的长度)

因为区间一共就那么长,前面都没到既定长度,不会出现子串的。

这样做就避免了aaaaaaaa里面aaa的问题。

(最开始自己写的时候还弄了个后缀和,傻死了,不要妄想着特殊特殊特殊最后就过了呀,如果遇到自己发现的一组奇怪的样例,应该分析一下哪里导致了问题呀,或者样例多试试几次,手写一下的话其实也能发现有问题的)

其中字符串匹配最好还是用kmp,自己手写了一个好像有时候会碰到麻烦,反正wa9了,先扔到这里吧,……面对aaab和aab的时候其实应该从2开始算,后面的cls记录又乱了,也没再改【所以说去学kmp啊!!!】

注意一下l到r应该是r-l+1   还有字符串匹配的+1小问题【】

百度之星这题猛一看没什么思路,其实是一样的,这里问的是在一个大串串里,最小的数的出现的次数

其实。。。。。

思路猛然打开就会发现,还是前缀和,用一个二维sum数组存好,(不复杂)然后查询的时候每次先揪出这次是哪个,再输出它的出现次数就好了。

【注意是A-'A'+1】

还有,我蠢到aaa实验了半天样例,发现全错了。。。 这题是大写的,小写习惯了。。。

【话说感觉在纸上写出来再实现,优化了不少诶,。。既然t了那大概总会有更简单的地方吧,忽然想到最后查询区间的时候一个个跑过去也很浪费,那么直接从小到大看哪个不是0就输出哪里好了,最后复杂度只有1e5*26+q*26】

ps 百度之星这个vs里面好像开不了这么大的数组

wa了竟然是因为没加case 所以说了嘛 看样例看样例看样例  这种年代怎么还出现没测样例就扔上去的惨案啊...

 以及我用的cin..  加了一句神奇的东西

或者你可以直接cin.sync_with_stdio(0);来加速cin(但是不能使用scanf了)


#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;

int main() {
    ios::sync_with_stdio(0);
    int t;
    //char ccc; cin >> ccc;
    //cout << ccc - 'A'+1 << endl;
    cin >> t; int kase = 0;
    while (t--) {
        int n, q; cin >> n >> q;
        int a[100005];
        char c[100005];
        int sum[100005][30];
        for(int i=1;i<=26;i++)
            sum[0][i] = 0;
        c[0] = ' ';//啊啊啊啊啊啊啊这里要用' ' 
        for (int i = 1; i <= n; i++)//scanf("%s",c+1);
            cin >> c[i];
        //先测试是什么
        for (int i = 1; i <= n; i++) {
            //cout << c[i] - 'A' +1<<"---------"<<endl;
            sum[i-1][c[i] - 'A' +1]++;// ta ++ 
            //cout << sum[i - 1][c[i] - 'A' +1]<<endl;
            for (int j = 1; j <= 26; j++)
                sum[i][j] = sum[i-1][j];
            sum[i - 1][c[i] - 'A' + 1]--;
            //cout << sum[i][c[i] - 'A' +1] << endl;
            //cout << "-------" << endl;
        }cout << "Case #" << ++kase << ":" << endl;
        while (q--) {
            int aa, bb,cc; cin >> aa >> bb;
            if (aa == bb)cout << "1" << endl;
            else {//所谓优化 就是减少浪费啊
                for (int i = 1; i <= 26; i++) {
                    //    cc c[i] - 'A' +1));
                    //cout << cc << endl;
                    //for (int i = 1; i <= n; i++)cout << sum[i][cc] << " ";
                    //cout << endl;
                
                    if (sum[bb][i] - sum[aa - 1][i] != 0)
                    {
                        cout<< sum[bb][i] - sum[aa - 1][i] << endl;
                        break;
                    }
                }
            }
        }

    }
    return 0;
}

(怎么写的这么丑。。)

420ms..  直接暴力改了kmp..  这个以后当板子用吧=-=

	// 其实我写的那个每个地方存好cnt[i]的也是不再增加的...  
	//感觉我写的那个也有点线性吧-.- 
	// 就是cls那里的关系没搞清楚 然后各个地方经常容易错,现在用kmp再来写一遍 
	//看别人的, 感觉直接对区间进行kmp的每个查询也能过
	//或者,他们的预处理是分开的预处理 , 对每个地方都预处理一下
	//我的是区间直接用的
	 
	
#include<stdio.h>
#include<string>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std; 
int rec[1005];
int n,m,aaa;
int nnext[1005];
char P[1005];
char T[1005];
void makeNext()
{
//	memset(next,0,sizeof(next));
//...  每个地方都用到了的话 应该还好吧 
    int q,k;
    nnext[0] = 0;
    for (q = 1,k = 0; q < m; ++q)
    {
        while(k > 0 && P[q] != P[k])
            k = nnext[k-1];
        if (P[q] == P[k])
        {
            k++;
        }
        nnext[q] = k;
    }
}

int kmp(int j)
{
	int cnt=0;
    int q=0;
    makeNext();
    for (int i = 0; i < j; ++i)
    {
        while(q > 0 && P[q] != T[i])
            q = nnext[q-1];
        if (P[q] == T[i])
        {
            q++;
        }
        if (q == m)
        {
        cnt++;
        }
    } 	 return cnt;   
}

int main()
{	cin>>n>>m>>aaa;
   scanf("%s",T);
   scanf("%s",P);
   for(int i=0;i<=n;i++){
   rec[i]=kmp(i);
   }
//	for(int i=1;i<=n;i++)cout<<rec[i];
//	cout<<endl;
	int l,r;
	while(aaa--){
	cin>>l>>r;
	if(r-l+1<m)cout<<"0"<<endl;
	else 
	 cout<<max(0,rec[r]-rec[l+m-2])<<endl;	
	}
    return 0;
}

已经很暴力了...  注意一下如果l和r之间如果没到那个关系, 可能会出现负的,关键是l+m了啊,但是,是l-1加的m,因为是l之前的,所以是l-1,但是这里又是区间,区间问题格外要注意,区间是5-7 ,长度要7-5+1  

所以m是长度,长度是3,区间跨度是2,最后就是l+m-2

留意一下吧,关键是rec[i]的话 不会可以输出一下 

然后kmp的地方,我看着自己之前的那个博客,自己手写模拟了一下过程

。。。 图片传布上来

我的天啊。。。  经过一点改进,j是要算的长度、、、

n是大的长度  m是小的长度



//注意 传入的j是长度
//然后从0开始是因为字符串的限制
//最后的rec[i]  是从1开始的
void makeNext()
{
//	memset(next,0,sizeof(next));
//...  每个地方都用到了的话 应该还好吧 
    int k=0;

    nnext[0] = 0;
    for (int q = 1; q < m; ++q)
//这m是长度
    {
        while(k > 0 && P[q] != P[k])
            k = nnext[k-1];
        if (P[q] == P[k])
        {
            k++;
        }
        nnext[q] = k;
    }
}

int kmp(int j)
{
	int cnt=0;
    int q=0;
    makeNext();
    for (int i = 0; i < j; ++i)
    {
        while(q > 0 && P[q] != T[i])
            q = nnext[q-1];
        if (P[q] == T[i])
        {
            q++;
        }
        if (q == m)
        {
        cnt++;
        }
    } 	 return cnt;   
}

最后面这个就别看了


#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
int rec[1005];
int after[1005];
int main(){
	int n, m, q; cin >> n >> m >> q;
	int cnt;
	string a, b; cin >> a>>b;
	int l, r;
	if (m != 1 && n > m) {
		int j = 0; cnt = 0;
		int cls=0;
		//问题就是这个-1...  -1 -1 -1 
		for (int i = 0; i < n; i++) {
			if (a[i] != b[j]) j = 0; 
			
			//一个不相等了 肯定都要等于0 的
			// i加了  j不变
			else if (a[i] == b[j]) j++; 
			//开始是0  完了++是1 最后++是2
			//相等的话  j要加 i也要加 继续往后比
			if (j == m)// 走完了..
			{
				cnt++;
				j = 0;
				rec[i] = cnt;
				 cls = m - 1;//偏移量
				i = i - m + 1;
				
			}
			//如果没走完
			else {// 如果cls大于0的话说明前面是偏移来的....  
				if (cls == 0)
					rec[i] = cnt;
				else cls--;
			}
		}
		
		/*
		  j = m-1;  cnt = 0;
		  cls = 0;
		 //问题就是这个-1...  -1 -1 -1 
		 for (int i = n-1; i >=0; i--) {
			 if (a[i] != b[j]) j =m-1 ;

			 //一个不相等了 肯定都要等于0 的
			 // i加了  j不变
			 else if (a[i] == b[j]) j--;
			 //开始是0  完了++是1 最后++是2
			 //相等的话  j要加 i也要加 继续往后比
			 if (j == -1)// 走完了..
			 {
				 cnt++;
				 j = m-1;
				 after[i] = cnt;
				 cls = m - 1;//偏移量
				 i = i + m - 1;

			 }
			 //如果没走完
			 else {// 如果cls大于0的话说明前面是偏移来的....  
				 if (cls == 0)
					 after[i] = cnt;
				 else cls--;
			 }
		 }*/
	}
	//for (int i = 0; i <n; i++)cout << rec[i];
	//cout << endl;
	//for (int i = 0; i <n ; i++)cout << after[i];
	while (q--) {
		bool flag = true;
		cin >> l >> r;
		if (n < m)cout << "0" << endl;
		else if (m == 1) {
			int cnt = 0;
			for (int i = l-1; i <= r-1; i++) {
				if (a[i] == b[0])cnt++;
			}cout << cnt << endl;
		}
		else {
			//  记得要-1  -1  -1
			//if (rec[l] == 0 )cout << rec[r-1]-rec[l-1] << endl;
			r--; l--;
			if(r-l+1>=m)
				cout <<max(rec[r]-rec[l+m-2],0)<< endl;
			else cout << "0" << endl;
		}
	}


	return 0;
}

猜你喜欢

转载自blog.csdn.net/StrongerIrene/article/details/81428418