2021-09-14校ACM专题训练赛(数论、博弈)记录

总结

本次训练赛共7题,时间3个半小时。题目难度分布在cf的1300分~2100分之间。我开了4题,只做出A题,BCD题都没做出。在此记录一下BCD题的写法。

B题

给定p,q,要求最大的x,满足p%x==0且x%q!=0。
数据范围: 1 < = p < = 1 0 18 1<=p<=10^{18} 1<=p<=1018 2 < = q < = 1 0 18 2<=q<=10^{18} 2<=q<=1018

思路

1.要让x尽量大且为p的因数,只需不断从p中选择质因数进行相乘。
理解:由唯一分解定理,任何数都可以表示为质数的乘积。x是p的因子,因此要先将p拆分成多个质数相乘的形式,再从这些质数因子中选择质因数进行相乘。

2.要求x%q!=0,意味着选中的质因数不可能完全覆盖q的分解。
理解:由唯一分解定理,我们知道q也可以表示为质数的乘积。我现在取的这些质数不能完全包含q分解产生的这些质数,不然我就可以让x%q==0。

3.我们只需要尽可能多地选择,若完全覆盖了q的分解,删去q中的一个最小质因数即可。
理解:在我们从p的分解中选择的这些质因数中,我们尽可能多地去选择,只要完全覆盖了q的分解,我们就删去q中一个最小质因数来避免。

4.具体实现
我们不可能求出p的所有质数因子,只能用素数筛得到q的所有质数因子。我们只需要满足上述不完全覆盖原则即可,因此我们先假设p就是我们要的答案,然后检查p有没有包含q的某一质数因子,如果包含了我们就一直让p除以它,直到p不能整除q为止,就实现了不完全覆盖。全部遍历操作一次q的所有质因子,选出得到的最大的那个数,就是我们要求的答案。

5.时间复杂度
线性素数筛的时间复杂度不超过 O ( 1 0 5 ) O(10^5) O(105)。检查过程的时间复杂度约为 O ( 1 0 6 ) O(10^6) O(106)。在50组数据的情况下,可以快速跑完。注意线性素数筛的算法核心:保证每个合数只会被其最小质因子筛掉。

6.注意
从p中选择质因子一定不能选择到q这整个因子,因为满足 p = q m × r p=q^m×r p=qm×r的形式,因此要先除掉所有的因子q,得到一个最小的答案,再考虑增大答案。

AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e5 + 5;
bool isprime[maxn + 10];
int primes[maxn + 10], pn = 0;
void get_primes() {
	memset(isprime, true, sizeof(isprime));
	isprime[0] = isprime[1] = false;
	for (int i = 2; i < maxn; i++) {
		if (isprime[i] == true)primes[pn++] = i;
		for (int j = 0; j < pn; j++) {
			if (i*primes[j] > maxn)break;
			isprime[i*primes[j]] = false;
			if (i%primes[j] == 0)break;
		}
	}
}
signed main()
{
	int t;
	cin >> t;
	get_primes();
	while (t--) {
		int p, q;
		cin >> p >> q;
		int ans = p;
		while (ans%q == 0)ans /= q;
		for (int i = 0; i < pn; i++) {
			int cur = p;
			if (q%primes[i] == 0) {
				while (cur%primes[i] == 0) {
					cur /= primes[i];
					if (cur%q != 0)break;
				}
				ans = max(cur, ans);
			}
		}
		cout << ans << endl;
	}
	return 0;
}

C题

有一个点初始在 ( 0 , 0 ) (0,0) (0,0),两人轮流将其向上或向右移动k个单位。谁先移出以原点为圆心,以d为半径的圆谁就输。给定d,k,问先后手胜负情况。

思路

向右和向上的本质为互逆操作,即无论先手向上还是向右移动,后手总能将点纠正到直线 l : y = x l:y=x l:y=x上。因此这种博弈的本质是:经过m轮操作,点最终运动到 ( m k , m k ) (mk,mk) (mk,mk)位置,此时有两种情况,是为边界条件。其一,点无法再移动,后手胜;其二,点已经出界,先手胜。对于第一种情况,先手无论怎么操作都是后手必胜;对于第二种情况,后手可以通过先模仿一次先手的走法完成先后手的转换,只不过这次转换是转换到了 ( ( m + 1 ) k , m k ) ((m+1)k,mk) ((m+1)k,mk) ( m k , ( m + 1 ) k ) (mk,(m+1)k) (mk,(m+1)k)处,由对称性,这两个位置等价。然而这个点就是第一种情况中的先手必胜点,也就是说,后手无论怎么走,先手都可以将点最终维持在先手必胜点上。

容易求出圆包括的以k为单位的最小格子数。 ( m k , m k ) (mk,mk) (mk,mk)在界内是一定的,我们只需要判断 ( m k , ( m + 1 ) k ) (mk,(m+1)k) (mk,(m+1)k)是否在界内。在界内,先手必胜;在界外,先手必败。数学不等式 ( ( k x ) 2 + ( k ( x + 1 ) ) 2 > d 2 ((kx)^2+(k(x+1))^2>d^2 ((kx)2+(k(x+1))2>d2为判断条件。

AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
	int t;
	cin >> t;
	while(t--) {
		int d, k;
		cin >> d >> k;
		int x = d / (k*sqrt(2));
		bool flag = ((k*x)*(k*x) + (k*(x + 1))*(k*(x + 1))) > d*d;
		if (flag == true)puts("Utkarsh");
		else puts("Ashish");
	}
	return 0;
}

D题

A和U两人各持有一个长度均为n的字符串,轮流向一个新的长也为n的字符串里放字符,A先手。采取透明博弈的形式,A每一步都试图让字符串按字典序最小化,U每一步都试图让字符串按字典序最大化,求这个字符串最终会是什么。

思路

按照博弈的思想,A所持的字符串按从小到大排序,U所持的字符串按从大到小排序,最终每个人所持的字符串被使用的长度,A只有 l e n − l e n / 2 len-len/2 lenlen/2,U只有 l e n / 2 len/2 len/2。因此我们只需要考虑这固定长度的字符串即可。

如果当前A所持的字符串中字符的最小字典序都比U所持的字符串中字符的最大字典序要大或相等,那么A再去放所持字符串中字典序最小的那个字符在前面就没有意义了。因为只要A放了,那么一定不是最优解,一定可以找到U所持的字符串中的字符去替换它,可以得到更小的字典序。转换思路,考虑A所持有效字符串中字典序最大的字符。这个字符最终一定会被放在字符串中,由贪心的思想,我们考虑将这个字符放在字符串最后面,就可以保证字典序最小,并且一定是最优解。

如果当前A所持的字符串中字符的最小字典序比U所持的字符串中字符的最大字典序要小,将这字符放在字符串最前面。这样放可以干扰U,让U的具有更大字典序的字符无法放在更前面。否则,U就会把具有更大字典序的字符放到前面,就无法满足A的需求。反之亦然。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
int main()
{
	string s1, s2;
	cin >> s1 >> s2;
	int len = s1.size();
	char oleg[maxn], igor[maxn], ans[maxn];
	for (int i = 0; i < len; i++) {
		oleg[i] = s1[i];
		igor[i] = s2[i];
	}
	sort(oleg, oleg + len);
	sort(igor, igor + len, greater<char>());
	int leno = len - (len >> 1);
	int leni = (len >> 1);
	int fro = 0, bao = leno - 1;
	int fri = 0, bai = leni - 1;
	int front = 0, back = len - 1;
	for (int i = 1; i <= len; i++) {
		if (i & 1) {
			if (oleg[fro] >= igor[fri]) {
				ans[back] = oleg[bao];
				back--, bao--;
			}
			else {
				ans[front] = oleg[fro];
				front++, fro++;
			}
		}
		else {
			if (oleg[fro] >= igor[fri]) {
				ans[back] = igor[bai];
				back--, bai--;
			}
			else {
				ans[front] = igor[fri];
				front++, fri++;
			}
		}
	}
	for(int i = 0; i < len; i++)cout << ans[i];
	return 0;
}

心得

9月19号就是icpc亚洲区域赛网络赛了,感觉自己在思维题方面还是有很大的欠缺。如数论、博弈、贪心,许多基础算法都是与思维结合在一起考察的。以后想题千万不要复杂了,而且考虑要全面,要多尝试一些一般性的样例,不要被题目本身所迷惑。祝网络赛好运。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

keguaiguai

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

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

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

打赏作者

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

抵扣说明:

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

余额充值