2024年9月15日 CSP-S 个人练习总结

T1 水池

原题链接

题目大意

每一格水会往左,下,右运动,如果一片水在其运动方向上均有石头则判定为被储存(石头可以悬空),给出T组n,m,问对于每n个石头能否储存m格水,如果可以,输出n个石头最多储存多少水,如果不行,输出至少需要多少石头。

思路

用时约一小时二十分钟,70pts

看到这题,首先想到的是找规律然后对于每组数据O(1)或O(log)的时间搜索答案,可以看出这道题的答案有着明显的规律

差值001122334
n123456789
m00124691216

所以我们可以用一个程序来处理出一个答案数组

for(int i=1; i*2<maxn; i++){
	for(int j=1; j<=2; j++){
		int temp = (i-1)*2+2+j;
		a[temp] = a[temp-1]+i;
	}
}

这样就可以70pts了,事实上,一分都多得不了,要改成1e8+5,那就得MLE了。

注意到其实这个答案除了这个规律以外还满足单调性(单调不降,后面单调上升)

因此考虑二分答案。

代码

AC

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;

int check(int x){
	int water;
	if (x%2 == 0){
		water = (1 + (x-2)/2) * ((x-4)/2 + 1);
	}
	else{
		water = (1 + (x-2))/2 * ((x-3)/2 + 1);
	} 
	return water;
}

signed main(){
	int T;
	cin >> T;
	while(T--){
		int n, m;
		cin >> n >> m;
		int water = check(n);
		if(water >= m) cout << water << endl;
		else{
			int l = 3, r = 2e9+1;
			while(l < r-1){
				int mid = (l + r) / 2;
				if(check(mid) >= m) r = mid;
				else l = mid;
			}
			cout << r << endl;
		}
	}
} 

T2学习乘法

原题链接

题目大意

给出一个长度为n的数组,问取出任意多个数乘起来能凑出多少个1e6以内的数

思路

用时约一小时十分钟,全WA

注意到前一些数据范围较小,使用暴力枚举,用set装起来,看set的大小就行。

但是比赛时暴力枚举出错导致全错,赛后使用DFS进行枚举,并且使用桶代替了set,获得了60pts的好成绩XD,疑似数据过水,实际上,只要剪枝一下就可以轻松过60pts,但是看这个2e5感觉过不去,比较玄学。

代码

60pts:

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;

const int maxn = 1e6;
int n,ans;
vector<int> a;
vector<int> b(maxn+1);

void dfs(int u,long long s){
	if(s > maxn) return;
	if(u > n){
		if(s<=maxn) b[s]=1;
		return;
	}
	dfs(u+1,s*a[u]);
	dfs(u+1,s);
}

int main(){
	cin >> n;
	a = vector<int>(n+1);
	int flag = 1;
	for(int i=1; i<=n; i++){
		cin >> a[i];
		if(a[i] == 1) flag = 0;
	}
	dfs(1,1);
	
//	for(auto i:st){
//		cout << i << ' ';
//	}
//	cout << endl;
	
	ans=count(b.begin(),b.end(),1);
	cout << ans-flag << endl;
} 

T3开车

题目大意

开车,有限速,每次车速上下浮动最多1,能超速k次(k<2,k∈N),问车速和最快多少。

思路

先考虑不超速的注意到在每个单位时间的车速肯定有一部分是贴着限速的,设这些点叫做关键点,确保这些点周围的速度曲线尽可能为一个45°向上的线,如图:

红色为限速,橙色为关键点,绿色为非关键点的速度曲线,对于每一个符合要求的点,满足如果左边(右边)的点的限速大于他,则左边(右边)的点被降低到这个点的现速+1,这个效果只要前后扫一遍就可以。

接下来考虑超速的,我们可以考虑一个一个点变成正无限(这里是1e9),然后对这个曲线进行判断,找到最快的一种方式,实测40pts。

如何剪枝,只需要考虑关键点就可以了!因为非关键点哪怕变大了也没用。所以就可以做到剪枝,但是能搞hack,要是每一个点都是关键点,那就会被卡成n方,好在测试点十分温暖,我直接过了。

代码

AC

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;

vector<long long> a,b,c;
int n,k;

long long p(){//这里是处理用的函数
	for(int i=1; i<=n; i++){//前一遍
		if(b[i+1] > b[i]+1 && i != n){
			b[i+1] = b[i]+1;
		}
	}
	for(int i=n; i>=1; i--){//后一遍
		if(b[i-1] > b[i]+1 && i != 1){
			b[i-1] = b[i]+1;
		}
	}

	long long ans = 0;
	for(int i=1; i<=n; i++){
		ans += b[i];
	}
	return ans;
}

int main() {
    cin >> n >> k;
    a = vector<long long>(n+1);
	for(int i=1; i<=n; i++){
		cin >> a[i];
	}
	c = b = a;
	
	//这里开始-------------------------
	for(int i=1; i<=n; i++){
		if(c[i+1] > c[i]+1 && i != n){
			c[i+1] = c[i]+1;
		}
	}
	for(int i=n; i>=1; i--){
		if(c[i-1] > c[i]+1 && i != 1){
			c[i-1] = c[i]+1;
		}
	}
	//-------------------------这里结束
	//c是用来记录原来的处理结果的,可以删掉这里
	if(k==0) cout << p() << endl;
	else{
		long long ans = 0;
		for(int i=1; i<=n; i++){
			if(c[i] != a[i]) continue;//再删掉这里,就是40pts
			b=a;
			b[i] = 145410126;
			ans = max(ans,p());
		}
		cout << ans << endl;
	}
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值