2020年湖南省大学生计算机程序设计竞赛

  1. String Commutativity kmp 求最小循环节

大意:给定 n 字符串 s 1 , s 2 . . . . . . s n s_1,s_2......s_n s1,s2......sn 问多少对 i,j 满足 i<j并且 s i + s j = s j + s i s_i+s_j=s_j+s_i si+sj=sj+si。n<=1e5,所有字符串的总长度不超过 5e6。

思路:首先得从 s i + s j = s j + s i s_i+s_j=s_j+s_i si+sj=sj+si 中分析出 s i 和 s j s_i和s_j sisj 有相同的最小循环节。分析出来这点之后就是简单的kmp 求最小循环节了。对于某个字符串 s ,记 s 的长度为 len。求出 ne 数组后,最小循环节的长度L= len-ne[len]。若len%L=0 则,字符串s, 恰好可以通过循环节循环 len/L 次得到。若 len%L!=0, 则需要添加 L-ne[len]%L 个字符(从 s 1 s_1 s1开始…),构成循环节循环若干次得到的字符。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=1000010, M=1e9+7;
typedef long long LL;
int n;
char s[N];
int ne[N];
string cale()
{
	int len=strlen(s+1);
	for(int i=2,j=0;i<=len;i++)
	{
		while(j&&s[i]!=s[j+1])j=ne[j];
		if(s[i]==s[j+1])j++;
		ne[i]=j;
	}
	int L=len-ne[len];
	if(len%L==0)len=L;
	string ans="";
	for(int i=1;i<=len;i++)ans+=s[i];
	return ans;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	while(cin>>n)
	{
		map<string,int> cnt;
		LL ans=0;
		for(int i=1;i<=n;i++)
		{
			cin>>(s+1);
			string ss=cale();
			ans+=cnt[ss];
			cnt[ss]++;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

总结:KMP 求最小循环节、以及最少添加的字符。

  1. Absolute Difference Equation 思维

大意:给定一个长度为 n 的序列 a, 只包含 0,1,?,其中 ? 可以替换成0 或 1。定义 绝对值差分 b i = ∣ a i − a i + 1 ∣ b_i=|a_i-a_{i+1}| bi=aiai+1 ,一直进行此操作,知道剩一个数,问使剩的最后的数是1的方案数。对 1e9+7 取模

思路:首先得知道减法绝对值运算本质也就是异或运算。

随便举个例子,观察下:

a 1 , a 2 , a 3 , a 4 a_1,a_2,a_3,a_4 a1,a2,a3,a4

a 1 ⨁ a 2 , a 2 ⨁ a 3 , a 3 ⨁ a 4 a_1\bigoplus a_2,a_2\bigoplus a_3,a_3\bigoplus a_4 a1a2,a2a3,a3a4

a 1 ⨁ a 2 ⨁ a 2 ⨁ a 3 , a 2 ⨁ a 3 ⨁ a 3 ⨁ a 4 a_1\bigoplus a_2\bigoplus a_2\bigoplus a_3,a_2\bigoplus a_3\bigoplus a_3\bigoplus a_4 a1a2a2a3,a2a3a3a4

a 1 ⨁ a 2 ⨁ a 2 ⨁ a 2 ⨁ a 3 ⨁ a 3 ⨁ a 3 ⨁ a 4 a_1\bigoplus a_2\bigoplus a_2\bigoplus a_2\bigoplus a_3\bigoplus a_3\bigoplus a_3\bigoplus a_4 a1a2a2a2a3a3a3a4

我们发现 a 1 , a 2 , a 3 , a 4 a_1,a_2,a_3,a_4 a1,a2,a3,a4 出现的次数分别 1 3 3 1 恰好是组合数

所以 a_i 对最终出现的次数为 C ( n − 1 , i − 1 ) C(n-1,i-1) C(n1,i1)

有根据异或预算的性质,只有出现奇数次才会对最终结果有影响。

当奇数位置出现的数字能确保最终结果是1时,对于偶数部分的 ? 对最终结果的贡献就是 2 c n t 1 2^{cnt1} 2cnt1,对于奇数部分就是 2 c n t 2 − 1 2^{cnt2-1} 2cnt21 。若最终结果不可能是1,那结果就是0。然后就会遇到另一个难题,我们没办法在允许的时间复杂度内计算出组合数。因为我们要判断的奇偶性,对具体的值是多少并不关系,这里有个性质,对于 C(n,k),若 n&k=k,则 C(n,k) 为奇数,否则为偶数。然后就可以写了。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N=1000010, M=1e9+7;
typedef long long LL;
int n;
char s[N];
int qmi(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1)res=(LL)res*a%M;
		a=(LL)a*a%M;
		b>>=1;
	}
	return res%M;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	while(cin>>(s+1))
	{
		n=strlen(s+1);
		int js=0,os=0,t=0;
		for(int i=1;i<=n;i++)
		{
			if((n-1&i-1)==i-1)
			{
				if(s[i]=='?')js++;
				else t^=(s[i]-'0');
			}	
			else if(s[i]=='?')os++;
		}
		int ans=0;
		if(!js)
		{
			if(t==1)ans=qmi(2,os);
		}
		else ans=qmi(2,js+os-1);
		cout<<ans<<"\n";
	}
	return 0;
}

总结:绝对值运算可以转化成异或运算。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值