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


    比赛传送门
    作者: fn


    签到题

    A题 Car Show / 车展

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

    考察内容
    双指针

    分析

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

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

    #include
    #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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    基本题

    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
    #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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    进阶题

    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
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

  • 相关阅读:
    笙默考试管理系统-SMExamination.Model.Notice展示(2)
    excel中通过ROW函数返回引用的行号
    OpenAI GPT5计划泄露
    A114-经典赛题-Web应用程序文件包含安全攻防
    go 邮件功能(全)
    12. Java异常及异常处理处理
    jvm-java 内存模型 以及各个分区具体内容
    盘点5个C#实用的Word、PPT、Excel、Mail第三方库
    【Unity3D】Shader Graph简介
    Gin 打包vue或react项目输出文件到程序二进制文件
  • 原文地址:https://blog.csdn.net/weixin_51937688/article/details/126351174