2022牛客多校联赛第九场

比赛传送门

A题 Car Show

题意
有n 个数字,并且数字的种类不超过m ,问一共有多少个区间满足区间内m 种数字至少出现1 次。
分析
可以使用双指针来solve 此题l 表示左端点r 表示右端点,我们通过不断将 r 右移,一旦出现区间l−r] 内满足m 个不同种类数出现至少1 次,那我们就不断将 l 右移,直至区间内不满足m 个不同种类的数出现至少1 次。那么每有区间[l,r] 满足 m 个不同种类数出现至少1 次,那么贡献就可以加上n−r+1 。因为[r−n] 区间 无论放几个数都一定满足条件 。
代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define int long long
#define lson 2*p
#define rson 2*p+1
const int N=200005;
const int mod=1e9+7;
int n;
int a[N],cnt[N];

signed main()
{
	int t=1;
	//cin>>t;
	while(t--)
	{
		int n,m;
		cin>>n>>m;
		for(int i=1;i<=n;i++) cin>>a[i];
		if(m==1)
		{
			cout<<(n)*(n+1)/2;
			return 0;
		}
		int ans=0;
		int res=0;
		for(int i=1,j=1;i<=n-m+1;)
		{
			int pos;
			while(j<=n)
			{
				
				if(!cnt[a[j]]) 
				{
					
					res++;
				}
				
				if(j!=pos) cnt[a[j]]++;
				if(res==m)
				{
					pos=j;
					break;
				}
				j++;
			}
			//cout<<cnt[5]<<endl;
			ans+=(n-j+1);
			//cout<<i<<" "<<j<<" "<<res<<endl;
			//cout<<ans<<endl;
			for(int k=i+1;k<=j;k++)
			{
				if(a[k]!=a[i])
				{
					cnt[a[i]]--;
					if(!cnt[a[i]]) res--;
					
					i=k;
					break;
				}
				else if(a[k]==a[i])
				{
					if(k!=i&&res==m) ans+=(n-j+1);
					cnt[a[i]]--;
					if(!cnt[a[i]]) res--;
				}
			}
//			cout<<i<<" "<<j<<endl;
//			cout<<ans<<endl; 
			//j++;
		}
		cout<<ans<<endl;
	}
}

B题 Two Frogs

题意
有两只青蛙,起始位置在 1 号点,每个点都有一个跳跃区间,跳跃区间内的点落地概率相同,假设 aa[1]=5 ,那么 1 号点到2−6 号点的概率相同,求两只青蛙从 1 跳到 n 号点,所有步数的概率之和。
分析
首先由于青蛙不会呆在原地,所以跳跃次数最多不超过n−1 ,设dp[i][j] 表示到达j 点需要i 步,那么初始状态为dp[0][1]=1 即消耗0 步到达1 的概率1 。
代码如下

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
//#define int long long
#define lson 2*p
#define rson 2*p+1
const int N=200005;
const int mod=998244353;
int a[8005];
int dp[8005][8005];
int sum[8005][8005];
long long inv[8005];
int qpow(int a,int b)
{
	if(b==0) return 1;
	if(b&1) return a*qpow(a,b-1)%mod;
	else
	{
		int mul=qpow(a,b/2)%mod;
		return mul*mul%mod;
	}
}
signed main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n-1;i++) cin>>a[i];
	inv[1]=1;
	for(int i=2;i<=n;i++)
	{
	    inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	//for(int i=1;i<=n;i++) cout<<inv[i]<<endl;
	dp[n][0]=1;
	sum[0][n]=1;
	for(int i=n-1;i>=1;i--)
	{
		for(int j=1;j<=n-i;j++)
		{
			if(j==1)
			{
				if(i+a[i]>=n)
				{
					dp[i][j]=dp[n][0]*inv[a[i]]%mod;
					
				}
				else dp[i][j]=0;
				sum[j][i]=(sum[j][i+1]+dp[i][j])%mod;
			}
			else
			{
				dp[i][j]=(dp[i][j]+(sum[j-1][i+1]%mod+mod-sum[j-1][min(i+a[i],n)+1]%mod)%mod*inv[a[i]]%mod)%mod;
			    dp[i][j]%=mod;
			    sum[j][i]=(sum[j][i+1]+dp[i][j])%mod;
			    sum[j][i]%=mod;
			}
			//printf("dp[%d][%d]=%d\n",i,j,dp[i][j]);
		}
		
	}
    long long ans=0;
	for(int i=1;i<=n;i++) ans=(ans+(long long)dp[1][i]*dp[1][i]%mod)%mod,ans%=mod;
	cout<<ans<<endl;
}

G题 Magic Spells

题意
给定不超过 5个字符串,统计这些字符串中公有的不同回文串数量,下标不同若回文串相同认为是同一个回文串。
分析
manacher 用来统计每个字符串中的回文子串的左右端点,hash 用于O(1) 查询该回文子串是否出现,并放入 set进行记录。最终如果一个回文子串出现个数和字符串个数相同那么说明该回文子串在所有字符串中皆出现过,总贡献+1。前面部分都不是难点,该题毒瘤 难点在于会卡掉ull 自然溢出的情况。因此要开两个hash一个自然取模,另一个设定一个模数例如 1e9+7 ,减少冲突。
代码如下

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<ll,ll>
typedef unsigned long long ull;
const int N = 6e5+10,P=131;
const ull mod = 1e9+7;
ull h1[N],h2[N],p1[N],p2[N];
map<pair<ull,ull>,int>v;
set<pair<ull,ull>>s;
set<pair<int,int>>v2;
char a[N];
char b[N];
int p[N];
int res;
int t;
int n;
pair<ull,ull> find(int l,int r){//降低冲突
    return {(h1[r]-h1[l-1]*p1[r-l+1]%mod+mod)%mod,(h2[r]-h2[l-1]*p2[r-l+1])};
}
void manacher(){
    int mr=0,mid;
    memset(p,0,sizeof p);
    for(int i=1;i<n;i++){//首尾是$^
        if(i<mr)p[i]=min(p[mid*2-i],mr-i);//p[mid*2-i]就是j
        else p[i]=1;
        int l=i-p[i]+1,r=i+p[i]-1;
        while(b[i-p[i]]==b[i+p[i]]){
            p[i]++;
            if(p[i]<2)continue;
            int l=i-p[i]+1,r=i+p[i]-1;
            if(b[l]!='#') continue;
            v2.insert({l,r});//当前字符串的回文子串左右端点
        }
        if(i+p[i]>mr){
            mr=i+p[i];
            mid=i;
        }
    }
}
void init(){
    int k=0;
    b[k++]='$';b[k++]='#';
    for(int i=0;i<n;i++)b[k++]=a[i],b[k++]='#';
    b[k++]='^';
    n=k;
    s.clear();v2.clear();
}
signed main(){
    scanf("%d",&t);
    for(int tt=1;tt<=t;tt++){
        scanf("%s",a);
        n=strlen(a);
        init();
        manacher();
        p1[0]=1;p2[0]=1;
        for(int i=1;i<n-1;i++){
            h1[i]=(h1[i-1]*P+b[i]-'#')%mod;//mod 1e9+7
            p1[i]=(p1[i-1]*P)%mod;
        }
        for(int i=1;i<n-1;i++){
            h2[i]=(h2[i-1]*P+b[i]-'#');
            p2[i]=(p2[i-1]*P);
        }
        for(auto x:v2){
            int l=x.first,r=x.second;
            auto get=find(l,r);
            s.insert(get);
        }
        for(auto x:s){
            v[x]++;
            if(tt==t&&v[x]==t)res++; 
        }
        for(int i=0;i<=n;i++)a[i]='\0',b[i]='\0';
    }
    cout<<res<<endl;
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值