2022牛客多校联赛第九场 题解

比赛传送门
作者: fn


签到题

A题 Car Show / 车展

题目大意
给定一个长为 n n n 的序列,求有多少个区间包括序列中所有种类的数字。

考察内容
双指针

分析

枚举左端点 𝐿 𝐿 L ,合法的右端点集合一定是区间 [ 𝑅 , 𝑛 ] [𝑅,𝑛] [R,n] ,且 𝑅 𝑅 R 随着 𝐿 𝐿 L 的递增而不减。
采用双指针,在移动指针的同时维护区间内每种数字的个数以及出现数字的种类数,包含全部种类则累加答案。

两个指针都只枚举一遍,时间复杂度 𝑂 ( 𝑛 ) 𝑂(𝑛) O(n)

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+10;
ll n,m,a[N];
int vis[N];
int num=0;

int main(){ 
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	
	ll ans=0;
	ll p=1,q=0;
	while(p<=n-1 || q<=n-1){
		while(num<m && q<=n-1){
			q++;
			vis[a[q]]++;
			if(vis[a[q]]==1){
				num++;
			}
		}
		
		// num>=m || q>=n
		if(num>=m){ // 
			ans+=n-q+1;
		}
		
		while(num>=m){
			vis[a[p]]--;
			if(vis[a[p]]==0){
				num--;
			}
			
			p++;
			if(num>=m){
				ans+=n-q+1;
			}
		}
		if(q==n && num<m)break;
	}

	cout<<ans<<endl;
	return 0;
}
/*
5 3
1 1 1 2 3

*/ 

基本题

B题 Two Frogs / 两只青蛙

题目大意
河道里有 𝑛 𝑛 n 个荷叶排成一排,从第 𝑖 ( < 𝑛 ) 𝑖(<𝑛) i(<n) 个荷叶出发可以跳到第 [ 𝑖 + 1 , 𝑖 + 𝑎 𝑖 ] [𝑖+1,𝑖+𝑎_𝑖] [i+1,i+ai] 个荷叶上,有两只青蛙从第1个荷叶出发,每一步都独立地等概率随机地跳向后边的荷叶,求两只青蛙以相同步数到达第 𝑛 𝑛 n 个荷叶的概率。

𝑛 ≤ 8000 𝑛≤8000 n8000 ,保证 1 ≤ 𝑎 𝑖 ≤ 𝑛 − 𝑖 1≤𝑎_𝑖≤𝑛−𝑖 1aini

考察内容
差分,概率dp

分析

概率dp。
状态:
a [ i ] [ j ] a[i][j] a[i][j] :跳 i i i 次到第 j j j 格的概率。

边界:
a [ 0 ] [ 1 ] = 1 a[0][1]=1 a[0][1]=1
跳了0次,必定在第1格。

转移:
枚举往后跳了 j j j 次。
枚举当前跳到第 i i i 格。
更新区间 a [ j ] [ i + 1 , i + 2 , . . . , i + a i ] a[j][i+1,i+2,...,i+a_i] a[j][i+1,i+2,...,i+ai]

一些细节:

  1. 时间复杂度
    用树状数组维护带log,亲测tle了。
    用差分维护,时间复杂度 𝑂 ( 𝑛 2 ) 𝑂(𝑛^2) O(n2) ,可以通过。注意一下枚举的顺序。

  2. 空间复杂度
    a [ i ] [ j ] a[i][j] a[i][j]必须用int,亲测用long long会内存超限。

#include<bits/stdc++.h>
#define ll long long
// #define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=8002;
const ll mod=998244353;
ll read(ll &n){
	char ch=' '; ll q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
}
ll power(ll a,ll b){ // a^b%mod
	ll c=1;
	for(;b;b>>=1){
		if(b&1)c=c*a%mod;
		a=a*a%mod;
	}
	return c;
}
int a[N][N]; // a[i][j]: 跳i次到第j格的概率
int c[N][N]; // 第二维是a的差分数组 
ll n;
ll a0[N]; // 最多跳几格 
ll p[N]; // 1/a0[i]

signed main(){ // AC
	read(n);
	for(int i=1;i<=n-1;i++){
		read(a0[i]);
		p[i]=power(a0[i],mod-2)%mod; // 概率为 1/a0[i]
	}
    
    a[0][1]=1; // 跳了0次,必定在第1格
	c[0][1]=1;
    c[0][2]=mod-1; // 差分 
	
    // 差分,复杂度O(n^2) 
	for(int j=1;j<=n;j++){ // 枚举往后跳了j次
		ll cnt=0;
		for(int i=1;i<=n;i++){ // 求出a[j-1][i] 
		   	cnt+=c[j-1][i];
		   	cnt%=mod; 
		   	a[j-1][i]=cnt;
		} 
		for(int i=1;i<=n-1;i++){ // 枚举当前的格子,当前第i格 
			ll t1=a[j-1][i]*p[i]%mod; // 
			
			if(t1!=0){ // 对区间a[j][i+1]-a[j][i+a0[i]]进行更新,O(1)
				c[j][i+1]+=t1;
				c[j][i+1]%=mod;
				
				c[j][i+a0[i]+1] += mod-t1;
				c[j][i+a0[i]+1] %= mod;
			}
		} 
	}
	
	for(int i=1;i<=n;i++){
    	ll cnt=0;
		for(int j=0;j<=n;j++){
    		cnt+=c[i][j];
    		cnt%=mod;
    	} 
    	a[i][n]=cnt;
    } 
    
	ll ans=0;
	for(int i=0;i<=n;i++){
		ll t0=a[i][n]%mod; // 单点查询 
		ans+=t0*t0%mod; 
		ans%=mod;
	}
	cout<<(ans+mod)%mod<<endl;
	
    return 0;
}
/*
5
1 1 1 1

*/ 

进阶题

G题 Magic Spells / 魔法咒语

题目大意
给定 𝑘 𝑘 k 个字符串 𝑆 1 , 𝑆 2 , . . . , 𝑆 𝑘 𝑆_1,𝑆_2,...,𝑆_𝑘 S1,S2,...,Sk ,求有多少个不同的公共回文子串。
𝑘 ≤ 5 , ∑ 𝑖 = 1 𝑘 ∣ 𝑆 𝑖 ∣ ≤ 3 × 1 0 5 𝑘≤5,∑_{𝑖=1}^𝑘 |𝑆_𝑖 |≤3×10^5 k5i=1kSi3×105

考察内容
回文树

分析
对每个字符串 𝑆 𝑆 S 分别求出所有本质不同回文子串。
使用回文树求解,时间复杂度 𝑂 ( ∑ ∣ 𝑆 ∣ ) 𝑂(∑|𝑆|) O(S)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MN 300010
#define SN 35
char c[MN];int b[MN];
int l[MN],fl[MN],n,tot,Lt,tr[SN][MN],v[MN],g[MN];
inline void init(){
	l[1]=-1;Lt=tot=fl[0]=1;c[0]='a'+26;
}
inline ll Max(ll a,ll b){
	return a<b?b:a;
}
inline void add(int x,int y){
	int cc=c[x]-'a';
	while(c[x]!=c[x-l[Lt]-1])Lt=fl[Lt];
	if(!tr[cc][Lt]){
		l[++tot]=l[Lt]+2;int g=fl[Lt];
		while(c[x-l[g]-1]!=c[x])g=fl[g];
		fl[tot]=tr[cc][g];tr[cc][Lt]=tot;
	}g[Lt=tr[cc][Lt]]|=y;
}
int main(){ // 回文树
	int k;
	scanf("%d",&k);init();
	int n=0;
	for(int I=0;I<k;++I){
		scanf("%s",c+n+1);
		for(int i=n+1;c[i]!='\0';++i)b[i]=I;
		n=strlen(c+1);c[++n]='z'+I+1;b[n]=k;
	}

	ll res=0;
	for(int i=1;i<=n;++i)add(i,1<<b[i]);
	for(int i=tot;i;--i)g[fl[i]]|=g[i];
	for(int i=tot;i;--i)if(g[i]==(1<<k)-1)++res;
	cout<<res;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值