2024CCPC网络赛题目(二)包含

2024CCPC网络赛题目(二)包含D J E

所有题目链接

D 编码器-解码器
解题思路

考虑每次迭代,每次实则就是用一个本身去更新自己,再用一个字符去进行更新(中间的字符)

那么可以使用区间dp,使得更新后能将进行到i个字符时t字符串中所有的区间情况找到。

没学过区间dp的可以看区间dp链接

在迭代时,只需要使用区间合并的方式,将两个相同性质的区间合并即可,分配的方案数显然已经确立

对于新增加的字符,正常dp处理即可。

值得注意的是,由于需要使用自身对自进行dp,所以不用继承之前的影响
∑ j = 1 m ∑ k = j m ∑ c = j − 1 k − 1 { d p [ i ] [ j ] [ k ] = ( d p [ i ] [ j ] [ k ] + d p [ i − 1 ] [ j ] [ c ] ∗ d p [ i − 1 ] [ c + 1 ] [ k ] ) ; , ( d p [ i ] [ j ] [ k ] + = d p [ i − 1 ] [ j ] [ c ] ∗ d p [ i − 1 ] [ c + 2 ] [ k ] ∣ 当 s 1 [ i ] = = s 2 [ c + 1 ] 时 ) \sum_{j=1}^{m}\sum_{k=j}^{m}\sum_{c=j-1}^{k-1}\begin{cases}dp[i][j][k] = (dp[i][j][k]+dp[i-1][j][c]*dp[i-1][c+1][k]); \\, (dp[i][j][k]+=dp[i-1][j][c]*dp[i-1][c+2][k]|当s1[i]==s2[c+1]时)\end{cases} j=1mk=jmc=j1k1{dp[i][j][k]=(dp[i][j][k]+dp[i1][j][c]dp[i1][c+1][k]);,(dp[i][j][k]+=dp[i1][j][c]dp[i1][c+2][k]s1[i]==s2[c+1])

代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	string s;
	string t;
	cin>>s>>t;
	int len1 = s.length();
	int len2 = t.length();
	s = ' '+s;
	t = ' '+t;
	int dp[len1+10][len2+10][len2+10];
	memset(dp,0,sizeof dp);
	for(int i=0;i<len1+1;i++) for(int j=1;j<len2+2;j++) for(int k=0;k<j;k++)	dp[i][j][k] = 1;
	
	for(int i=1;i<len1+1;i++)
		for(int j=1;j<len2+1;j++)
			for(int k=j;k<len2+1;k++)
			{
				for(int c=j-1;c<=k;c++)
				{
					dp[i][j][k] = (dp[i][j][k]+dp[i-1][j][c]*dp[i-1][c+1][k])%mod;
				}
				for(int c=j-1;c<k;c++) if(s[i]==t[c+1])
				{
					dp[i][j][k] = (dp[i][j][k]+dp[i-1][j][c]*dp[i-1][c+2][k])%mod;
				}
			}
	cout<<dp[len1][1][len2];
}
J 找最小
解题思路

涉及到异或的一般都和线性基有关系,没学过的最好去学一下。本题也是用到了。
1 、首先求出所有 a i 以及 b i 的异或和,分别记为 s 1 , s 2 2 、之后求出所有 a i ⊕ b i 的线性基 1、首先求出所有a_i以及b_i的异或和,分别记为s_1,s_2\\ 2、之后求出所有a_i⊕b_i的线性基 1、首先求出所有ai以及bi的异或和,分别记为s1,s22、之后求出所有aibi的线性基
在有了上面的条件基础后,从s1和s2的最高位到低位依次遍历,记为f1,f2。
{ 1 、 f 1 = f 2 = 1 : 如果存在该位的线性基,则将 s 1 ⊕ = 该位的线性基,和 s 2 ⊕ = 该位的线性基 2 、 f 1 = f 2 = 0 : 继续遍历 3 、分别求出 m i n ( 异或线性基的,不异或线性基的 ) \begin{cases} 1、f1=f2=1:如果存在该位的线性基,则将s1⊕=该位的线性基,和s2⊕=该位的线性基\\ 2、f1=f2=0:继续遍历\\ 3、分别求出min(异或线性基的,不异或线性基的)\\ \end{cases} 1f1=f2=1:如果存在该位的线性基,则将s1=该位的线性基,和s2=该位的线性基2f1=f2=0:继续遍历3、分别求出min(异或线性基的,不异或线性基的)
当1 0,或0 1 时情况比较复杂,需要选择异或线性基,或者不异或线性基两种情况分别求出最终的结果。

假如出现了上面这种01 10的情况后,在做出选择后,针对后面位的处理,则就是这样了
{ 1 、 f 1 = f 2 = 1 : 如果存在该位的线性基,则将 s 1 ⊕ = 该位的线性基,和 s 2 ⊕ = 该位的线性基 2 、 f 1 = f 2 = 0 : 继续遍历 3 、一个 1 一个 0 的情况 , 尽量使得大异或的 1 消失 \begin{cases} 1、f1=f2=1:如果存在该位的线性基,则将s1⊕=该位的线性基,和s2⊕=该位的线性基\\ 2、f1=f2=0:继续遍历\\ 3、一个1一个0的情况,尽量使得大异或的1消失\\ \end{cases} 1f1=f2=1:如果存在该位的线性基,则将s1=该位的线性基,和s2=该位的线性基2f1=f2=0:继续遍历3、一个1一个0的情况,尽量使得大异或的1消失

代码实现
#include <bits/stdc++.h>
using namespace std;
const int max_bit=31;
int bit[max_bit+1];
void insert(int n)
{
	for(int i=max_bit;i>=0;i--) 
	{
		if((n>>i)&1==1)
		{
			if(!bit[i])
			{
				bit[i] = n;
				break; 
			}
			else n^=bit[i];
		}
	}
}

int dfs(int pos,int s1,int s2) 
{
	if(pos==-1)
		return max(s1,s2);
	int f1 = (s1>>pos)&1;
	int f2 = (s2>>pos)&1;
	// 最高位 0 0情况 不作处理 
	if(f1==0&&f2==0) dfs(pos-1,s1,s2);
	// 最高位 1 1情况 尝试变0 
	else if(f1==1&&f2==1) dfs(pos-1,s1^bit[pos],s2^bit[pos]);
	else
	{
		//当属于大值且有1时,尝试变0 
		if(f1&&s1>s2||f2&&s1<s2) dfs(pos-1,s1^bit[pos],s2^bit[pos]);
		//照常处理 
		else dfs(pos-1,s1,s2); 
	}
}

void solve()
{
	int n;
	cin>>n;
	memset(bit,0,sizeof bit);
	vector<int> a(n+1),b(n+1);
	int s1=0,s2=0;
	//分别求a,b的异或,以及 ai与bi的线性基 
	for(int i=1;i<=n;i++) cin>>a[i],s1^=a[i];
	for(int i=1;i<=n;i++) cin>>b[i],s2^=b[i];
	for(int i=1;i<=n;i++) insert(a[i]^b[i]);
	//从最高位开始判断
	for(int i=max_bit;i>=0;i--)
	{
		int f1 = (s1>>i)&1;	
		int f2 = (s2>>i)&1;
		// 最高位 0 0情况 不作处理 
		if(f1==0&&f2==0) continue;
		// 最高位 1 1情况 尝试变0 
		else if(f1==1&&f2==1) s1^=bit[i],s2^=bit[i]; 
		else 
		{
			//如果有10,01的情况则分别求其值,取最小的即可。 
			cout<<min(dfs(i-1,s1,s2),dfs(i-1,s1^bit[i],s2^bit[i]))<<"\n";
			return;
		}
	} 
	cout<<max(s1,s2)<<"\n";
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	int t;
	cin>>t;
	while(t--) solve();
}
E 随机过程
解题思路

首先考虑节点字典树的最大值,
总共 26 个字母,显然对于深度为 i 的层,最多可包含 2 6 i 次个节点 总共26个字母,显然对于深度为i的层,最多可包含26^i次个节点 总共26个字母,显然对于深度为i的层,最多可包含26i次个节点
由于一个叶子节点,是由一个字符串决定的,为了保证最大值,要保证它们各不相同,也就是当满足
2 6 i < n 时,意味着该层是可以被填满的 2 6 i > = n 时 , 意味着该层以及之后的所有层都最多只能包含(满足条件的层数量 ∗ n ) 26^i<n时,意味着该层是可以被填满的\\ 26^i>=n时,意味着该层以及之后的所有层都最多只能包含(满足条件的层数量*n) 26i<n时,意味着该层是可以被填满的26i>=n,意味着该层以及之后的所有层都最多只能包含(满足条件的层数量n
由上面的思路就可以推出解决最大值的公式
∑ i = 0 m m i n ( n , 2 6 i ) \sum_{i=0}^{m}min(n,26^i) i=0mmin(n,26i)
之后考虑节点字典树的期望值

一个节点想要存在,期望值就是这m个串包含这个节点的前缀
可以这样考虑一个节点想成为其他串的前缀,也就意味着 P 2 = 1 − P 1 ( p 1 :它不能成为其他串节点的概率 ) 可以这样考虑一个节点想成为其他串的前缀,也就意味着P_2=1-P_1(p_1:它不能成为其他串节点的概率) 可以这样考虑一个节点想成为其他串的前缀,也就意味着P2=1P1(p1:它不能成为其他串节点的概率)

而一个串包含一个前缀的概率为 P 3 , 那么它不包含这个前缀的概率就为 P 4 = 1 − P 3 那么所有串都不包含这个前缀的概率就为 ( P 4 ) n 而一个串包含一个前缀的概率为P_3,那么它不包含这个前缀的概率就为P_4=1-P_3\\ 那么所有串都不包含这个前缀的概率就为(P_4)^n 而一个串包含一个前缀的概率为P3,那么它不包含这个前缀的概率就为P4=1P3那么所有串都不包含这个前缀的概率就为(P4)n

就可以把问题简化成求每层的每个节点存在的概率 , P 2 = 1 − ( P 4 ) n 就可以把问题简化成求每层的每个节点存在的概率,P_2 = 1-(P_4)^n 就可以把问题简化成求每层的每个节点存在的概率,P2=1P4)n

对于每一层,进行这样的迭代,考虑该层的节点,是否能成为这m个串的前缀P_s^i,i表明了这个层下,该节点在某个串出现的概率
p S i = ( 1 2 6 i ) p_S^i = (\frac{1}{26^i}) pSi=(26i1)
有了上述条件之后,该层的所有节点数目*P_2

公式也就是
∑ i = 0 m 2 6 i ∗ ( 1 − ( 1 − ( 1 26 ) i ) n ) \sum_{i=0}^{m}26^i*(1-(1-(\frac{1}{26})^i)^n) i=0m26i(1(1(261)i)n)

解题代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using ll=long long;
const ll maxl=1e5+10;
ll max26[maxl];

const ll mod = 998244353;

ll qsm(ll n, ll m) {
    ll res = 1;  // 改为 long long 类型
    for(; m; m >>= 1, n = n * n % mod) {
        if(m & 1) {
            res = res * n % mod;
        }
    }
    return res;  // 返回结果
}


signed main()
{
	ios::sync_with_stdio(false); 
	cin.tie(0);
	cout.tie(0);
	
	max26[0]=1;
	int n,m;
	cin>>n>>m;
	for(int i=1;i<maxl;i++) max26[i] = max26[i-1]*26%mod;
	//计算期望值) 
	//计算每一层的期望//即都不希望成为别的前缀和的概率 
	ll ans1=1,ans2=0;
	ll inv26 = qsm(26,mod-2);
//	cout<<inv26<<"\n";
	for(int i=1;i<=m;i++)
	{
		ans1+=max26[i]*((1-qsm((1-qsm(inv26,i)+mod)%mod,n)+mod)%mod)%mod;
		ans1%=mod; 
	}
	//省去了第0层  
	ans2 = 1;
	int tem = 1;
    for (int i = 1; i <= m; i++) {
        tem *= 26;
        if (tem < n) ans2 += tem, ans2 %= mod;
        else {
            ans2 += (m - i + 1) * n % mod;
            ans2 %= mod;
            break;
        }
    }
	cout<<ans2<<" "<<ans1<<"\n";
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值