这场坠机了,除了 L L L 是签到,其他全都沾点数学,给我们队干不会了。而且 C , J , K C,J,K C,J,K 的数学思路很新颖奇特,初见杀了。
B 军训 II
思路:
其实可以猜到不整齐度最小的时候应该是整段升序或降序的时候。证明可以看官方题解:
所以我们对原数组排个序,然后看怎么找方案数。发现相同的数可以互相交换位置,假设某个数个数为 c n t cnt cnt,那么排列的可能就是 c n t cnt cnt 的全排列也就是 A c n t c n t = c n t ! A_{cnt}^{cnt}=cnt! Acntcnt=cnt!。所以我们预处理一下阶乘,然后找到每个数的个数,算出 ∏ c n t i ! \prod cnt_{i}! ∏cnti! 即可。注意正序和逆序也是不同的方案,所以最后要乘以 2 2 2,不过如果所有数都相同的话,就不存在正序逆序之分了,就不能乘 2 2 2 了。
算不整齐度因为 n = 1000 n=1000 n=1000,所以直接 O ( n 2 ) O(n^2) O(n2) 暴力枚举左右端点计算即可。
code:
#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e3+5;
int n,a[maxn];
map<int,int> mp;
ll mi,ans;
ll fac[maxn];
int main(){
fac[0]=1;
for(int i=1;i<=1e3;i++)fac[i]=fac[i-1]*i%mod;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
mp[a[i]]++;
}
sort(a+1,a+n+1);
ans=mp.size()==1?1:2;
for(auto [ai,cnt]:mp)
ans=(ans*fac[cnt])%mod;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
mi+=a[j]-a[i];
cout<<mi<<" "<<ans<<endl;
return 0;
}
D 编码器-解码器
思路1(矩阵乘法):
我们设 f i f_i fi 表示匹配 T T T 串前 i i i 个字母的方案数,假如 T T T 串是 b a ba ba,然后先匹配上了一个 b b b,那么 [ f 2 f 1 f 0 ] → [ f 2 f 1 + f 0 f 0 ] [f_2\ f_1\ f_0]\rightarrow[f_2\ f_1+f_0\ f_0] [f2 f1 f0]→[f2 f1+f0 f0],再匹配一个 a a a,那么 [ f 2 f 1 f 0 ] → [ f 2 + f 1 f 1 f 0 ] [f_2\ f_1\ f_0]\rightarrow[f_2+f_1\ f_1\ f_0] [f2 f1 f0]→[f2+f1 f1 f0],这很有矩阵乘法的感觉,所以我们可以用矩阵乘法来做这个题,把每个字母化成一个转移矩阵,给关于 f i f_i fi 的列向量乘上一个字符的转移矩阵就相当于匹配了这个字符。
转移矩阵的配平可以看 牛客竞赛 的讲解。
比如对 T T T 串为 b a ba ba 时,字符 b b b 的转移矩阵就是 [ 1 0 0 1 1 0 0 0 1 ] \left[\begin{array}{c} 1 & 0 & 0\\ 1 & 1 & 0\\ 0 & 0 & 1 \end{array}\right] 110010001 ,因为 [ 1 0 0 1 1 0 0 0 1 ] ∗ [ f 0 f 1 f 2 ] = [ f 0 f 0 + f 1 f 2 ] \left[\begin{array}{c} 1 & 0 & 0\\ 1 & 1 & 0\\ 0 & 0 & 1 \end{array}\right]*\left[\begin{array}{c} f_0\\ f_1\\ f_2 \end{array}\right]=\left[\begin{array}{c} f_0\\ f_0+f_1\\ f_2 \end{array}\right] 110010001 ∗ f0f1f2 = f0f0+f1f2 ,同理 a a a 的转移矩阵就是 [ 1 0 0 0 1 0 0 1 1 ] \left[\begin{array}{c} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 1 & 1 \end{array}\right] 100011001 ,因为 [ 1 0 0 0 1 0 0 1 1 ] ∗ [ f 0 f 1 f 2 ] = [ f 0 f 1 f 1 + f 2 ] \left[\begin{array}{c} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 1 & 1 \end{array}\right]*\left[\begin{array}{c} f_0\\ f_1\\ f_2 \end{array}\right]=\left[\begin{array}{c} f_0\\ f_1\\ f_1+f_2 \end{array}\right] 100011001 ∗ f0f1f2 = f0f1f1+f2 。我们匹配多个字符就相当于多个转移矩阵相乘,比如先匹配 a a a 再匹配 b b b 就相当于 [ 1 0 0 0 1 0 0 1 1 ] ∗ [ 1 0 0 1 1 0 0 0 1 ] ∗ [ f 0 f 1 f 2 ] = [ f 0 f 0 + f 1 f 0 + f 1 + f 2 ] \left[\begin{array}{c} 1 & 0 & 0\\ 0 & 1 & 0\\ 0 & 1 & 1 \end{array}\right]*\left[\begin{array}{c} 1 & 0 & 0\\ 1 & 1 & 0\\ 0 & 0 & 1 \end{array}\right]*\left[\begin{array}{c} f_0\\ f_1\\ f_2 \end{array}\right]=\left[\begin{array}{c} f_0\\ f_0+f_1\\ f_0+f_1+f_2 \end{array}\right] 100011001 ∗ 110010001 ∗ f0f1f2 = f0f0+f1f0+f1+f2 。初始状态时 [ f 0 f 1 f 2 ] = [ 1 0 0 ] \left[\begin{array}{c} f_0\\ f_1\\ f_2 \end{array}\right]=\left[\begin{array}{c} 1\\ 0\\ 0 \end{array}\right] f0f1f2 = 100 ,所以结果是 [ 1 1 1 ] \left[\begin{array}{c} 1\\ 1\\ 1 \end{array}\right] 111 。
我们把 S i − 1 ′ S'_{i-1} Si−1′ 所有的字母的转移矩阵乘起来就是这个串的转移矩阵,而 S i ′ = S i − 1 ′ + s i + S i − 1 ′ S'_i=S'_{i-1}+s_i+S'_{i-1} Si′=Si−1′+si+Si−1′,我们按顺序把转移矩阵乘起来就推出了 S i ′ S'_i Si′ 的转移矩阵,这一步有点像矩阵快速幂,我们递推出 S n ′ S'_n Sn′ 的转移矩阵后再乘给 f i f_i fi 对应矩阵,取 f m f_m fm 那一项就是匹配到 T T T 串前 m m m 个字母的答案。
code:
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
string s,t;
int n,m;
using matrix=vector<vector<ll> >;
matrix mul(const matrix& a,const matrix& b){
int N=a.size(),M=b[0].size(),K=a[0].size();
matrix ans(N,vector<ll>(M));
for(int i=0;i<N;i++)
for(int j=0;j<M;j++){
auto &t=ans[i][j];
for(int k=0;k<K;k++){
t+=a[i][k]*b[k][j]%mod;
}
t%=mod;
}
return ans;
}
void print(matrix a){
for(int i=0;i<a.size();i++,puts(""))
for(int j=0;j<a[0].size();j++)
cout<<a[i][j]<<" ";
}
int main(){
cin>>s>>t;
n=s.length();m=t.length();
matrix ans(m+1,{0});
// print(ans);
ans[0][0]=1;
matrix base(m+1,vector<ll>(m+1));
for(int i=0;i<=m;i++)base[i][i]=1;
vector<matrix> c(26,base);
for(int i=1;i<=m;i++)
c[t[i-1]-'a'][i][i-1]=1;
for(int i=1;i<=n;i++){
base=mul(base,mul(c[s[i-1]-'a'],base));
}
ans=mul(base,ans);
cout<<ans[m][0]<<endl;
return 0;
}
思路2(动态规划):
我们设 d p [ t ] [ i ] [ j ] dp[t][i][j] dp[t][i][j] 表示 T T T 串的 [ i , j ] [i,j] [i,j] 区间匹配 S t ′ S'_{t} St′ 串的个数。
我们每次 S i ′ = S i − 1 ′ + s i + S i − 1 ′ S'_i=S'_{i-1}+s_i+S'_{i-1} Si′=Si−1′+si+Si−1′ 时, T T T 串 [ i , j ] [i,j] [i,j] 区间被 S i ′ S'_i Si′ 匹配可能是由前一个 S i − 1 ′ S'_{i-1} Si−1′ 匹配 [ i , k ] [i,k] [i,k] 区间,后一个 S i − 1 ′ S'_{i-1} Si−1′ 匹配 [ k + 1 , j ] [k+1,j] [k+1,j] 区间推来的。也有可能是中间的 s i s_i si 匹配了 T T T 串 [ i , j ] [i,j] [i,j] 区间中的第 k k k 个字符,由前一个 S i − 1 ′ S'_{i-1} Si−1′ 匹配 [ i , k − 1 ] [i,k-1] [i,k−1] 区间,后一个 S i − 1 ′ S'_{i-1} Si−1′ 匹配 [ k + 1 , j ] [k+1,j] [k+1,j] 区间推来的。
写成递推公式就是 d p [ t ] [ i ] [ j ] = ∑ d p [ t − 1 ] [ i ] [ k ] ∗ d p [ t − 1 ] [ k + 1 ] [ j ] + ∑ d p [ t − 1 ] [ i ] [ k − 1 ] ∗ d p [ t − 1 ] [ k + 1 ] [ j ] ∗ [ s t = = T k ] dp[t][i][j]=\sum dp[t-1][i][k]*dp[t-1][k+1][j]+\sum dp[t-1][i][k-1]*dp[t-1][k+1][j]*[s_t==T_k] dp[t][i][j]=∑dp[t−1][i][k]∗dp[t−1][k+1][j]+∑dp[t−1][i][k−1]∗dp[t−1][k+1][j]∗[st==Tk]。
然后递推即可。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=105;
int n,m;
string s,t;
ll dp[maxn][maxn][maxn];
int main(){
cin>>s>>t;
n=s.length();m=t.length();
s=" "+s;t=" "+t;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++)
dp[i][j+1][j]=1;
for(int _=1;_<=n;_++)
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++){
for(int k=i-1;k<=j;k++){
dp[_][i][j]+=dp[_-1][i][k]*dp[_-1][k+1][j]%mod;
dp[_][i][j]%=mod;
}
for(int k=i;k<=j;k++)
if(s[_]==t[k]){
dp[_][i][j]+=dp[_-1][i][k-1]*dp[_-1][k+1][j]%mod;
dp[_][i][j]%=mod;
}
}
cout<<dp[n][1][m]<<endl;
return 0;
}
E 随机过程
思路:
其实思路对上了就不是很难的数学题,官方讲解讲的也很好,我不讲了。
code:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod=998244353;
int n,m;
ll qpow(ll a,ll b){
ll base=a%mod,ans=1;
b%=mod;
while(b){
if(b&1)ans=base*ans%mod;
base=base*base%mod;
b>>=1;
}
return ans;
}
ll inv(ll x){return qpow(x,mod-2);}
ll maxx(){
ll ans=0;
ll i=0,t=1;
while(t<n && i<=m){
ans=(ans+t)%mod;
i++;
t*=26;
}
for(;i<=m;i++)ans=(ans+n)%mod;
return ans;
}
ll calc(){
ll ans=0;
for(int i=0;i<=m;i++){
ans+=(1-qpow(1-inv(qpow(26,i)),n))%mod*qpow(26,i)%mod;
}
return (ans%mod+mod)%mod;
}
int main(){
cin>>n>>m;
cout<<maxx()<<" "<<calc();
return 0;
}
J 找最小
思路:
贪心,线性基。
从高位向低位考虑,如果都是
1
1
1 那么考虑异或一下这一位的线性基,变成
0
0
0。如果都是
0
0
0 则跳过,如果一个
1
1
1 一个
0
0
0 ,那么不管怎么异或,结果的两个数肯定都是一个
1
1
1 一个
0
0
0,那么这一位是
1
1
1 的数一定更大(前面看过了都是一样的,后面不管怎么取都小于这一位)。那么我们让这一位是
1
1
1 的数后面尽可能小,最后最大值就会尽可能的小。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e6+5;
int T,n;
int a[maxn],b[maxn];
int ans1,ans2;
int p[40];
void init(){
memset(p,0,sizeof(p));
ans1=ans2=0;
}
void insert(int x){
for(int i=30;i>=0;i--){
if(x>>i&1){
if(!p[i]){
p[i]=x;
return;
}
else x^=p[i];
}
}
}
int main(){
cin.tie(0)->sync_with_stdio(false);
cin>>T;
while(T--){
cin>>n;
init();
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
for(int i=1;i<=n;i++){
ans1^=a[i];
ans2^=b[i];
insert(a[i]^b[i]);
}
for(int i=30;i>=0;i--){
if((ans1>>i&1)==0 && (ans2>>i&1)==0)continue;
else if((ans1>>i&1)==1 && (ans2>>i&1)==1){
ans1^=p[i];
ans2^=p[i];
}
else {
if(ans2>>i&1)swap(ans1,ans2);
for(int j=i-1;j>=0;j--){
if(ans1>>j&1){
ans1^=p[j];
ans2^=p[j];
}
}
break;
}
}
cout<<ans1<<endl;
}
return 0;
}
K 取沙子游戏
现场不是我们队这样推理的,而是这样:
假如 n n n 是奇数,那么先手取 1 1 1 就赢了,如果是偶数的话,显然不可能取 1 1 1,假如 n = 2 k ∗ m n=2^k*m n=2k∗m ( m 为奇数 ) (m为奇数) (m为奇数),那么拿 2 k 2^k 2k 就赢了,但是如果拿不到就输了。总结一下,就是必须要拿到 l o w b i t ( n ) lowbit(n) lowbit(n) 才能赢,如果拿不到就输了。
code:
#include <iostream>
#include <cstdio>
using namespace std;
int T,n,k;
int lowbit(int x){return x&-x;}
int main(){
cin>>T;
while(T--){
cin>>n>>k;
if(lowbit(n)<=k)cout<<"Alice\n";
else cout<<"Bob\n";
}
return 0;
}
L 网络预选赛
思路:
签到
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=505;
int n,m;
string s[maxn];
int ans=0;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=" "+s[i];
}
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
if(s[i][j]=='c' && s[i][j+1]=='c' && s[i+1][j]=='p' && s[i+1][j+1]=='c')
ans++;
cout<<ans<<endl;
return 0;
}