2024CCPC网络赛题目(一)包含L,K,B,I

2024CCPC网络赛题目(一)包含L,K,B,I

所有题目链接

题目链接

L 网络预选赛
思路

签到题,输入数据后,暴力枚举即可

解题代码
// L
#include<bits/stdc++.h> 

using namespace std;
using ll = long long;
int ans=0;
char g[510][510];

int main()
{
	ios::sync_with_stdio(false) ;
	cin.tie(0);
	cout.tie(0);
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) 
		for(int j=1;j<=m;j++)
			cin>>g[i][j];
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
	{
		if(g[i-1][j]=='c'&&g[i-1][j]==g[i-1][j+1]&&g[i-1][j+1]==g[i][j+1]&&g[i][j]=='p')
			ans++;
	}
	cout<<ans;
	return 0;
 } 
K 取沙子游戏
思路

首先遍历一定范围内n值,比如140时,k从1n的获胜情况,通过观察,寻找规律。再使用规律来完成题解。

首先可以确定的时当,n为奇数时,Alice必赢,因为当k=1时,有奇数个1,代表了Alice赢。所以只用看n取偶数时的情况

探索代码

遍历所有情况的代码

#include<bits/stdc++.h> 
using namespace std;
using ll = long long;

int lowbit(int x){
	return x&-x;
}
//探索思路 
int dfs(int n,vector<int>& v,int p,int cnt,vector<int> path)
{
	//cnt1表示乙选择
	//cnt0表示甲选择 
	if(n==0)
	{
//		cout<<"数量"<<path.size()<<"\n";
//		for(int i=0;i<path.size();i++)
//			cout<<path[i]<<"->";
		int anc = path.size()%2;
//		cout<<"\n";
//		if(anc)
//			cout<<"甲"; 
//		else
//			cout<<"乙";
//		cout<<"\n" ;
		//返回的是获胜情况 在该路情况下 1甲赢 0乙赢 
		return anc;
	}
	for(int i=0;i<=p;i++)
		if(n-v[i]<0) continue;
		else 
		{
			vector<int> path2=path;
			path2.push_back(v[i]) ;
			int m =dfs(n-v[i],v,i,1-cnt,path2);
			//如果返回的是自己赢,那肯定会选自己 
			if(m==cnt)
				return cnt;
		}
	//都没有找到能让自己赢的方法,就返回对手赢 
	return 1- cnt;
}

int main()
{
	int n,k;
	for(int z=2;z<100;z+=2) 
	{
		n=z;
		k=z; 
		bool find = false;
		cout<<"n:"<<n<<"\n" ;
		for(int i=1;i<k;i++)
		{
			vector<int> v;
			vector<int>path;
//			cout<<"公因子:";
			for(int j=1;j<=i;j++) if(i%j==0) v.push_back(j);
//			for(int k=0;k<v.size();k++) cout<<v[k]<<" ";
//			cout<<"\n";
			path.push_back(i);
			string anss = dfs(n-i,v,v.size()-1,0,path)==1?"甲":"乙";
//			cout<<"dfs:"<<anss<<" "<<"\n";
			if(anss.compare("甲")==0)	
			{
				bitset<8> binary_rep(n); // 假设是8位二进制数,根据实际情况调整位数  
				cout<<"最低位为"<<(lowbit(n))<<"\n";
				cout << binary_rep << "\n";
				cout<<"k="<<i<<"时"<<" ";
				cout<<anss<<"赢\n";
				cout<<"\n" ;
				cout<<(lowbit(n))<<" "<<i<<"\n" ;
				if(lowbit(n)==i) 
				{
					cout<<"yes\n";
				}
				else
				{
					cout<<"no\n";
				} 
				find = true;
				break;
			}
		}
		
		if(!find) 
		{
			cout<<"乙赢了" <<"\n\n" ;
		}
	}
}

这是前30的情况

n:2
乙赢了

n:4
乙赢了

n:6
最低位为2
00000110
k=2时 甲赢

2 2
yes
n:8
乙赢了

n:10
最低位为2
00001010
k=2时 甲赢

2 2
yes
n:12
最低位为4
00001100
k=4时 甲赢

4 4
yes
n:14
最低位为2
00001110
k=2时 甲赢

2 2
yes
n:16
乙赢了

n:18
最低位为2
00010010
k=2时 甲赢

2 2
yes
n:20
最低位为4
00010100
k=4时 甲赢

4 4
yes
n:22
最低位为2
00010110
k=2时 甲赢

2 2
yes
n:24
最低位为8
00011000
k=8时 甲赢

8 8
yes
n:26
最低位为2
00011010
k=2时 甲赢

2 2
yes
n:28
最低位为4
00011100
k=4时 甲赢

4 4
yes
n:30
最低位为2
00011110
k=2时 甲赢

观察以上代码时,发现规律,当k为偶数时,只要k至少大于等于自己二进制的最低位时,甲可以获胜。

一个数的二进制最低位的含义

比如6,6的二进制是110,那么它最低为也就是10,也就是2

比如7,7的二进制是111,那么它最低为就是1,也就是1

解题方程

A l i c e 必赢 { 当 n 为奇数时, 当 n 为偶数时且 l o w b i t ( n ) < = k 时, Alice必赢\begin{cases} 当n为奇数时,\\ 当n为偶数时且lowbit(n)<=k时, \end{cases} Alice必赢{n为奇数时,n为偶数时且lowbit(n)<=k时,

解题代码
#include<bits/stdc++.h> 
using namespace std;
using ll = long long;
//返回一个数最低为的函数
int lowbit(int x){
	return x&-x;
}
//Problem K. 取沙子游戏
int main() 
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin>>t;	
	while(t--)
	{
		int n,k;
		cin>>n>>k;
		bool F=false;
		if(n&1)
		{
			F=true;
		}
		else
		{
			if(lowbit(n)<=k)
			{
				F=true;
			}
		}
		if(F)
		{
			cout<<"Alice"<<"\n";
		}
		else
			cout<<"Bob"<<"\n";
	}
}
B 军训 Ⅱ
思路

思路,为了取得最小的身高极差,显然当将序列降序或升序时,即可。

9 9 8 2 4 4 3 5 3

就会变为

2 3 3 4 4 5 8 9 9

由于同一身高的人,可以随意调换位置,所以应该将每个身高的排序情况相乘(An取n,也就是A的阶乘)得到答案。
∏ i = 1 n ! a i , 其中 a i 表示身高为 i 的人数 \prod_{i = 1}^{n}!a_i,其中a_i表示身高为i的人数 i=1n!ai,其中ai表示身高为i的人数

解题方程

考虑到降序和升序也是两种不同的情况,但只对于有多组同身高的情况,所以有以下分类
{ 2 ∗ ! ∏ i = 1 n ! a i ,当有多组不同情况身高时 ! a i ,当只有一组身高的情况时 \begin{cases} 2*!\prod_{i = 1}^{n}!a_i,当有多组不同情况身高时\\ !a_i,当只有一组身高的情况时 \end{cases} {2!i=1n!ai,当有多组不同情况身高时!ai,当只有一组身高的情况时

解题代码
//B
#include<bits/stdc++.h> 
#define int long long
using namespace std;
using ll = long long;

const int mod = 998244353;
unordered_map<int,int> mcap;
int Am_n(int m,int n) 
{
	int ans=1;
	int mm = m;
	while(n--)
	{
		if(mcap.count(m))
		{
			//为了避免溢出,需要取模 
			ans = (ans*mcap[m])%mod;
			break;
		}
		ans=(ans*m--)%mod;
	}
	mcap[m] = ans;
//	cout<<ans<<"\n";
	return ans;
}
signed main()
{
	int n;
	ios::sync_with_stdio(false) ;
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	vector<int> a(n);
	for(int i=0;i<n;i++) cin>>a[i];
	sort(a.begin(),a.end());
	int ans=0;
	for(int i=0;i<n-1;i++)
		for(int j=i+1;j<n;j++)
			ans+=a[j]-a[i];
	//统计连续的个数
	int cnts=1;
	int cnt=1;
	vector<int> m;
	for(int i=1;i<n;i++) 
	{	
		if(a[i]==a[i-1]) cnt++;
		else
		{
			m.push_back(cnt);
//			cout<<a[i-1]<<" "<<cnt<<"\n";
			cnt=1;
			cnts++;
		}
	}
	m.push_back(cnt);
	int anss=1;
	for(int i=0;i<m.size();i++)
	{
		int h  =Am_n(m[i],m[i]); 
//		cout<<"个数"<<h<<"\n";
		anss= (anss*h)%mod;
//		cout<<anss<<"\n";
	}
	if(cnts>1)
		anss = (anss+anss)%mod;			//为了避免溢出,需要取模 
	cout<<ans<<" "<<anss<<"\n";
}
I 找行李
思路(参考官方题解)
官方题解思路:

有一个很好的性质,先将人和行李排序,如果第 i 个人选了行李 j,则第 i 个人往后的人选 j 以前的行
李,对答案一定是没有贡献的,则有个朴素dp:fi,j,k 表示前 i 个人,最后一个被选的行李是 j,在 j 之
前有 k 个行李还没有被选,则每次转移枚举选哪个行李即可(或者不选)。时间复杂度 O(n4)。
但是并不好优化,换一个思路:令 g(d) 表示答案大于等于 d 的方案数,则答案为 ∑∞
d=1 g(d),因为一个
方案的答案为 x,则会被 g(1) 到 g(x) 分别统计一次。
考虑求一个 g(d),每个人可以选的行李个数是确定的,就是左边距离他大于等于 d 的行李们,且第 i 个
人能选的行李集合一定是第 i + 1 个人的子集,则可以dp:fi,j 表示前 i 个人选 j 个行李的方案数,转移
只需要考虑选和不选。时间复杂度 O(n3)。

理解思路

就是,我们首先将人的位置进行排序,并将处于行李同一位置的数量进行统计。

d=n的意思,可以理解为过了ns后,行李与人可以产生配对的情况。

观看一遍这个很好的性质:[有一个很好的性质,先将人和行李排序,如果第 i 个人选了行李 j,则第 i 个人往后的人选 j 以前的行
李]再来理解下面的语言
假设过了 d s ,判断某个人能够选择哪些行李,显然是根据 b i ( 第 i 个人 ) 的位置,以及过了的时间 d s ,来决定的。 满足条件 : b i − d > = a i ( 表示第 i 个物品 ) , 该行李可以被这个人选择 为了快速得到哪些行李,是满足上面条件的,可以使用统计过后的行李数量,再对其求前缀和的方式,来快速拿到满足条件的所有行李数量 f [ i ] 表示的是,在有 i − 1 个人的情况下,能有多少种匹配情况 d p [ i ] 表示的是,在有 i 个人的情况下,能有多少种匹配情况 假设过了ds,判断某个人能够选择哪些行李,显然是根据b_i (第i个人)的位置,以及过了的时间ds,来决定的。\\ 满足条件:b_i-d>=a_i(表示第i个物品),该行李可以被这个人选择\\ 为了快速得到哪些行李,是满足上面条件的,可以使用统计过后的行李数量,再对其求前缀和的方式,来快速拿到满足条件的所有行李数量\\ f[i]表示的是,在有i-1个人的情况下,能有多少种匹配情况\\ dp[i]表示的是,在有i个人的情况下,能有多少种匹配情况 假设过了ds,判断某个人能够选择哪些行李,显然是根据bi(i个人)的位置,以及过了的时间ds,来决定的。满足条件:bid>=ai(表示第i个物品),该行李可以被这个人选择为了快速得到哪些行李,是满足上面条件的,可以使用统计过后的行李数量,再对其求前缀和的方式,来快速拿到满足条件的所有行李数量f[i]表示的是,在有i1个人的情况下,能有多少种匹配情况dp[i]表示的是,在有i个人的情况下,能有多少种匹配情况
假设:过了ds
遍历 b i 假设 b 1 有 n 个满足条件的行李 , 那么它只能影响一个人匹配行李的情况,因为目前只有它一个人正在做选择, 所以它只能对 d p [ 1 ] 造成影响 , 所以 d p [ 1 ] + = n ∗ f [ 0 ] , 含义就是在先前情况 ( f [ 0 ] ) 选择的基础上,进行选择 更新 f , 变为更新后的 d p 遍历 b i + 1 假设 b 2 有 m 个满足条件的行李 , 那么它会影响一个人匹配行李的情况以及两个人匹配行李的情况, 所以它不仅会对 d p [ 1 ] 造成影响还会对 d p [ 2 ] 造成影响 d p [ 1 ] + = m ∗ f [ 0 ] , 影响一个人匹配行李的情况 d p [ 2 ] + = ( m − 1 ) ∗ f [ 0 ] 影响两个人个人匹配行李的情况【在有一个人选择的情况下,显然可选择的行李数量少了 1 个】 遍历b_i\\ 假设b_1有n个满足条件的行李,那么它只能影响一个人匹配行李的情况,因为目前只有它一个人正在做选择,\\ 所以它只能对dp[1]造成影响,所以dp[1]+=n*f[0],含义就是在先前情况(f[0])选择的基础上,进行选择\\ 更新f,变为更新后的dp\\ 遍历b_{i+1}\\ 假设b_2有m个满足条件的行李,那么它会影响一个人匹配行李的情况以及两个人匹配行李的情况,\\ 所以它不仅会对dp[1]造成影响还会对dp[2]造成影响\\dp[1]+=m*f[0],影响一个人匹配行李的情况\\dp[2]+=(m-1)*f[0]影响两个人个人匹配行李的情况【在有一个人选择的情况下,显然可选择的行李数量少了1个】 遍历bi假设b1n个满足条件的行李,那么它只能影响一个人匹配行李的情况,因为目前只有它一个人正在做选择,所以它只能对dp[1]造成影响,所以dp[1]+=nf[0],含义就是在先前情况(f[0])选择的基础上,进行选择更新f,变为更新后的dp遍历bi+1假设b2m个满足条件的行李,那么它会影响一个人匹配行李的情况以及两个人匹配行李的情况,所以它不仅会对dp[1]造成影响还会对dp[2]造成影响dp[1]+=mf[0],影响一个人匹配行李的情况dp[2]+=m1f[0]影响两个人个人匹配行李的情况【在有一个人选择的情况下,显然可选择的行李数量少了1个】
同时最大匹配情况为min(可匹配行李的数量,人数)

将所有满足匹配条件的b_I匹配过后,就得到在该时刻,满足匹配情况的所有情况数量。
假设经过累加求和过后的数量,为 c n t [ ] 举个例子,比如有行李数据 1 , 1 , 3 , 3 , 5 那么正常的话 c n t [ ] = [ 0 , 2 , 0 , 2 , 0 , 1 ] 经过累加求和过后 c n t [ ] = [ 0 , 2 , 2 , 4 , 4 , 5 ] 假设经过累加求和过后的数量,为cnt[]\\ 举个例子,比如 有行李 数据 1,1,3,3,5 那么正常的话cnt[]=[0,2,0,2,0,1] 经过累加求和过后 cnt[]=[0,2,2,4,4,5] 假设经过累加求和过后的数量,为cnt[]举个例子,比如有行李数据1,1,3,3,5那么正常的话cnt[]=[0,2,0,2,0,1]经过累加求和过后cnt[]=[0,2,2,4,4,5]

解题方程

k = m i n ( s i z e ( b ) , c n t [ b i − d ] ) − − 表示人最大能匹配行李数量的匹配数 ∑ d = 1 505 ( ∑ i s i z e ( b ) 当 ( b i − d > = 0 时 ( ∑ j = k 1 d p [ i ] + = f [ i − 1 ] ∗ j ) ) , k = min(size(b),cnt[b_i-d])--表示人最大能匹配行李数量的匹配数 \\\sum_{d=1}^{505}(\sum_{i}^{size(b)}当(bi-d>=0时(\sum_{j=k}^{1}dp[i]+=f[i-1]*j)), k=min(size(b),cnt[bid])表示人最大能匹配行李数量的匹配数d=1505(isize(b)(bid>=0(j=k1dp[i]+=f[i1]j))

解题代码
#include<bits/stdc++.h> 
using namespace std;
#define int long long
vector<int>a(510);
vector<int>an(510);

const int lim =  998244353;
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n,m;
	cin>>n>>m;
	vector<int>b(m);
    //对处于某一位置的行李进行统计
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		++an[a[i]];
	}
    //进行累计前缀和
	for(int i=1;i<=500;i++) an[i] +=an[i-1];
    //对人的位置进行升序
	for(int i=0;i<m;i++)cin>>b[i];	
	sort(b.begin(),b.end()) ;
	int ans=0;
	//将d从1~510开始遍历
	for(int d=1;d<510;d++) 
	{
		//用于维护的dp数组,表示了前n个人匹配行李的情况
		vector<int> dp(m+1,0);
        //初始化//0个人选择行李时,有一种情况【便于后续计算】
		dp[0] =1;
		//p有b的数量轮次,显然第n轮最多能选到n人, 
		for(auto p:b) 
		{ 
			//用于记录前一轮的人的选择情况 
			auto ddp = dp;
            
			//求取过了该时间ds后,该人可选择的行李的情况
			int num = (p-d>=0?an[p-d]:0);
		    if(num!=0)
            {	
//            	std::cout<<"当前num:"<<num<<"\n";
			}
			//显然最多 只可能出现在前n轮人选择最多不超过m的情况下。(最大就是一人匹配一个行李) 
			for(int i=1;i<min(num,m)+1;i++) 
			{
                //
				dp[i] =(dp[i] + ddp[i-1]*(num - i + 1))%lim;
//				cout<<"dpk:"<<i<<" "<<dp[i]<<" "<<"\n" ;
			}
		}
		bool first =false;
		for(auto c:dp)
		{
			if(!first)
			{
				first = true;
				continue;
			}
            //对过了s后,满足条件的所有匹配情况进行统计
			ans=(ans+c)%lim;
		}
//		cout<<"\n";
	}
	cout<<ans<<"\n";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值