【2021.10.10CCPC复赛】1006 Nun Heh Heh Aaaaaaaaaaa

题目链接:Nun Heh Heh Aaaaaaaaaaa - HDU 7131 - Virtual Judge (vjudge.net)  

题目描述:

 


解题思路:

快速幂+动态规划+排列组合+思维 

把主串s分成前缀数组和后缀数组两部分,后缀数组记录a的个数,前缀数组保存每个位置前出现nunhehheh的个数(dp),那么答案就是:假设某个nunhehheh后面有n个a,这一组的个数就是2^(n-1)-1,所有情况个数就是算出每种可能然后求和。


快速幂:

直接上代码

//快速幂
ll powerMod(ll a,ll b)
{
	ll ans=1;
	a=a%mod;
	while(b>0)
	{
		if(b%2==1)  ans=(ans*a)%mod;
		b=b/2;
		a=(a*a)%mod;
	}
	return ans;
}

动态规划:

首先是后缀数组(求每个位置之后的a有多少个),简单dp,不做说明。

void get_a(int len)
{
    for(int i = len-1;i>=0;i--){
        s[i] = s[i+1];
        if(s[i] == 'a') s[i] += 1;
    }
}

然后是前缀数组,这里有两种算法,一种空间复杂度O(mn),一种空间复杂度O(n),时间复杂度都是O(mn)。前缀数组这儿卡了好久,其实和LCS有点像,可以先去学LCS。


第一种,空间复杂度O(mn):

s串为用户输入,t串为nunhehheh。

二维动态规划,定义一个dp[10] [MAX_N], dp[j] [i]的意思是s的前i-1个字符中有多少个子串和t中的1~j-1字符串相同。

动态规划:如果s的前i-1个字符中有x个子串与t中的1~j-1字符串相同,那么s的前i个字符中一定有x个子串与t中的1~j-1字符串相同。如果s[i] == t[j-1],那么s的前i个字符中还会多出 s的前i-2个字符的子串与t中1~j-2子符串相同时个数的个数。(绕的要死,建议看公式)

换成公式就是:

dp[j][i] = dp[j][i-1]%mod;
            if(s[i] == t[j])
                dp[j][i] += dp[j-1][i-1]%mod;

当找出完整字符串时,直接计算一次答案。

if(j == len_t){
                dp[j][i] = (dp[j-1][i-1]%mod)*(s[i] == t[j]);
                //如果是完整串,单次计算,只需要上一位的所有可能就行了
                if(dp[j][i]){
                    //计算
                    ans  = ans%mod + dp[j][i]*(powerMod(2,a[i+1])-1)%mod;
                }
            }

该模块代码:

void Dp(int len_s,int len_t)
{
    for(int i = 0;i<=len_s;i++) dp[0][i] = 1;//s的第i个字符之前(包括自己)与 t一个都不匹配的数量是1

    for(int i = 1;i<=len_s;i++){
        for(int j = 1;j<=len_t;j++){
            dp[j][i] = dp[j][i-1]%mod;
            if(s[i] == t[j])
                dp[j][i] += dp[j-1][i-1]%mod;//如果该位相同,会多出上一位匹配的总可能数。
            if(j == len_t){
                dp[j][i] = (dp[j-1][i-1]%mod)*(s[i] == t[j]);
                //如果是完整串,单次计算,只需要上一位的所有可能就行了
                if(dp[j][i]){
                    //计算
                    ans  = ans%mod + dp[j][i]*(powerMod(2,a[i+1])-1)%mod;
                }
            }
        }
    }
}


AC代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<string>
using namespace std;
typedef long long int ll;

const int MAX_N = 1e5+10;
const ll mod = 998244353;
ll a[MAX_N];
ll dp[10][MAX_N];
ll ans = 0;
string s;
string t= "$nunhehheh";

inline void init()
{
    ans = 0;
    memset(a,0,sizeof(a));
    memset(dp,0,sizeof(dp));
}

void get_a(int len)
{
    for(int i = len;i>0;i--){
        a[i] = a[i+1];
        if(s[i] == 'a') a[i] += 1;
    }
}

//快速幂
ll powerMod(ll a,ll b)
{
	ll ans=1;
	a=a%mod;
	while(b>0)
	{
		if(b%2==1)  ans=(ans*a)%mod;
		b=b/2;
		a=(a*a)%mod;
	}
	return ans;
}


void Dp(int len_s,int len_t)
{
    for(int i = 0;i<=len_s;i++) dp[0][i] = 1;//s的第i个字符之前(包括自己)与 t一个都不匹配的数量是1

    for(int i = 1;i<=len_s;i++){
        for(int j = 1;j<=len_t;j++){
            dp[j][i] = dp[j][i-1]%mod;
            if(s[i] == t[j])
                dp[j][i] += dp[j-1][i-1]%mod;//如果该位相同,会多出上一位匹配的总可能数。
            if(j == len_t){
                dp[j][i] = (dp[j-1][i-1]%mod)*(s[i] == t[j]);
                //如果是完整串,单次计算,只需要上一位的所有可能就行了
                if(dp[j][i]){
                    //计算
                    ans  = ans%mod + dp[j][i]*(powerMod(2,a[i+1])-1)%mod;
                }
            }
        }
    }
}

int main()
{
    int T;
    cin>>T;
    
    while(T--){
        init();
        cin>>s;
        s = "$"+s;
        int len_s = s.length()-1;
        int len_t = t.length()-1;
        get_a(len_s);
        Dp(len_s,len_t);
        cout<<ans%mod<<endl;
    }
    return 0;
}

第二种之后再写思路,先贴个AC代码(朋友写的)

空间复杂度:O(n)

AC代码: 

#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
ll num[100050];
ll numa[100050];
const ll mod = 998244353;

inline ll PowerMod(ll a, ll b, ll c){
	ll ans = 1;
	a = a % c;
	while(b>0){
		if(b % 2 == 1)
			ans = ((ans % c) * (a % c)) % c;
		b = b/2;
		a = (a * a) % c;
	}
	return ans;
}

int main(void){
    string aim = "nunhehheh";
    int len = aim.length();
    int t;
    cin>>t;
    while(t--){
        ll ans = 0;
        string str;
        cin>>str;
        int l = str.length();
        memset(num, 0, sizeof(num));
        memset(numa, 0, sizeof(numa));
        for(int i = l-1;i >= 0;i--){
            if(str[i] == 'a') numa[i] = numa[i + 1] + 1;
            else numa[i] = numa[i + 1];
        }
        num[0] = 1;
        for(int i = 0;i < l;i++){
            for(int j = len;j>0;j--){
                if(str[i] == aim[j-1]){
                    if(j == len)  ans = (ans + (((PowerMod(2, numa[i], mod) - 1) % mod) * num[len-1]) % mod) % mod;
                    else{
                        num[j] = (num[j] + num[j-1]) % mod;
                    }
                }
            }
        }
        cout<<ans % mod<<endl;
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Twilight Sparkle.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值