总结
本次训练赛共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 len−len/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亚洲区域赛网络赛了,感觉自己在思维题方面还是有很大的欠缺。如数论、博弈、贪心,许多基础算法都是与思维结合在一起考察的。以后想题千万不要复杂了,而且考虑要全面,要多尝试一些一般性的样例,不要被题目本身所迷惑。祝网络赛好运。