【刷题日记】北京交通大学C语言积分赛第二轮题解

复盘小结

酣畅淋漓!!!怎一个爽字了得?
没学过数论,于是熬夜学了数论基础,并在6小时17发WA和TLE之后完成了题目!爽!
感受到数学美,智慧美了,回头更新数论的博客吧!
同样,按照我个人认为的难度升序排序(不过说实话其他四题,跟G比起来简直毫无难度!!!)

J Numbers

中文题面

给定一个数字A,求2~A-1进制中A的数位和之和。(3<=A<=1000)

题解

直接暴力。

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

i64 a,ans = 0,g;

i64 solve(i64 b){
	i64 aa = a, res = 0;
	while(aa){
		res += aa%b;
		aa /= b;
	}
	return res;
}

int main(){
	scanf("%lld",&a);
	for(i64 i = 2;i<a;i++)	ans += solve(i);
	g = __gcd(ans, a-2);
	printf("%lld/%lld",ans/g,(a-2)/g);
	return 0;
}

F Binary Imbalance

中文题面

t个测试样例。
输入m和一个长度为m的01串。你可以任意顺序、不限次数执行以下两个操作:
1.在两个相同数字之间插入1
2.在两个不同数字之间插入0
判断是否能使该串中的0的数量严格大于1的数量。输出YESNO
(1<=t<=100,1<=m<=100)

题解

注意到,能使该串中0的数量严格大于1的数量的充分必要条件是原串中有0。证明如下:
先证必要性。已知该串确实可以变为题述状态,推出原串中有0.可以证明其逆否命题:已知原串中没有0,推出该串不能变为题述状态。显然,如果原串没有0只有1,无法执行操作2,执行操作1也没有用,0的数量始终为0。所以必要性成立。
然后证充分性。已知原串中有0,分类讨论:
1.只有0没有1,显然0的数量严格大于1的数量。
2.有1也有0,那么一定存在01相邻构成不同相邻对。在不同相邻对之间插入0,又能构成新的不同相邻对。所以可以无限插入0,使得0的数量严格大于1的数量。
对于每个测试用例,判断有没有0即可。

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

string s;
i64 n,a;
bool flag;

int main(){
	cin >> n;
	while(n--){
		cin>>a;
		cin>>s;
		flag = 1;
		for(i64 i = 0;i<s.size();i++){
			if(s[i] == '0'){
				cout <<"YES\n";
				flag = 0;
				break;
			}
		}
		if(flag){
			cout<<"NO\n";
		}
	}
	return 0;
}

I Matryoshkas

中文题面

t个测试用例。
一串玩具是一个连续的整数序列。现多串玩具被拆开打乱成n个单个的整数ai,请还原出原来至少有多少串玩具。
(1<=t<=1e4,1<=n<=2e5,1<=ai<=1e9,所有t的n之和<=2e5)

题解

大概看了一下,觉得可能有很多方法。我选择最朴实无华的贪心法:升序排序,统计每个数字出现的次数,记录现在的串数。
如果该数比上一个大1,也就是可以合并,那么看数量,将串数调整为该数的出现次数,如果需要减少串数记得记录到答案里。
如果该数比上一个大更多,也就是断开了,那么前面所有串都记录到答案里。
为了便于处理结尾,我在最后加了一个无穷大的元素,用来终止最后剩下的所有串。
有点考数据结构的意思了……

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

i64 t,n,a[200100],ans,now_value,now_chains,now,num,last;

int main(){
	scanf("%lld",&t);
	while(t--){
		scanf("%lld",&n);
		for(i64 i = 0;i<n;i++){
			scanf("%lld",&a[i]);
		}
		sort(a,a+n);
		a[n] = 1e17;
		ans = now_chains = now_value = now = 0;
		last = -1;
		while(now<=n){
			num = 0;
			now_value = a[now];
			//printf("now:%lld %lld\n",now,a[now]);
			for(;a[now]==now_value;now++)	num++;
			if(now_value == last+1){	//原链可用
				if(num >= now_chains){
					now_chains = num;
				}
				else{
					ans += now_chains - num;
					now_chains = num;
				}
			}
			else{	//不可用
				ans += now_chains;
				now_chains = num;
			}
			last = now_value;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

H Okabe and Boxes

中文题面

现在有n个方块(编号从1到n)和一个空栈。小明要执行小红的命令。
命令有2*n条,其中n条是增加指令,n条是移除指令。
对于增加指令,输入一行字符串,格式为add i表示将i号方块添加到栈顶。(保证每个方块只会被添加一次,所有方块都会被添加)。
对于移除指令,输入一行字符串,为remove,第i次收到remove指令表示将i号方块从栈中移除。(保证该方块在栈中,但不一定在栈顶)。
可是小明每次只能移除栈顶的方块。于是他决定偷偷调整栈。每次当他无法执行移除指令时就会调整栈,按任意顺序将栈中所有方块排列。试问:执行完所有指令,小明最少需要进行几次调整?
(1<=n<=3e5)

测试样例

输入1

3
add 1
remove
add 2
add 3
remove
remove

输出1

1

输入2

7
add 3
add 2
add 1
remove
add 4
remove
remove
remove
add 6
add 7
add 5
remove
remove
remove

输出2

2

题解

首先,每次调整肯定是降序排列现在的栈,也就是元素越小越靠近栈顶。
但用vector存储栈依然会超时,最坏情况对于每次remove指令都要进行一次排序操作,时间复杂度为O(n^2logn)。
这道题就纯考数据结构了,只要能想到用priority_queue去存储已经排好序的栈底部的一串方块,vector只用来存储未排序的栈顶部的一串方块,直接模拟。

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

vector<i64>box;
priority_queue<i64>ordered_box;
string s;
i64 now = 1,scan,n,ans = 0;

i64 cmp(i64 a, i64 b){
	return a>b;
}

int main(){
	ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
	cin>>n;
	for(i64 i = 0;i<2*n;i++){
		cin>>s;
		if(s[0] == 'a'){
			cin>>scan;
			box.push_back(scan);
		}
		else{
			if(box.size()){	//box还有东西
				if(box[box.size()-1] == now){
					box.pop_back();
				}
				else{
					while(box.size()){
						ordered_box.push(box[box.size()-1]);
						box.pop_back();
					}
					ans++;
				}
			}
			else{
				ordered_box.pop();
			}
			now++;
		}
	}
	cout<<ans;
	return 0;
}

G Lucky Chains

中文题面

输入n,输入n对数据x,y。
认为gcd(x,y)=1时这样的x,y是幸运的,并可以向后延续,得到(x+1,y+1),直到gcd(x+k,y+k)!=1,得到长度为k,输出k即可。

测试用例

输入

4
5 15
13 37
8 9
10009 20000

输出

0
1
-1
79

题解

对大佬来说简单,送分题。对我来说一点也不……
涉及知识盲区了,于是潜心一晚上学了数论基础,没想到仍然以正确的思路TLE了无数发……
先说思路,再谈优化。

知识点1:更相减损术

gcd(a,b) = gcd(b,a-b)
老祖宗的智慧,就这一行小小的公式,成了这道题思路的突破口!我们要找最小的k使得gcd(x+k,y+k)!=1,根据更相减损术的公式变为gcd(x+k,y-x)!=1,由于y-x是个定量,题目一下变得简单了好多。
接下来,我们要找x+ky-x的不为1的公因子。

知识点2:唯一分解定理

任何大于1的自然数都可以以某种方式分解成质数的乘积,这种分解是唯一确定的,除了因数的顺序外,不存在其他分解方式。
正如《三体》中对黑暗森林法则那样,真理往往是简洁,而力量无穷的。这句话就是。由于y-x是个定量,我们可以把它分解成质数形式,遍历它的质因子,假如遍历到p[i],那么显然(prime[i] - x%prime[i]) % prime[i]作为对应的最小k,使得gcd(x+k,y+k)!=1成立。那么我们遍历质因子,得到的k中取最小的,就能找到答案!
问题是,怎么找质因子呢?难道对于每个y-x都找一遍质数集合吗?

知识点3:欧拉筛

欧拉筛可以以O(n)的时间复杂度处理出1~n范围内所有的素数。预处理得到素数集合,到时候直接用就行!
欧拉筛回头单独出博客!

优化

按理说这样的代码已经可以过了,但还是TLE……
可以在四个地方做优化:

快读

利用getchar()实现快速读取大量数据,可以比scanf()快五倍左右。

欧拉筛范围

欧拉筛的范围不需要到1e7,其实只需要到sqrt(1e7)即3300左右即可。

分解质因数算法

质因数pi只需要满足pi*pi<=y-x即可,最后单独处理一次剩余的y-x(把剩余的y-x当作一个质因数处理。)

数据结构

补药用long long啊!之前用long long一直超时,改成int,两秒就过了。

代码

AC代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;

int n,x,y,ans,cnt,k,cha;
int prime[10000],isprime[10000];

void eular(){	//欧拉筛,求范围内所有质数
	cnt = 0;
	for(i64 i = 2;i<=3300;i++)	isprime[i] = 1;
	for(i64 i = 2;i<=3300;i++){
		if(isprime[i])	prime[cnt++] = i;
		for(i64 j = 0;j<cnt;j++){
			if(i*prime[j] > 3300)	break;
			isprime[i*prime[j]] = 0;
			if(i % prime[j] == 0)	break;
		}
	}
	return;
}

inline void read(int& a)
{
	int s = 0, w = 1;
	char ch = getchar();
	while (ch < '0' || ch>'9')
	{
		if (ch == '-')
			w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		s = s * 10 + ch - '0';
		ch = getchar();
	}
	a = s * w;
}


int main(){
	eular();
	read(n);
	while(n--){
		read(x);
		read(y);
		if(y-x == 1){
			printf("-1\n");
			continue;
		}
		ans = 1e8;
		cha = y-x;
		for(i64 i = 0;prime[i] * prime[i]<=cha && i<cnt;i++){	//分解cha为质因数,处理所有质因数可能带来的k值
			if(cha%prime[i])	continue;
			k = (prime[i] - x%prime[i]) % prime[i];
			ans = min(ans, k);
			while(cha%prime[i]==0)	cha /= prime[i];
		}
		k = (cha - x%cha) % cha;
		if(cha!=1)	ans = min(ans, k);
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2025-03-24 14:11  Alkaid16  阅读(13)  评论(0)    收藏  举报