2021-2022年度第三届全国大学生算法设计与编程挑战赛(冬季赛)-正式赛 部分题解

比赛链接

http://oj.saikr.com/contest/19

B.Error

思路

这道题其实我们贪心加上二分就能做了,首先我们贪心得把 b [ 1 ] b[1] b[1] 变得很小,然后构造 b [ i ] b[i] b[i] 的时候也是贪心的去构造,尽可能比 b [ i − 1 ] b[i-1] b[i1]大1,如果不行的话就让 b [ i ] = b [ i ] − e p s b[i] = b[i] - eps b[i]=b[i]eps ,然后我们对这个 e p s eps eps 的值进行二分查找就好了,详情请看代码

代码

#include<bits/stdc++.h>
using namespace std;
//----------------自定义部分----------------
#define ll long long
#define mod 1000000007
#define endl "\n"
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define int long long
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

ll ksm(ll a,ll b) {
	ll ans = 1;
	for(;b;b>>=1LL) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}

ll lowbit(ll x){return -x & x;}

const int N = 2e6+10;
//----------------自定义部分----------------
int t,n,m,q,a[N],b[N];

bool check(int eps){
	for(int i = 1;i <= n; ++i) b[i] = a[i];
	b[1] -= eps;
	b[1] = max(1LL,b[1]);
	for(int i = 2;i <= n; ++i) {
		if(b[i] + eps <= b[i - 1]) return false;
		if(b[i] - eps > b[i - 1])
				b[i] -= eps;
		else {
			if(b[i] + eps > b[i-1])
				b[i] = b[i-1] + 1;
			else return false;
		}
		b[i] = max(0LL,b[i]);
	}
//	for(int i = 1;i <= n; ++i) cout<<b[i]<<" \n"[i == n];
	return true;
}

void slove(){
	cin>>n;
	for(int i = 1;i <= n; ++i) cin>>a[i];
	int l = 0,r = 1e9+10;
	while(l < r) {
		int mid = l + r >> 1LL;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	cout<<r<<endl;
}

signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	t = 1;
	while(t--){
		slove();
	}
	
	return 0;
}

E.吃利息

思路

按照他说的一步一步模拟就好了,注意的是:

  • 先计算利息,再加上多的5金币
  • 利息最多5块

代码

#include<bits/stdc++.h>
using namespace std;
//----------------自定义部分----------------
#define ll long long
#define mod 1000000007
#define endl "\n"
#define PII pair<int,int>
#define INF 0x3f3f3f3f

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

ll ksm(ll a,ll b) {
	ll ans = 1;
	for(;b;b>>=1LL) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}

ll lowbit(ll x){return -x & x;}

const int N = 2e6+10;
//----------------自定义部分----------------
int t,k,n,m,q,a[N];
void slove(){
	cin>>k>>n;
	ll ans = k,cnt = 0;
	for(int i = 1;i <= n; ++i) {
		ans += min(ans/10,5LL);
		ans += 5;
	}
	cout<<ans<<endl;
}

int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	t = 1;
	while(t--){
		slove();
	}
	
	return 0;
}

F.Star

思路

计算凸包连边的概率就好,然后正常求凸包面积
ps:代码是借用的别人的模板

代码

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

//----------------自定义部分----------------
#define ll long long
#define mod 1000000007
#define endl "\n"
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define int long long

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
ll t;
ll ksm(ll a,ll b) {
	ll ans = 1;
	for(;b;b>>=1LL) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}

ll lowbit(ll x){return -x & x;}

//----------------自定义部分----------------
int n;
const int maxn=55;

struct point{
	double x,y;
	point (double a=0,double b=0):x(a),y(b){};
	point operator + (const point &a)const{
		return point (x+a.x,y+a.y);
	}
	point operator - (const point &a)const{
		return point (x-a.x,y-a.y);
	}
	double operator * (const point &a)const{
		return x*a.y-y*a.x;
	}
	friend double dist(const point &a){
		return a.x*a.x+a.y*a.y;
	}
	bool operator < (const point &a)const{
		if (x!=a.x) return x<a.x;
		else return y<a.y;
	}
};

class Constellation{
private:
	double pr[maxn];
	point nod[maxn];
	int n;
public:
	double expectation(vector <double> x, vector <double> y, vector <double> prob){
		double ans=0;
		n=x.size();
		for (int i=0;i<n;++i){
			nod[i]=point(x[i],y[i]);
			pr[i]=prob[i];
		}
		
		//枚举所有有向边。 
		for (int i=0;i<n;++i)
			for (int j=0;j<n;++j) if (i!=j){
			double tmp=1;
			point use=nod[j]-nod[i];
			
			//计算这条边在凸包上的概率 
			for (int k=0;k<n;++k) if (i!=k&&j!=k){
				if (use*(nod[k]-nod[i])>0) tmp*=(1-pr[k]);
				else if (use*(nod[k]-nod[i])>-1e-12&&dist(nod[i]-nod[k])<dist(nod[j]-nod[i]))
					tmp*=(1-pr[k]);
			}
			
			//累计答案 
			ans+=tmp*(nod[i]*nod[j])*pr[i]*pr[j];
		}
		
		//注意要除以2 ... 
		ans/=2;
		return fabs(ans);
	}
}CC;

void slove(){
	vector <double> x,y,prob;
	double a,b,c;
	cin>>n;
	for(int i = 0;i < n; ++i){
		cin>>a>>b>>c;
		x.push_back(a);
		y.push_back(b);
		prob.push_back(c);
	}
	cout << fixed << setprecision(6);
	cout<<CC.expectation(x,y,prob)<<endl;
		
}

signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	t = 1LL;
	while(t--) {
		slove();
	}
	
	return 0;
}

G.MP4

思路

实际上就是求解字典序第 k k k 小的数字,可以在leetcode找到原题:440. 字典序的第K小数字 ,实际上这题做法很多样,可以直接搜索,也可以看规律模拟(不知道为啥我的模拟写炸了,对拍到1e7的数据都没问题)

代码

模拟代码:

#include<bits/stdc++.h>
using namespace std;
//----------------×Ô¶¨Ò岿·Ö----------------
#define ll long long
#define mod 1000000007
#define endl "\n"
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define int long long

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

ll ksm(ll a,ll b) {
	ll ans = 1;
	for(;b;b>>=1LL) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}

ll lowbit(ll x){return -x & x;}

const int N = 2e6+10;
//----------------×Ô¶¨Ò岿·Ö----------------
int t,n,m,q,a[N];

map<int,bool> vis;

void slove(){
	cin>>n;
	int cnt = 0;
	int p;
	for(int i = 1LL;i <= 9LL; ++i) {
		p = n;
		int j = i,c = 1LL;
		for(;j <= p; j *= 10LL,c *= 10LL){
			if(vis[j]) continue;
			vis[j] = true;
			cnt++;
			cout<<j<<".mp4"<<endl;
			if(cnt == 50) return;
		}
		if(j > p)
			j/=10LL,c/=10LL;
		for(int l = j;l > 1LL; l/=10LL,c/=10LL){
			int k = 1LL;
			j = l;
			while(j < n && k < c) {
				j++;
				k++;
				if(j % 10LL == 0LL && vis[j/10] == false) 
					cout<<(j/10LL)<<".mp4"<<endl,cnt++;
				if(cnt == 50LL) return;
				if(vis[j] == false){
					cout<<j<<".mp4"<<endl;
					cnt++;
					if(cnt == 50LL) return;
				}
			}
		}
	}
	
	
	
}

signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	t = 1;
	while(t--){
		slove();
	}
	
	return 0;
}

搜索代码:

#include<bits/stdc++.h>
using namespace std;
//----------------自定义部分----------------
#define ll long long
#define mod 1000000007
#define endl "\n"
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define int long long

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

ll ksm(ll a,ll b) {
	ll ans = 1;
	for(;b;b>>=1LL) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}

ll lowbit(ll x){return -x & x;}

const int N = 2e6+10;
//----------------自定义部分----------------
int t,n,m,q;

class Solution {
public:
	int getSteps(int curr, long n) {
		int steps = 0;
		long first = curr;
		long last = curr;
		while (first <= n) {
			steps += min(last, n) - first + 1;
			first = first * 10;
			last = last * 10 + 9;
		}
		return steps;
	}
	
	int findKthNumber(int n, int k) {
		int curr = 1;
		k--;
		while (k > 0) {
			int steps = getSteps(curr, n);
			if (steps <= k) {
				k -= steps;
				curr++;
			} else {
				curr = curr*10;
				k--;
			}
		}
		return curr;
	}
}pp;


signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i = 1;i <= 50; ++i) {
		cout<<pp.findKthNumber(n,i)<<".mp4"<<endl;
	}
	
	return 0;
}

I.展览

思路

由于是代码填空题,并且注释写的非常清楚你要怎么做,所以大家看代码就好啦

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=1e6+5;
ll n,b[N],a[N],ans;
//b[i]表示二进制下的第i位
void update(ll x){
	for(ll i=60;i>=0;i--){
		if(x>>i){//如果x在二进制表示下含有第i位
			if(b[i])x^=b[i];//如果b[i]存在则让x^b[i],
			//因为之前b[i]也是由已经保存过的a[]数组贡献的
			//所以,这样异或x可以看作x于之前的a[]数组进行异或
			//然后一直异或到为0或者当前b[i]还没有被赋值
			else {  b[i]=x;break;}//否则b[i]赋值为x,
			//表示当前二进制下的第i位可以被异或出来,且x的最高位就是i
		}
	}
}

int main(){
	cin>>n;
	for(ll i=1;i<=n;i++){cin>>a[i];update(a[i]);}//读入数据对于每一个数字都下放来维护b[i]
	for(ll i=60;i>=0;i--)if((ans^(1ll<<i))>ans)ans^=b[i];
	//贪心的过程,ans看作一个二进制数,从高位开始,如果b[i]存在,
	//肯定优先跟b[i]异或,倒着让小值不会影响到大值
	cout<<ans<<endl;
	return 0;
}

K.礼物

思路

排序,然后输出最大即可

代码

#include<bits/stdc++.h>
using namespace std;
//----------------自定义部分----------------
#define ll long long
#define mod 1000000007
#define endl "\n"
#define PII pair<int,int>
#define INF 0x3f3f3f3f

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

ll ksm(ll a,ll b) {
	ll ans = 1;
	for(;b;b>>=1LL) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
	}
	return ans;
}

ll lowbit(ll x){return -x & x;}

const int N = 2e6+10;
//----------------自定义部分----------------
int t,n,m,q,a[N];
void slove(){
	cin>>n;
	for(int i = 0;i < n; ++i) cin>>a[i];
	sort(a,a+n);
	cout<<a[n-1]<<endl;
}

int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	t = 1;
	while(t--){
		slove();
	}
	
	return 0;
}

L.看错题

思路

直接每次都从上往下查询的话复杂度就是 n 2 l o g 2 n n^2log_2 n n2log2n显然不行的,我们其实只需要按照他的要求从每个叶子节点到根节点求一次初始状态的和ans,然后再后面的 update 的过程中动态更新ans 就好了,注意的是如果我们在一个完全包含我们修改区间的时候,直接计算一下权值更新和就好了,实际上到后面你会发现完全不用维护每个节点的区间和,只需要计算区间之间的关系即可,详情请看update 函数中的操作

代码

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

#define ll long long

const int N = 2e5+10;

ll a[N];
ll n,m;

ll b[N<<2];

struct Tnode{
	ll l,r,sum,lazy;
};
ll ans = 0;
struct SegmentTree{
	
	Tnode tree[N<<2];
	
	inline void init_lazy(ll root){//清空lazy标记
		tree[root].lazy = 0;
	}
	
	inline void push_up(ll root){//向上更新
		push_down(root<<1);
		push_down(root<<1 | 1);
		tree[root].sum = tree[root<<1].sum + tree[(root<<1) | 1].sum;
	}
	inline void push_down(ll root){//向下更新
		if(tree[root].lazy != 0){
			tree[root].sum += tree[root].lazy * (tree[root].r - tree[root].l + 1);
			if(tree[root].l != tree[root].r){//第二步,下推lazy标记
				tree[root<<1].lazy += tree[root].lazy;
				tree[(root<<1) | 1].lazy += tree[root].lazy;
			}
			init_lazy(root);//第三步,清空root的lazy标记,因为已经下推过了
		}
	}
	
	void build(ll l,ll r,ll root){//建树
		tree[root].l = l;
		tree[root].r = r;
		init_lazy(root);//初始化lazy节点
		if(l == r) tree[root].sum = a[l];
		else{
			ll mid = ((r+l) >> 1);
			build(l,mid,root<<1);
			build(mid+1,r,(root<<1)+1);
			push_up(root);
		}
	}
	void update(ll l,ll r,ll root,ll add){//区间更新,在 [l,r] 区间都加上add
		push_down(root);//向下更新,因为有可能还存在lazy节点没更新
		if(tree[root].l == tree[root].r){
			tree[root].sum += add;
			ans += add;
			return;
		}
		else if(tree[root].l >= l && tree[root].r <= r){
			tree[root].sum += add;//这里的懒标记直接标记为add就好了
			ll len = b[root] * (2LL * b[root] - 1LL);
			ans += add * len;
		}
		else{
			if(tree[root].l <= l && tree[root].r >= r){
				ans += add * (r - l + 1) * b[root];
			} 
			else if(tree[root].l <= r){
				ans += add * (max(min(tree[root].r,r) - max(tree[root].l,l) + 1LL,0LL)) * b[root];
			}
			ll mid = ((tree[root].r+tree[root].l) >> 1);
			if(l <= mid) {
				update(l,r,root<<1,add);
			}
			if(r > mid)  {
				update(l,r,(root<<1)+1,add);
			}
			push_up(root);//向上更新
		}
	}
	ll query(ll l,ll r,ll root) {//区间查询
		push_down(root);//下推标记,可能还有lazy节点没更新
		if(tree[root].l >= l && tree[root].r <= r) return tree[root].sum;
		else{
			ll mid = tree[root].l + ((tree[root].r-tree[root].l) >> 1);
			ll ans = 0LL;
			if(l <= mid) ans += query(l,r,root<<1);//如果我们查询区间的左边界比当前节点的中间点小,那么说明查询区间要往左走
			if(r > mid)  ans += query(l,r,(root<<1) + 1);//如果我们查询的右边界要比当前节点的中间节点大,那么说明查询区间要往右走
			return ans;
		}
	}
	ll find(ll root) {
		push_down(root);
		if(tree[root].l == tree[root].r) return tree[root].sum;
		ll mid = tree[root].l + ((tree[root].r-tree[root].l) >> 1);
		ll ans = 0LL;
		ans += find(root<<1);
		ans += find((root<<1) + 1);
		ans += tree[root].sum * b[root];//(r - l + 1);//
		return ans;
	}
}Tree;

void dfs(ll root) {
	if(root >= n) {
		b[root] = 1;
		return;
	}
	ll l = root,r = root;
	while(l * 2LL < n * 2) l *= 2LL;
	while((r * 2LL + 1) < n * 2) r = r * 2LL + 1;
	dfs(root<<1);
	dfs((root<<1) | 1);
	b[root] = r - l + 1;
	
}



int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i = 1;i <= n; ++i) cin>>a[i];
	Tree.build(1,n,1);
	dfs(1LL);
	ans = Tree.find(1LL);
	
	ll op,k,l,r;
	for(int t = 1;t <= m; ++t){
		cin>>l>>r>>k;
		Tree.update(l,r,1LL,k);
		cout<<ans<<endl;
//		cout<<Tree.find(1LL)<<endl;
	}
}

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
CSP-S2提高组是中国计算机学会(CCF)主办的一项全国性计算机竞,旨在挑选出优秀的高中生并为他们提供提高计算机科学和编程能力的平台。2021年第二轮题解分为以下几个部分。 第一题是关于石头游戏的思考。题目给出了一堆石头的数量,两位玩家轮流选择石头进行取走,每次可以取走1个或者2个石头,最后无法继续取走者输掉游戏。通过观察可以发现,如果一开始给定的石头数量是3的倍数,那么第一个选手必胜;否则,第一个选手无法必胜。这是因为无论第一个选手怎么选取,第二个选手总可以使得每一轮选取后的石头数量保持在3的倍数。因此,只需要判断起始时石头数量是否为3的倍数即可。 第二题是关于好书的购买。题目给出了若干种书的价格和折扣情况,要求在有限的预算下买到尽可能多的书籍。这是一个经典的背包问题。使用动态规划算法可以解决,按照价格从小到大的顺序遍历书籍,设置一个二维数组dp[i][j]表示在前i本书中,花费j的预算能够买到的最多书籍数量。状态转移方程为:dp[i][j]=max(dp[i-1][j], dp[i-1][j-price[i]]+1)。最终的结果即为dp[n][budget],其中n为书籍总数,budget为预算。 第三题是关于均匀生成所有正整数的问题。题目给出了一个区间[L, R],要求输出在该区间内存在的所有正整数。首先通过观察可以发现,对于任意的正整数x,若2x在区间[L, R]内,那么x也在该区间内;若2x+1在区间[L, R]内,那么x也在该区间内。基于这个思路,可以使用递归的方式实现。若L<=R,则输出L,然后递归输出从2*L到R的所有整数。若L>R,则结束递归。 以上就是CSP-S2提高组2021第二轮题解的简要概述。希望这些解题思路对参与竞的同学有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MangataTS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值