有趣的思维题。
赛时被卡常了,硬是没想到特判。
部分分给的挺好。
先口胡了一个结论:不管怎么排列, 1 1 1 总在最前面。最后发现结论似乎是对的。
Part 1:15 pts \texttt{Part 1:15 pts} Part 1:15 pts
为什么先讲 15 15 15 分呢?因为这 15 15 15 分完全没有思维难度啊。
直接枚举全排列,找到共振最大值即可。
单组数据时间复杂度 O ( n ! ) O(n!) O(n!)。
Part 2:10 pts \texttt{Part 2:10 pts} Part 2:10 pts
从这一部分开始我们就要思考正解了。
这 10 10 10 pts 是 p = 2 p=2 p=2 的特殊性质。
考虑题目中的描述:“其中相邻两项产生「共振」当且仅当这两个数的和为 p p p 的倍数。”
那么我们关心的其实就是这些数的余数啊!
为了方便,我们使用 n = 9 n=9 n=9 作为例子。
于是,我们可以得到这个数列: 1 1 1, 0 0 0, 1 1 1, 0 0 0, 1 1 1, 0 0 0, 1 1 1, 0 0 0, 1 1 1。
我们把 1 1 1 放在最前面,接下来,为了凑成 p p p 的倍数,只能选择再选择一个余数为 1 1 1 的数。于是我们选择 3 3 3。
以此类推,直到 9 9 9。
此时我们发现,没有 1 1 1 可以匹配了。那么我们就只能把所有的 0 0 0 拿出来,放在后面。
最终的答案就是: 1 1 1, 3 3 3, 5 5 5, 7 7 7, 9 9 9, 2 2 2, 4 4 4, 6 6 6, 8 8 8。
可以发现,这样一定是最优的。
找到规律:先放所有的奇数,再放所有的偶数。
单组数据时间复杂度 O ( n ) O(n) O(n)。
for(int i = 1;i <= n;i += 2) cout << i << ' ';
for(int i = 2;i <= n;i += 2) cout << i << ' ';
加上前面的总共可以获得 25 25 25 分。
Part 3:30 pts \texttt{Part 3:30 pts} Part 3:30 pts
这 30 30 30 pts 是 p = 3 p=3 p=3 的特殊情况。
我们拿 10 10 10 举例,通过取模得到 1 1 1, 2 2 2, 0 0 0, 1 1 1, 2 2 2, 0 0 0, 1 1 1, 2 2 2, 0 0 0, 1 1 1。
还是将 1 1 1 放在最前面,此时为了凑出 3 3 3,需要余数 2 2 2。那么还是这样循环往复,放完了就放 0 0 0,得到 1 1 1, 2 2 2, 1 1 1, 2 2 2, 1 1 1, 2 2 2, 1 1 1, 0 0 0, 0 0 0, 0 0 0。转换回来就是 1 1 1, 2 2 2, 4 4 4, 5 5 5, 7 7 7, 8 8 8, 10 10 10, 3 3 3, 6 6 6, 9 9 9。
注意特判最后一组 1 2
即可。
对于转换,可以自己手推一下,得到第 i i i 组 1 , 2 1,2 1,2 分别是 ( i − 1 ) × p + 1 (i-1)\times p + 1 (i−1)×p+1 和 ( i − 1 ) × p + 2 (i-1) \times p + 2 (i−1)×p+2。第 i i i 组 0 0 0 就是 i × p i \times p i×p。
单组数据时间复杂度 O ( n ) O(n) O(n)。
if(n % 3 == 0) {
int zu = n / p;
for(int i = 1;i <= zu;i++) cout << (i-1)*p+1 << ' ' << (i-1)*p + 2 << ' ';
for(int i = 1;i <= zu;i++) cout << i*p << ' ';
cout << endl;
}
else if(n % 3 == 1) {
//多一个 1
int zu = n / p;
for(int i = 1;i <= zu;i++) cout << (i-1)*p+1 << ' ' << (i-1)*p + 2 << ' ';
cout << n << ' ';
for(int i = 1;i <= zu;i++) cout << i*p << ' ';
cout << endl;
}
else {
//多一组 1,2
int zu = n / p;
for(int i = 1;i <= zu;i++) cout << (i-1)*p+1 << ' ' << (i-1)*p + 2 << ' ';
cout << n-1 << ' ' << n << ' ';
for(int i = 1;i <= zu;i++) cout << i*p << ' ';
cout << endl;
}
加上前面的部分分就可以拿到 55 55 55 分了。
Part 3.5:找规律 \texttt{Part 3.5:找规律} Part 3.5:找规律
如果自己多手玩几个小数据,就可以发现规律:
对于每一个 1 ≤ i ≤ ⌊ p 2 ⌋ 1 \le i \le \lfloor\frac{p}{2}\rfloor 1≤i≤⌊2p⌋,它如果要配对一定是跟 p − i p-i p−i 配对。那么这两个数至少有 ⌊ n p ⌋ \lfloor\frac{n}{p}\rfloor ⌊pn⌋ 组,即可以配对 ⌊ n p ⌋ \lfloor\frac{n}{p}\rfloor ⌊pn⌋ 组。当然还会剩下 n m o d p n \bmod p nmodp,如果这其中包含了 i i i 和 p − i p-i p−i,是要特判的。
当 p ≡ 1 ( m o d 2 ) p \equiv 1 \pmod 2 p≡1(mod2) 时,使用上面的规律加上 0 0 0 即可;
当 p ≡ 0 ( m o d 2 ) p \equiv 0 \pmod 2 p≡0(mod2) 时,需要特判 p 2 \frac{p}{2} 2p 的情况。
注意:当 p = 1 p=1 p=1 时,随便输出都可以。
到这里,我们看起来就做完了这道题。
if(p == 1) {
for(int i = 1;i <= n;i++) cout << i << ' ';
cout << "\n";
}
else if(p % 2 == 0) {
//此时一定会多出来一个 p/2
int zu = n / p,mo = n % p;
for(int i = 1;i * 2 < p-1;i++) {
int first = i,second = p-i;
for(int j = 1;j <= zu;j++) {
cout << (j-1)*p+first << ' ' << (j-1)*p+second << ' ';
}
if(mo >= second) {
cout << zu*p + first << ' ' << zu*p+second << ' ';
}
else if(mo < second && mo >= first) {
cout << zu*p + first << ' ';
}
}
for(int i = 1;i <= zu;i++) {
cout << (i-1)*p + p / 2 << ' ';
}
if(mo >= p / 2) {
cout << zu*p+p/2 << ' ';
}
for(int i = 1;i <= zu;i++) {
cout << i*p << ' ';
}
cout << "\n";
}
else {
int zu = n / p,mo = n % p;
for(int i = 1;i * 2 <= p-1;i++) {
int first = i,second = p-i;
for(int j = 1;j <= zu;j++) {
cout << (j-1)*p+first << ' ' << (j-1)*p+second << ' ';
}
if(mo >= second) {
cout << zu*p + first << ' ' << zu*p+second << ' ';
}
else if(mo < second && mo >= first) {
cout << zu*p + first << ' ';
}
}
for(int i = 1;i <= zu;i++) {
cout << i*p << ' ';
}
cout << "\n";
}
很显然,我们的做法是正确的,但是超时了,问题在于常数太大。
那怎么解决呢?
Part 4:100 pts \texttt{Part 4:100 pts} Part 4:100 pts
通过观察数据范围,我们发现, p p p 有可能比 n n n 大很多,可以发现,当 p > 2 × n − 1 p>2\times n-1 p>2×n−1 时,每一个 i i i 都无法配对,也就不可能产生共振。但我们的程序仍然在判断,导致运算量过大。
其实此时,既然不能共振,那直接顺序输出 1 ∼ n 1 \sim n 1∼n 即可。这和 p = 1 p=1 p=1 是一样的。
if(p == 1 || (p > (2 * n - 1))) {
for(int i = 1;i <= n;i++) cout << i << ' ';
cout << "\n";
}
这样,我们就可以拿到 100 100 100 分 了。
Part 5:总结 \texttt{Part 5:总结} Part 5:总结
本题考察的是简单贪心 + 简单数学找规律,重点是比较考验代码能力,是一道好题。