0%

题目大意

题目描述
需要构造 nn 个具有 kk 个面的骰子,每个面上的数字取值范围为 0m0 \sim m
两个骰子进行对决时,随机掷出一个面,点数大者获胜。要求构造的骰子组合必须同时满足以下三个条件:

  1. 循环劣势:形成 123n1n11 \to 2 \to 3 \to \dots \to n-1 \to n \to 1 的胜负关系。即 1 号骰子输给 2 号,2 号输给 3 号,……,nn 号输给 1 号(“输给”定义为在 k×kk \times k 种可能的掷出组合中,获胜的次数严格少于对方)。

  2. 绝对无平局每个数字至多只能出现在一个骰子上,保证任意两个骰子对决时不可能掷出相同的点数。

  3. 字典序最小:将 1 号到 nn 号骰子上的所有数字按顺序拼接成一个长度为 n×kn \times k 的序列,要求该序列在所有合法方案中字典序最小。

输入格式
第一行输入一个正整数 TT,表示测试数据组数。
接下来每组数据输入三个正整数 nnmmkk,分别表示骰子数量、面上的最大允许数字、每个骰子的面数。

输出格式
对于每组数据,输出一行 n×kn \times k 个用空格分隔的整数,依次表示 1 号到 nn 号骰子各个面上的数字。

样例数据

输入样例:

1
2
1
3 4 3

输出样例:

1
0 3 3 1 1 4 2 2 2

样例解释
输入代表需要构造 33 个骰子,每个骰子有 33 个面,数字范围在 040 \sim 4 之间。根据输出样例,三个骰子的构造如下:

  • 1 号骰子:[0, 3, 3]

  • 2 号骰子:[1, 1, 4]

  • 3 号骰子:[2, 2, 2]

胜负关系验证(总对决情况数为 3×3=93 \times 3 = 9 种):

  • 1 号 vs 2 号:1 号获胜的情况为 3>1(共 4 次),2 号获胜的情况为 1>01>04>04>34>3(共 5 次)。2 号胜率更高,1 号输给 2 号

  • 2 号 vs 3 号:2 号获胜的情况为 4>2(共 3 次),3 号获胜的情况为 2>1(共 6 次)。3 号胜率更高,2 号输给 3 号

  • 3 号 vs 1 号:3 号获胜的情况为 2>0(共 3 次),1 号获胜的情况为 3>2(共 6 次)。1 号胜率更高,3 号输给 1 号

该方案数字各不相同(无平局),构成了 12311 \to 2 \to 3 \to 1 的循环劣势,且可以证明拼接后的序列 0 3 3 1 1 4 2 2 2 是所有满足条件的方案中字典序最小的。

思路讲解

image

首先,我们为什么会想到双段构造?呃,其实正常的思维链路应该是,我们先想怎么构造,

其实题面里面讲的非常清楚,一个骰子最多只会有两种不同的数字,每个数字仅能出现在至多一个骰子上。

image

那都说了,最多两个数,那还能不是双段构造吗?

如果分成双段构造,那么不难看出:

早段1<早段2<<早段n\text{早段}_1 < \text{早段}_2 < \dots < \text{早段}_n,$\text{晚段}_1 < \text{晚段}_2 < \dots < \text{晚段}_n $(为了字典序最小,这两个条件是必须的),但是早晚段之间是什么关系呢?

注意啊,为了赢得游戏,早段n<晚段1\text{早段}_n < \text{晚段}_1,这个也是为了赢得游戏,满足所谓的“循环劣势要求”,所必须的这个构造,一个的最小的,大于一个的最大的,两者之间是绝对不会发生这个穿插的这个情况的,因此就是 早段1<早段2<<早段n<晚段1<晚段2<<晚段n\text{早段}_1 < \text{早段}_2 < \dots < \text{早段}_n < \text{晚段}_1 < \text{晚段}_2 < \dots < \text{晚段}_n

okay,那这道题目比较关键的部分就做完了,当然,还需要处理一些细节问题,就是每个段的早段是几个数,晚段是几个数?

不过贪心求解的部分也不是那么容易的。

首先,先写出一系列的这个获胜条件:

image

接着,我们要进行这个贪心求解,我们不难发现:

image

然后我们知道了 x1 的值,后面就用前面的限制,一个一个往后推就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void gen_eraly_seg() {
for (int i = 1; i <= N; ++i) {
ll early_sz;
if (i == 1) {
early_sz = K - (bound_win + K - 1) / K;
} else {
if (early_sz_ls[i - 1] - K == 0) {
early_sz = K;
} else {
early_sz = (bound_win - K * K) / (early_sz_ls[i - 1] - K);
}
}
early_sz = min(early_sz, K);
early_sz_ls[i] = early_sz;
for (int j = 1; j <= early_sz; ++j) {
ans_mat[i][j] = idx;
}
if (early_sz != 0) {
++idx;
}
}
}

void gen_late_seg() {
for (int i = 1; i <= N; ++i) {
for (int j = early_sz_ls[i] + 1; j <= K; ++j) {
ans_mat[i][j] = idx;
}
if (early_sz_ls[i] != K) {
++idx;
}
}
}

AC代码

AC
https://acm.hdu.edu.cn/contest/view-code?cid=1199&rid=17137

心路历程(WA,TLE,MLE……)

除零错误——INTEGER_DIVIDE_BY_ZERO

题目大意

题目描述
你有三个栈 A,B,CA,B,C 和两个变量 L,RL,R(相当于两只手)。栈中元素必须时刻满足:越靠近栈底的元素越大。

初始时,L=R=0L=R=0AA 栈中有 nn 个元素(从栈底到栈顶依次为 n,n1,,1n, n-1, \dots, 1),B,CB, C 栈为空。目标是将所有元素移动到栈 CC,且操作过程中必须满足上述大小限制。

可以进行两种操作:

  • 操作 1(拿起):选择一个非空栈 SS 和一个值为 00 的变量 VVLLRR),弹出 SS 的栈顶元素,并将该元素的值赋给 VV

  • 操作 2(放下):选择一个值为 x0x \neq 0 的变量 VV 和一个栈 SS,将 xx 压入栈 SS,并将 VV 的值设为 00

求:将所有元素移到栈 CC 所需的最少操作 1(拿起)的次数
结果需要对 998244353998244353 取模。

输入格式
第一行输入一个整数 TT1T1001 \le T \le 100)表示测试数据组数。
接下来 TT 行,每行输入一个整数 nn1n1061 \le n \le 10^6)。

输出格式
输出 TT 行,每行一个整数,表示最少使用操作 1 的次数对 998244353998244353 取模的结果。

样例输入

1
2
3
4
5
6
5
1
2
3
10
100

样例输出

1
2
3
4
5
1
2
4
36
268435446

样例解释(对于 n=3n=3
初始状态: A(3,2,1),B(),C(),L=0,R=0A(3,2,1),B(),C(),L=0,R=0
操作 1: A(3,2),B(),C(),L=1,R=0A(3,2),B(),C(),L=1,R=0
操作 2: A(3,2),B(1),C(),L=0,R=0A(3,2),B(1),C(),L=0,R=0
操作 1: A(3),B(1),C(),L=2,R=0A(3),B(1),C(),L=2,R=0
操作 1: A(),B(1),C(),L=2,R=3A(),B(1),C(),L=2,R=3
操作 2: A(),B(1),C(3),L=2,R=0A(),B(1),C(3),L=2,R=0
操作 2: A(),B(1),C(3,2),L=0,R=0A(),B(1),C(3,2),L=0,R=0
操作 1: A(),B(),C(3,2),L=1,R=0A(),B(),C(3,2),L=1,R=0
操作 2: A(),B(),C(3,2,1),L=0,R=0A(),B(),C(3,2,1),L=0,R=0
共使用了 4 次操作 1,因此答案为 4。

思路讲解

PDF

直接打表是没什么思路的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1:1
2:2
3:4
4:6
5:9
6:13
7:17
8:22
9:28
10:36
11:44
12:54
13:66

再大,时间复杂度就要求太大了,没法继续打了。我们一般看规律是用差分与二阶差分这样子去看的,不过这个难度还是比较大,想把这个规律看出来,主要是

不过,汉诺塔问题嘛,就是 3 步

image

当然,由于我们现在有两只手,所以情况稍微复杂一点,就是我们在没有辅助杆的情况下我们可以移动的盘子就不一定是 1 个了

image

当然,具体是几根也不是那么重要了。可以使用 bfs 求解,或者你对自己的直觉和数学功底比较自信的话,也可以自己整一整。

1
2
3
4
5
6
7
8
9
没有辅助柱子的情况下,就靠两只手,所需要的拿去次数
_______________[ Sample Testcase #1 OUTPUT]_______________
1:1
2:2
3:5
4:10
5:2123213341(随便设置的哨兵值)
6:2123213341

其实套路和经典汉诺塔问题一样。

image

AC代码

AC
https://acm.hdu.edu.cn/contest/view-code?cid=1199&rid=18423

心路历程(WA,TLE,MLE……)

打表的时候遇到的问题

小心,不要把这个 step 也用于判重,如果 step 要用于优先队列,那么就使用两个比较器

初始化顺序问题,栈的初始化顺序有可能是反的

题目大意

题目大意

定义一种自然数下的二元运算「月球异或」(记作 \circledast):将两个自然数转换为三进制后,对每一位分别相加并对 33 取模(即三进制下的不进位加法)。例如 8(10)16(10)=022(3)121(3)=110(3)=12(10)8_{(10)} \circledast 16_{(10)} = 022_{(3)} \circledast 121_{(3)} = 110_{(3)} = 12_{(10)}

现给定一个长度为 NN 的自然数序列 v1,v2,,vNv_1, v_2, \dots, v_N你可以对序列中的任意元素 viv_i 执行以下两种操作任意次(包含零次):

  1. viviviv_i \leftarrow v_i \circledast v_i

  2. vi0v_i \leftarrow 0

QQ 次独立的询问,每次询问给出一个自然数 ss。对于每次询问,你需要判断能否通过若干次上述操作,使得序列全部元素的「月球异或」和等于 ss(即 v1v2vN=sv_1 \circledast v_2 \circledast \dots \circledast v_N = s)。

数据范围

测试组数1T1000001 \le T \le 100000
序列长度与询问次数1N,Q1061 \le N, Q \le 10^6,且所有测试组的 N,Q2×106\sum N, \sum Q \le 2 \times 10^6
序列元素与询问值0vi,si10180 \le v_i, s_i \le 10^{18}

样例数据

输入

1
2
3
4
5
6
7
8
9
10
3
1 4
1
0 1 2 3
3 5
0 5 10
4 5 6 7 8
3 6
8 16 32
12 17 57 41 4 44

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Yes
Yes
Yes
No
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
Yes

样例解释

以第一组样例为例:
此时 N=1,Q=4N=1, Q=4,序列只包含一个元素 v1=1v_1 = 1
四次询问的值分别为 s=0,1,2,3s = 0, 1, 2, 3

对于 v1=1v_1 = 1(三进制表示为 01(3)01_{(3)}),它可能变化得到的状态有:

  • 执行零次操作:得到原来的值 11

  • 执行一次第一种操作11=1(3)+1(3)(mod3)=2(3)=21 \circledast 1 = 1_{(3)} + 1_{(3)} \pmod 3 = 2_{(3)} = 2

  • 执行一次第二种操作:直接变为 00

因为操作本质上是对三进制的每一位独立进行乘法并对 33 取模,或者直接置零,所以原本为 00 的高位(如 313^1 对应的位)永远不可能变成非 00 值。
目标 s=3s=3 在三进制下表示为 10(3)10_{(3)},其第 11 位(对应数值 33)的值为 11,而 v1v_1 无论怎么操作在第 11 位上永远是 00,因此永远无法得到 33
最终对于 0,1,2,30, 1, 2, 3 的询问,前三个可以得到,后一个无法得到,分别输出 Yes、Yes、Yes、No。

思路讲解

其实,不难发现,我们其实就是要看一个数字能不能用我们的这个三进制异或线性基表示出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137


// 模 p 意义下的线性基(用于处理 p 进制下的无进位加法,本题中 p = 3)
class modn_XOR_base {
private:
vector<ll> base; // 记录主元位置对应的原始数值
vector<vector<ll>> mat; // mat[i] 存储最高位为 i 且系数已被标准化为 1 的基向量
ll mod; // 模数 p(要求 p 为质数,才能使用费马小定理求逆元)


// 快速幂计算 (a^b % mod)
ll power(ll a, ll b) const {
ll res = 1;
a %= mod;
while (b > 0) {
if (b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res;
}

// 费马小定理求逆元(因为 mod 为质数)
ll modInverse(ll n) const {
return power(n, mod - 2);
}

public:
// 构造函数,初始化模数和基数组的大小
// 64 位数组足以容纳 10^18 在三进制下的最大位数(约为 38 位)
modn_XOR_base(ll Mod) {
mod = Mod;
base.assign(64, 0);
mat.assign(64, vector<ll>(64, 0));
}

// 插入元素 x 到线性基中
// 如果 x 能被已有的线性基表出,返回 false;否则将其作为新的基插入并返回 true
bool insert(ll x) {
ll orig_x = x;
vector<ll> v(64, 0);

// 将 x 转换为 mod 进制的向量表示(从低位到高位分离)
for (int i = 0; i < 64; ++i) {
v[i] = x % mod;
x /= mod;
}

// 从高位到低位进行高斯消元操作
for (int i = 63; i >= 0; --i) {
if (v[i] == 0) continue; // 当前位为 0,无需处理

// 如果当前位没有主元(即线性基在此位为空)
if (base[i] == 0) {
ll inv = modInverse(v[i]); // 求当前位系数的逆元
// 将整个向量乘以逆元,使得最高位(第 i 位)的系数标准化为 1
for (int j = 0; j <= i; ++j) {
mat[i][j] = (v[j] * inv) % mod;
}
base[i] = orig_x; // 记录在该位有主元被插入
return true; // 成功插入新的基向量,向量空间维数 +1
}

// 如果当前位已经有主元了,利用已有的基向量 mat[i] 消去当前位 v[i]
ll k = v[i]; // 因为 mat[i][i] 已经被标准化为 1,所以要消去 v[i],需减去 mat[i] 的 k 倍
for (int j = 0; j <= i; ++j) {
v[j] = (v[j] - k * mat[i][j]) % mod;
if (v[j] < 0) v[j] += mod; // 保证结果始终在 [0, mod-1] 的合法范围内
}
}
// 如果循环结束,说明整个向量被彻底消成了 0,即它可以由已有基表出
return false;
}


// 查询元素 x 能否被当前的线性基表出
bool query(ll x) const {
vector<ll> v(64, 0);

// 同样将待查询的 x 转换为 mod 进制的向量表示
for (int i = 0; i < 64; ++i) {
v[i] = x % mod;
x /= mod;
}

// 从高位到低位依次尝试被线性基消元
for (int i = 63; i >= 0; --i) {
if (v[i] == 0) continue;

// 如果 x 在某一位不为 0,但线性基该位为空,说明无法被表出,直接返回 false
if (base[i] == 0) return false;

// 利用线性基消去当前位
ll k = v[i];
for (int j = 0; j <= i; ++j) {
v[j] = (v[j] - k * mat[i][j]) % mod;
if (v[j] < 0) v[j] += mod;
}
}

// 如果所有非零位都被成功消去(最终变为全 0 向量),说明 x 能被完美表出
return true;
}
};

int main() {

// 优化标准 I/O 速度,避免因为读写大规模数据超时
ios::sync_with_stdio(false);
cin.tie(nullptr);


int T;
cin >> T;
while (T--) {
// 针对题目“月球异或”:三进制下的每一位独立相加后模 3,因此实例化模数为 3 的线性基
modn_XOR_base base(3);
ll N, Q;
cin >> N >> Q;

// 读入序列 v 并将每一个元素构建到线性基中
// 注意:线性基可以允许重复操作操作对象 v_i <- v_i ⊛ v_i,本质就是该向量的数乘操作
for(int i = 0; i < N; ++i){
ll x;
cin >> x;
base.insert(x);
}

// 读入每个询问的数值 t,利用已求得的线性基判断是否能生成 t
for(int i = 0; i < Q; ++i){
ll t;
cin >> t;
cout << (base.query(t) ? "Yes\n" : "No\n");
}
}
return 0;
}

AC代码

AC

https://acm.hdu.edu.cn/contest/view-code?cid=1199&rid=7820&from=rank

心路历程(WA,TLE,MLE……)

注意 dfs 要记得进行回退操作的时候,所有东西都要回退,包括这个 vis 数组。

image