The 2024 CCPC Online Contest(B,D,E,J,K,L)

比赛链接

这场坠机了,除了 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} Si1 所有的字母的转移矩阵乘起来就是这个串的转移矩阵,而 S i ′ = S i − 1 ′ + s i + S i − 1 ′ S'_i=S'_{i-1}+s_i+S'_{i-1} Si=Si1+si+Si1,我们按顺序把转移矩阵乘起来就推出了 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=Si1+si+Si1 时, T T T [ i , j ] [i,j] [i,j] 区间被 S i ′ S'_i Si 匹配可能是由前一个 S i − 1 ′ S'_{i-1} Si1 匹配 [ i , k ] [i,k] [i,k] 区间,后一个 S i − 1 ′ S'_{i-1} Si1 匹配 [ 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} Si1 匹配 [ i , k − 1 ] [i,k-1] [i,k1] 区间,后一个 S i − 1 ′ S'_{i-1} Si1 匹配 [ 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[t1][i][k]dp[t1][k+1][j]+dp[t1][i][k1]dp[t1][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=2km ( 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值