P9573 「TAOI-2」核心共振 题解

本文介绍了如何通过贪心策略和数学分析解决一个关于数组中元素共振的问题,强调了找出规律、优化时间复杂度和利用特殊性质的重要性。最终通过简化条件,避免不必要的计算,成功得出最优解法。
摘要由CSDN通过智能技术生成

有趣的思维题。

赛时被卡常了,硬是没想到特判。

部分分给的挺好。

先口胡了一个结论:不管怎么排列, 1 1 1 总在最前面。最后发现结论似乎是对的。

Part   1:15   pts \texttt{Part 1:15 pts} Part 115 pts

为什么先讲 15 15 15 分呢?因为这 15 15 15 分完全没有思维难度啊。

直接枚举全排列,找到共振最大值即可。

单组数据时间复杂度 O ( n ! ) O(n!) O(n!)

Part   2:10   pts \texttt{Part 2:10 pts} Part 210 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 330 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 (i1)×p+1 ( i − 1 ) × p + 2 (i-1) \times p + 2 (i1)×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 1i2p,它如果要配对一定是跟 p − i p-i pi 配对。那么这两个数至少有 ⌊ 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 pi,是要特判的。

p ≡ 1 ( m o d 2 ) p \equiv 1 \pmod 2 p1(mod2) 时,使用上面的规律加上 0 0 0 即可;

p ≡ 0 ( m o d 2 ) p \equiv 0 \pmod 2 p0(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";
}

Record

很显然,我们的做法是正确的,但是超时了,问题在于常数太大。

那怎么解决呢?

Part   4:100   pts \texttt{Part 4:100 pts} Part 4100 pts

通过观察数据范围,我们发现, p p p 有可能比 n n n 大很多,可以发现,当 p > 2 × n − 1 p>2\times n-1 p>2×n1 时,每一个 i i i 都无法配对,也就不可能产生共振。但我们的程序仍然在判断,导致运算量过大。

其实此时,既然不能共振,那直接顺序输出 1 ∼ n 1 \sim n 1n 即可。这和 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:总结

本题考察的是简单贪心 + 简单数学找规律,重点是比较考验代码能力,是一道好题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值