欢迎关注
D. Kevin翻硬币
比赛的时候,一直在猜结论,一共wa了17次,泪目。
但有一个想法,慢慢浮现出现来,比较硬核。
硬核解法
- 模拟 + 环形差分
先来说下模拟,实际上从原点出发,每次走k步,在环形的n中,一定可以回到原点的。最简单的验证,就是做n次的k步,必然回到原点。
更通俗的数学证明
kx = 0 mod n
gcd(k, n) | 0 必然成立,也就说 x 有解.
根据鹊巢原理,最多走n次,就能回到原点.
为了方便处理,我们把 1-index 转换为 0-index
那剩下的核心问题是:
如何判定是否能够翻转呢?
这个问题可以抽象为,从0点每次走k,然后回到0点的过程中,[0, n - 1]中的每个数的翻转次数都是奇数? 那就一定可以。
假设当前点位t,翻转k,那相当于 [t, t + k - 1] 区间翻转次数整体+1
这里的一个难点,其实环形结构,如何处理这种跨区间的。
比如 [t, t + k - 1], t + k - 1 >= n - 1
那其实可分成两段[t, n - 1], [0, (t + k - 1) % n]做差分
这就是为什么说,环形结构的差分.
最后就是差分的常见累加计算,然后判定[0, n - 1]的数,改变次数都是奇数,那一定可行,否则不行。
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
int t = sc.nextInt();
while (t-- > 0) {
int n = sc.nextInt(), k = sc.nextInt();
// 模拟 + 环形差分
// 构建每次走k就行,难在于可行解的判定上
int[] diff = new int[n + 1];
List<Integer> res = new ArrayList<>();
int x = 0;
do {
res.add(x);
diff[x]++;
if (x + k >= n) {
// 跨区间,就两段
diff[n]--;
diff[0]++;
diff[(x + k) % n]--;
} else {
diff[x + k]--;
}
x = (x + k) % n;
} while (x != 0);
// 差分验证, 如果一个数的改变次数是偶数,则无解
boolean f = true;
int acc = 0;
for (int i = 0; i < n; i++) {
acc += diff[i];
if (acc % 2 == 0) {
f = false;
break;
}
}
if (!f) {
System.out.println(-1);
} else {
System.out.println(res.size());
System.out.println(res.stream().map(y -> String.valueOf(y + 1)).collect(Collectors.joining(" ")));
}
}
}
}