2022牛客寒假算法基础集训营1

寒假第一场比赛,检验自己,相比去年2021寒假集训营有了较大进步了,下面我按照自己的做题顺序做个记录

L 牛牛学走路(签到)

在这里插入图片描述
这是一道签到题,我们直接上代码,注意输出格式小数点后的位数

代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 2e5+10;
int t, n, m;
string str;
PII p;
double ans;
void solve(){
	cin>>t;
	while(t--){
		str = "", ans = 0;
		cin>>n>>str;
		p.first = 0, p.second = 0;
		for(int i = 0;i < n;i++){
			if(str[i] == 'U'){
				p.first += 1;
			}
			else if(str[i] == 'D'){
				p.first -= 1;
			}
			else if(str[i] == 'L'){
				p.second -= 1;
			}
			else{
				p.second += 1;
			}
			double len = sqrt(abs(p.first*p.first)+abs(p.second*p.second));
			ans = max(ans,len);
		}
		printf("%.12lf\n",ans);
	}
}
int main(){
	solve();
	return 0;
}

E 炸鸡块君的高中回忆

在这里插入图片描述

分析+代码

这道题我们如果循环模拟的话肯定是会超时的,所以我们就可以尝试寻找他的规律,我们发现我们能够成功进入学校的时间一定是奇数,同时我们可以推出一个公式: n ≤ m ∗ c n t − c n t + 1 n\leq m*cnt-cnt+1 nmcntcnt+1时,我们可以成功让所有人都进入校园,那么就有 c n t ≥ n − 1 m − 1 cnt\geq\frac{n-1}{m-1} cntm1n1,最终我们得到的答案为 a n s = c n t × 2 − 1 ans=cnt\times2-1 ans=cnt×21
那么我们就可以通过

cnt = ceil(1.0*(n-1)/(m-1));

求得 c n t cnt cnt的结果, c n t cnt cnt表示进入学校的次数。

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 2e5+10;
int t, n, m;
ll ans;
void solve(){
	cin>>t;
	while(t--){
		ans = 0;
		cin>>n>>m;
		if(m==1&&n > 1)cout<<-1<<endl;
		else if(m >= n)cout<<1<<endl;
		else if(m < n){
			int cnt = ceil(1.0*(n-1)/(m-1));
			ans = cnt*2-1;
			cout<<ans<<endl;
		}
	}
}
int main(){
	solve();
	return 0;
}

H 牛牛看云

在这里插入图片描述

分析+代码

这道题有一看 1 e 6 1e6 1e6的数据范围,那么肯定是不能暴力的,比赛结束以后我看了一下大众的做法和我的不一样,我在这就只给出我的方法的代码了。
我们需要求得的是 ∑ i = 1 n ∑ j = i n ∣ a i + a j − 1000 ∣ \sum_{i=1}^{n} \sum_{j=i}^{n}\lvert a_i+a_j-1000 \rvert i=1nj=inai+aj1000的结果。
我首先是打表发现,我们输入的数据排序后与排序前得出的结果是一样的,那么我们不妨将输入的数据从小到大排序。同时我用 s u m sum sum数组作前缀和数组,记录前 i i i个的和,注意这个记录的是排序以后的。接着我用 v i m vim vim数组存储在我们输入的数据数组中第一个大于等于 1000 − n u m [ i ] 1000-num[i] 1000num[i]的数的位置,那么在这个 v i m [ i ] vim[i] vim[i]以前的位置的数字加上 n u m [ i ] num[i] num[i]必然是小于 1000 1000 1000的,那么我们前面从 i ∼ v i m [ i ] − 1 i\sim vim[i]-1 ivim[i]1的范围内的结果就是 ( 1000 − n u m [ i ] − n u m [ j ] ) (1000-num[i]-num[j]) (1000num[i]num[j])而在这之前总共会有 v i m [ i ] − i vim[i]-i vim[i]i个类似的结果,也就是
∑ j = i v i m [ i ] ( 1000 − n u m [ i ] − n u m [ j ] ) \sum_{j=i}^{vim[i]}(1000-num[i]-num[j]) j=ivim[i](1000num[i]num[j])
那么我可以将其转化为
1000 × ( v i m [ i ] − i ) − n u m [ i ] ∗ ( v i m [ i ] − i ) − ( s u m [ v i m [ i ] − 1 ] − s u m [ i − 1 ] ) 1000\times(vim[i]-i)-num[i]*(vim[i]-i)-(sum[vim[i]-1]-sum[i-1]) 1000×(vim[i]i)num[i](vim[i]i)(sum[vim[i]1]sum[i1])
而在 v i m [ i ] vim[i] vim[i]之后的即为 ∑ j = v i m [ i ] + 1 n ( n u m [ i ] + n u m [ j ] − 1000 ) \sum_{j=vim[i]+1}^{n}(num[i]+num[j]-1000) j=vim[i]+1n(num[i]+num[j]1000)。将其转化为
s u m [ n ] − s u m [ v i m [ i ] − 1 ] + n u m [ i ] × ( n − v i m [ i ] + 1 ) − 1000 × ( n − v i m [ i ] + 1 ) sum[n]-sum[vim[i]-1]+num[i]\times(n-vim[i]+1)-1000\times(n-vim[i]+1) sum[n]sum[vim[i]1]+num[i]×(nvim[i]+1)1000×(nvim[i]+1)
但是我们要算绝对值内的值小于0的前提还有就是要 i ≥ v i m [ i ] i\geq vim[i] ivim[i],所以我们就会有如下核心代码

int pos = vim[i];
		if(i < pos){
			ans += (1000*(pos-i)-num[i]*(pos-i)-(sum[pos-1]-sum[i-1]));
			ans += (sum[n]-sum[pos-1]+num[i]*(n-pos+1)-1000*(n-pos+1));
		}
		else 
			ans += (sum[n]-sum[i-1]+num[i]*(n-i+1)-1000*(n-i+1));

代码如下

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 1e6+10;
int t, n, m;
int num[N];
int sum[N];
int vim[N];
ll ans;
void solve(){
	scanf("%d",&n);
	for(int i = 1;i <= n;i++)scanf("%d",&num[i]);
	sort(num+1,num+n+1);
	for(int i = 1;i <= n;i++){
		sum[i] = sum[i-1]+num[i];
		vim[i] = lower_bound(num+1,num+n+1,1000-num[i])-(num);
		// cout<<vim[i]<<" ";
	}
	for(int i = 1;i <= n;i++){
		int pos = vim[i];
		if(i < pos){
			ans += (1000*(pos-i)-num[i]*(pos-i)-(sum[pos-1]-sum[i-1]));
			ans += (sum[n]-sum[pos-1]+num[i]*(n-pos+1)-1000*(n-pos+1));
		}
		else 
			ans += (sum[n]-sum[i-1]+num[i]*(n-i+1)-1000*(n-i+1));
	}
	cout<<ans<<endl;
}
int main(){
	solve();
	return 0;
}

J小朋友做游戏

在这里插入图片描述

分析+代码

根据出题人的做法:先将两种小朋友的幸福度分别按从大到小排序,记为 A A A B B B数组,那么最优的方案一定是从 A A A B B B中各选一个前缀,因此可以求出两个数组的前缀和,然后枚举从 A A A中选了多少人(从 B B B中选的人数等于总人数减去 A A A中的),利用前缀和 𝑂 ( 1 ) 𝑂(1) O(1)的获得此时的总幸福度,对于圆圈紧挨着的限制,其实就相当于限制闹腾小朋友最多选 n 2 \frac{n}{2} 2n个,在上面枚举的时候加入该限制即可;
我的做法略显复杂,不过也是类似于如此。
直接上代码了。

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 1e4+10;
int t, a, b, n, m;
int ans;
int va[N], vb[N];
bool cmp(int x, int y){
	return x > y;
}
void solve(){
	cin>>t;
	while(t--){
		memset(va,0,sizeof(va));
		memset(vb,0,sizeof(vb));
		ans = 0;
		cin>>a>>b>>n;
		for(int i = 1;i <= a;i++)cin>>va[i];
		for(int i = 1;i <= b;i++)cin>>vb[i];
		sort(va+1,va+a+1,cmp);sort(vb+1,vb+b+1,cmp);
		if((n%2==1&&a<=n/2)||(n%2==0&&a<n/2)){
			cout<<-1<<endl;continue;
		}
		int ta = 1, tb = 1;
		for(int i = 1;i <= n;i++){
			if(tb > n/2){
				ans += va[ta++];
			}
			else{
				if(va[ta]<vb[tb]){
					ans += vb[tb++];
				}
				else{
					ans += va[ta++];
				}
			}
		}
		cout<<ans<<endl;
	}	
}
int main(){
	solve();
	return 0;
}

出题人做法

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<map>
#define ll long long
#define mem(s,i) memset(s,i,sizeof(s))
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 1e4+10;
const int mod = 1e9+7;
int t, n, m, ans;
int A[N], B[N];
bool cmp(int x, int y){
	return x > y;
}
void solve(){
	cin>>t;
	while(t--){
		mem(A,0), mem(B,0);
		int a, b, x;
		cin>>a>>b>>x;
		for(int i = 1;i <= a;i++)cin>>A[i];
		for(int i = 1;i <= b;i++)cin>>B[i];
		sort(A+1,A+a+1,cmp);sort(B+1,B+b+1,cmp);
		for(int i = 1;i <= a;i++)A[i] += A[i-1];
		for(int i = 1;i <= b;i++)B[i] += B[i-1];
		ans = -1;
		for(int i = 1;i <= x;i++){
			int ta = i, tb = x-i;
			if(ta<=a&&tb<=b&&tb<=x/2)
				ans = max(ans,A[ta]+B[tb]);
		}cout<<ans<<endl;
	}
}
int main(){
	solve();
	return 0;
}

F中位数切分

在这里插入图片描述
这个题有两种方法,一个是找规律简单做法,一个是用树状数组+离散化+DP的做法,我只会简单的,所以就用简单做法了。
简单做法就是找规律,规律每个人找的可能不一样。就不多说了,直接上代码了。

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 1e5+10;
int t, n, m;
int num[N];
void solve(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i = 1;i <= n;i++)scanf("%d",&num[i]);
		sort(num+1,num+n+1);
		int pos = lower_bound(num+1,num+n+1,m)-num;
		// cout<<pos<<endl;
		if(pos > (n/2+n%2)){
			printf("-1\n");
		}
		else{
			printf("%d\n",n-pos*2+2);
		}
	}
}
int main(){
	solve();
	return 0;
}

A九小时九个人九扇门

在这里插入图片描述

分析加代码

  • D P DP DP的一道题,当时没写起,赛后补上的。
    我们需要知道,一个数的数字根等于这个数对 9 9 9取模的结果,特别的。取模为 0 0 0那么数字根为 9 9 9
    我们就可以将问题转化为从 n n n个数中取出一些数字,让这些数字的和对 9 9 9取模得 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 0,1,2,3,4,5,6,7,8 0,1,2,3,4,5,6,7,8有多少种选法。这个时候我们就想到 01 背 包 01背包 01类型的 D P DP DP问题了,我们定义一个 d p [ i ] [ j ] dp[i][j] dp[i][j]表示在我们考虑了前 i i i个数,选择一些数使得求和对 9 9 9取模的 j j j的方案数。
    重点是状态转移方程,如下所示。
			m = (j+num[i])%9;
			ans[i][m] = ans[i-1][m];
			ans[i][m] += ans[i-1][j];
			ans[i][m] %= mod;

代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define PII pair<int, int>
#define INF 0x7fffffff
using namespace std;
const int N = 1e5+10;
const int mod = 998244353;
int t, n, m;
int num[N];
int ans[N][15];
void solve(){
	cin>>n;
	for(int i = 1;i <= n;i++)cin>>num[i], num[i]%=9;
	for(int i = 1;i <= n;i++){
		for(int j = 0;j < 9;j++){
			m = (j+num[i])%9;
			ans[i][m] = ans[i-1][m];
			ans[i][m] += ans[i-1][j];
			ans[i][m] %= mod;
		}
		ans[i][num[i]]++;
	}
	for(int i = 1;i <= 9;i++)cout<<ans[n][i%9]<<" ";
}
int main(){
	solve();
	return 0;
}

暂时补题到这,后续会继续更新。
希望下次再接再厉!

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值