zoukankan      html  css  js  c++  java
  • P7114 [NOIP2020] 字符串匹配

    题意

    著名题目
    链接

    题解

    参考博客
    首先一个思路是 枚举循环节长度
    要变成 ((AB)^kC) 的形式 先枚举循环节(AC)长度(i)
    设循环结长度为 (i) 显然 (i)(2)(n-1) 都是合法的
    因为循环节非空且C非空
    接下来我们想,能循环多少次呢?
    这里引入扩展kmp函数(Z[i]) 表示从整个字符串从(i)号字符开始的后缀 和 整个串的 最长公共前缀
    不妨自己画个图 (t = Z[i+1]/i+1) 就是最多能循环的次数(额外加上上循环一次)

    然后循环这么多次 到底有多少种合法的方案呢?
    在这里 我们设(pre sub all) 表示前缀、后缀、整个串里面出现奇数次的字符的个数
    在枚举i的过程中 我们可以很轻松的更新这三个值

    对于循环节(AB) 我们分类讨论他出现了奇数次还是偶数次
    如果出现了偶数次 那么对整个序列的影响可以忽视
    也就是说 C中出现奇数次字符的个数 等于 整个序列出现奇数次字符的个数
    也就是说 我们的序列A 需要奇数字符个数 小于等于 整个数列的奇数字符个数
    对于这个数量的要求 可以用树状数组 维护(F(x))表示(0-i)所有前缀出现奇数次字符大于x的前缀个数
    对于这个函数的求法 我们一会再做讨论
    现在得出的结论是 这一部分对答案的贡献为

    [F(all)*(t/2) ]

    接下来 如果出现了奇数次
    思考后得出这一部分的贡献

    [F(suf)*(t-t/2) ]

    最后 我们想想F(x)如何动态维护
    每增加一个字符(s[i])
    我们把树状数组pre的位置+1
    查询的时候可以求出在i之前的且出现奇数次字符小于等于某个数x的方案数
    于是这题愉快的解决了

    #include<bits/stdc++.h>
    #define ll long long
    #define inf 0x7fffffff
    using namespace std;
    #define maxn 1048576
    int z[maxn];
    char s[maxn];
    int n;
    int suf=0,all=0,pre=0;
    int after[60],before[60]; 
    void Z()//exkmp 
    {
    	z[0]=n;
    	int now=0;
    	while(now+1<n&&s[now]==s[now+1])now++;
    	z[1]=now;
    	int p0=1;
    	for(int i=2;i<n;i++)
    	{
    		if(i+z[i-p0]<p0+z[p0])//够用 
    		{
    			z[i]=z[i-p0];
    		}else
    		{
    			now=p0+z[p0]-i;
    			now=max(now,0);
    			while(now<n&&now+i<n&&s[now]==s[now+i])now++;
    			z[i]=now;
    			p0=i;
    		}
    	}
    }
    int c[maxn];
    #define lowbit(x) (x&-x)
    void Add(int x)
    {
    	while(x<maxn)
    	{
    		c[x]++;
    		x+=lowbit(x);
    	}
    }
    int Query(int x)
    {
    	int res=0;
    	while(x)
    	{
    		res+=c[x];
    		x-=lowbit(x);
    	}
    	return res;
    }
    signed main()
    {
    	int T;
    	scanf("%d",&T);
    	while(T--)
    	{
    		memset(z,0,sizeof(z));
    		memset(c,0,sizeof(c));
    		for(int i=0;i<=59;i++)after[i]=0,before[i]=0;
    		scanf("%s",s);
    		n=strlen(s);
    		Z();
    		all=pre=suf=0;
    		for(int i=0;i<n;i++)
    		{
    			after[s[i]-'a']++;
    			if(i+z[i]==n)z[i]--;
    		}
    		for(int i=0;i<26;i++)
    		{
    			if(after[i]&1)all++;
    		}
    		ll ans=0;
    		suf=all;
    		for(int i=0;i<n;i++)//枚举循环节长度i 
    		{
    			if(after[s[i]-'a']&1)suf--;
    			else suf++; 
    			after[s[i]-'a']--;
    			if(before[s[i]-'a']&1)pre--;
    			else pre++;
    			before[s[i]-'a']++;
    			if(i!=0&&i!=n-1)
    			{
    				int t=z[i+1]/(i+1)+1;
    				ans+=1ll*Query(all+1)*(t/2)+1ll*Query(suf+1)*(t-t/2);
    				//奇数偶数的答案要分开计算 
    				//对于奇数次数 前面的奇数要比suf多 
    				//对于偶数次数 前面的奇数要比总数多 
    			}
    			Add(pre+1);
    		}
    		printf("%lld
    ",ans);
    	}
    	
    	
    	return 0;
    }
    /*
    f[i][j]表示i到j出现奇数次的字符数量 
    维护suf 表示后缀奇数次字符数量
        pre 表示前缀奇数次字符数量 
        all 表示总的奇数次字符数量 
    树状数组 用来查询前缀中奇数次字符小于等于一个数p的有多少种 
    Z[i]表示从i开始的后缀和整个数组的最大公共前缀 
    
    */
    
    
  • 相关阅读:
    wx-charts 微信小程序图表 -- radarChart C# .net .ashx 测试
    C# dev SearchLookUpEdit 和 RepositoryItemSearchLookUpEdit 测试
    C# Dev XtraReport 简单测试
    SQL查询,关联查询的区别 (LEFT JOIN 、LEFT OUTER JOIN、INNER JOIN)
    NPOI 读取Excel文件
    C# 截屏
    C# 程序运行进度显示Lable
    gridcontrol 添加行删除行
    C# 任务 数据加载不影响其他操作
    ChartControl ViewType.Pie3D 用法测试
  • 原文地址:https://www.cnblogs.com/lzy-blog/p/15335748.html
Copyright © 2011-2022 走看看