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=1∑mk=j∑mc=j−1∑k−1{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]时)
代码实现
#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、之后求出所有ai⊕bi的线性基
在有了上面的条件基础后,从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}
⎩
⎨
⎧1、f1=f2=1:如果存在该位的线性基,则将s1⊕=该位的线性基,和s2⊕=该位的线性基2、f1=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}
⎩
⎨
⎧1、f1=f2=1:如果存在该位的线性基,则将s1⊕=该位的线性基,和s2⊕=该位的线性基2、f1=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=0∑mmin(n,26i)
之后考虑节点字典树的期望值
一个节点想要存在,期望值就是这m个串包含这个节点的前缀
可以这样考虑一个节点想成为其他串的前缀,也就意味着
P
2
=
1
−
P
1
(
p
1
:它不能成为其他串节点的概率
)
可以这样考虑一个节点想成为其他串的前缀,也就意味着P_2=1-P_1(p_1:它不能成为其他串节点的概率)
可以这样考虑一个节点想成为其他串的前缀,也就意味着P2=1−P1(p1:它不能成为其他串节点的概率)
而一个串包含一个前缀的概率为 P 3 , 那么它不包含这个前缀的概率就为 P 4 = 1 − P 3 那么所有串都不包含这个前缀的概率就为 ( P 4 ) n 而一个串包含一个前缀的概率为P_3,那么它不包含这个前缀的概率就为P_4=1-P_3\\ 那么所有串都不包含这个前缀的概率就为(P_4)^n 而一个串包含一个前缀的概率为P3,那么它不包含这个前缀的概率就为P4=1−P3那么所有串都不包含这个前缀的概率就为(P4)n
就可以把问题简化成求每层的每个节点存在的概率 , P 2 = 1 − ( P 4 ) n 就可以把问题简化成求每层的每个节点存在的概率,P_2 = 1-(P_4)^n 就可以把问题简化成求每层的每个节点存在的概率,P2=1−(P4)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=0∑m26i∗(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";
}