LGR-144-题解
A. 新概念报数
Description
现在 A 和 B 玩起了报数游戏,但是他们非常喜欢 \(2\) 这个数字,于是制定了如下规则:
- 如果一个数 \(0\le a\leq2^{63}\) 满足 \(\operatorname{popcount}(a) \geq 3\),那么这个数字是非法的,对方需要回答
No,Commander
。 - 否则,这个数是合法的,对方需要回答下一个合法的数。
坐在旁边的你当然不知道游戏的乐趣,你只想知道某次报数之后对方应该回答什么。
Solution
其中 \(\text{popcount}(a)\) 是指 \(a\) 的二进制下的 \(1\) 的个数,可通过 \(\text{lowbit}\) 的 \(\log n\) 求解,也可以通过倍增的 \(\log(\log n)\) 求解。
-
如果 \(\text{popcount}=0\) 时,即 \(a=0\),此时下一个满足条件的数为 \(1\)。
-
如果 \(\text{popcount}=1\) 时,即 \(a= 2^x\),此时无论在任何位置添加 \(2^y\) 都可以。当 \(y=0\) 时,答案最小,即 \(a+1\)。
-
如果 \(\text{popcount}=2\) 时,
-
当 \(a\) 为奇数时,二进制下的最低位为 \(1\)。此时对 \(a+1\),二进制的数将会进位,但是并不会使得其 \(\text{popcount}\) 变大,所以答案即为 \(a+1\)。
-
当 \(a\) 为偶数时,二进制下的最低位为 \(0\)。此时对 \(a+1\),\(\text{popcount}\) 将会变为 \(3\),不合法。
考虑奇数时的情况,将 \(a\) 的最低位的 \(1\) 上面加上 \(1\)。满足条件。
答案可以归结为 \(a+\text{lowbit}(a)\)。
-
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
int x = 0; bool f = false; char c = getchar();
for (; !isdigit(c); f |= (c == '-'),c = getchar());
for (; isdigit(c); x = (x<<1) + (x<<3) + (c^48),c = getchar());
return f ? -x : x;
}
inline int lowbit(int x) {
return (x)&(-x);
}
inline int popcount(int x) {
x = (x & 0x5555555555555555) + ((x >> 1) & 0x5555555555555555);
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
x = (x & 0x0f0f0f0f0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f0f0f0f0f);
x = (x & 0x00ff00ff00ff00ff) + ((x >> 8) & 0x00ff00ff00ff00ff);
x = (x & 0x0000ffff0000ffff) + ((x >> 16) & 0x0000ffff0000ffff);
x = (x & 0x00000000ffffffff) + ((x >> 32) & 0x00000000ffffffff);
return x;
}
int n, k;
signed main() {
int T = read();
while (T -- ) {
k = popcount(n = read());
if (k >= 3) {
puts("No,Commander");
}
else {
if (k == 2) cout << n + lowbit(n) << '\n';
else cout << n + 1 << '\n';
}
}
return 0;
}
B. 河外塔
Description
但是,你可能没有听过河外塔问题。虽然但是,好像并没有河外塔问题。于是,伟大的 X_Xy 决定创造一个河外塔问题。
既然是河内塔问题的延申,就得有些一样的东西:有三个柱子
\(A\),\(B\) 和 \(C\) ,以及 \(n\) 个圆盘,其中编号为 \(i\) 的圆盘的半径长为 \(i\),这些圆盘最开始都在 \(A\) 上,最终都要顺序(即从上往下从小到大)地移到 \(C\) 上。
既然是河内塔问题的延伸,就得有些不同的东西:最开始在 \(A\) 上面的圆盘并不是顺序的,由于这个限制,我们也不在意移动过程中的顺序,也就意味着你可以将一个大的圆盘放在小的圆盘上。
但是 X_Xy 很懒,他只想让你操作至多 \(10^6\) 次。
Solution - 1
- 通过 \(\text{stack}\) 模拟,时间复杂度 \(\mathcal O(n^2)\)。
假设现在需要将编号为 \(x\) 的圆盘放在柱子 \(C\) 上,而 \(x\) 现在位于柱子 \(A\) 上。
通过两个栈模拟柱子 \(A,B\) 上面的圆盘。
将圆盘 \(x\) 上面所有的圆盘以此放入柱子 \(B\) 上,然后将圆盘 \(x\) 放在柱子 \(C\) 上。
Code - 1
#include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read(){
int x = 0; bool f = false; char c = getchar();
for (; !isdigit(c); f |= (c == '-'),c = getchar());
for (; isdigit(c); x = (x<<1) + (x<<3) + (c^48),c = getchar());
return f ? -x : x;
}
int n, k, a[10010];
bool A[10010], B[10010];
stack <int> As, Bs;
vector <string> ans;
signed main() {
n = read();
for (int i = 1; i <= n; i ++ ) A[a[i] = read()] = true;
for (int i = n; i >= 1; i -- ) As.push(a[i]);
for (int i = n; i >= 1; i -- ) {
if (A[i] == true) {
while (As.top() != i) {
swap(A[As.top()], B[As.top()]);
Bs.push(As.top()); As.pop();
ans.push_back("A B\n");
}
A[As.top()] = false; As.pop();
ans.push_back("A C\n");
}
else {
while (Bs.top() != i) {
swap(A[Bs.top()], B[Bs.top()]);
As.push(Bs.top()); Bs.pop();
ans.push_back("B A\n");
}
B[Bs.top()] = false; Bs.pop();
ans.push_back("B C\n");
}
}
cout << (k = ans.size()) << '\n';
for (int i = 0; i < k; i ++ ) cout << ans[i];
return 0;
}
Solution - 2
- 基数排序解决,时间复杂度为 \(\mathcal O(2n\log n)\)。
出现了是“鸽鸽子”(建议 \(\text{bdfs}\) )
Solution - 3
- 归并排序(分治)解决,时间复杂度为 \(\mathcal O(\frac{3}{2}n\log n)\)。
考虑这样一个过程:
-
对于一段区间 \(\left[l,r\right]\),我们可以先将 \([l,mid]\) 的数排完序放到柱子 \(\text{tmp1}\) 上面,并且是逆序排列。
-
再将区间 \(\left(mid,r\right]\) 的数排完放到柱子 \(\text{tmp2}\) 上面,并且是顺序排列。
-
然后将柱子 \(\text{tmp1}\) 上面的数放到柱子 \(\text{tmp2}\) 上面。
定义几个操作为:
-
\(S\,\text{(l,\ r,\ from,\ to,\ tmep,\ type)}\) 表示为将区间在 \(\left[l,r\right]\) 的数从柱子 \(\text{from}\)( 借助柱子 \(\text{temp}\))移动到柱子 \(\text{to}\) 上面.
其中 \(\text{type}=\left\{0,1\right\}\), 表示到柱子 \(\text{to}\) 时是逆序排列或是正序排列。
-
\(F\,\text{(k,from,to)}\) 表示将 \(k\) 个数,从柱子 \(\text{from}\) 移动到柱子 \(\text{to}\) 上面,顺序将会相反。
-
\(D\,\text{(k,\ from,\ to1,\ to2,\ }\Delta)\) 表示为将柱子 \(\text{from}\) 上的 \(k\) 个数字,分别放到柱子 \(\text{to1}\) 和 \(\text{to2}\) 上面。
其中将 \(\le\Delta\) 的数放在 \(\text{to1}\) 上面,大于 \(\Delta\) 的数放在 \(\text{to2}\) 上面。
可以发现其答案就是 \(S\,\text{(1,\ n,\ A,\ C,\ B,\ 1)}\)。
每次操作这样的过程 \(S\,\text{(l,\ r,\ from,\ to,\ tmep,\ 1)}\) 可以转化为:
当 \(\text{type}=0\) 时相似。
其中当 \(l=r\) 时,\(S\) 操作等价于 \(F\) 操作。
Code - 3
#include <bits/stdc++.h>
using namespace std;
// #define int long long
inline int read(){
int x = 0; bool f = false; char c = getchar();
for (; !isdigit(c); f |= (c == '-'),c = getchar());
for (; isdigit(c); x = (x<<1) + (x<<3) + (c^48),c = getchar());
return f ? -x : x;
}
const int A = 1, B = 2, C = 3;
#define mid ((l + r) >> 1)
int k[4][100100];
vector < pair<int,int> > ans;
void move(int frm, int to) {
ans.push_back(make_pair(frm, to));
k[to][ ++ k[to][0]] = k[frm][k[frm][0] -- ];
}
void F(int num, int frm, int to) {
while (num -- ) move(frm, to);
}
void D(int num, int frm, int to1, int to2, int dlt) {
while (num -- ) {
if (k[frm][k[frm][0]] <= dlt) move(frm, to1);
else move(frm, to2);
}
}
void S(int l, int r, int frm, int to, int tmp, int typ) {
if (l == r) return F(1, frm, to);
if (typ == 1) {
D(r - l + 1, frm, to, tmp, mid);
S(l, mid, to, frm, tmp, typ ^ 1);
S(mid + 1, r, tmp, to, frm, typ);
F(mid - l + 1, frm, to);
}
else {
D(r - l + 1, frm, tmp, to, mid);
S(mid + 1, r, to, frm, tmp, typ ^ 1);
S(l, mid, tmp, to, frm, typ);
F(r - mid, frm, to);
}
}
signed main() {
k[A][0] = read();
for (int i = k[A][0]; i >= 1; i -- ) {
k[A][i] = read();
}
S(1, k[A][0], A, C, B, 1);
cout << ans.size() << '\n';
for (pair<int,int> it : ans)
printf("%c %c\n",it.first + 64, it.second + 64);
return 0;
}
C. 有效打击
Description
定义:若序列 \(\Alpha\) 为
序列 \(\Beta\) 为
其中 \(A,B,C,a_1,a_2,a_3,...,b_1,b_2,b_3,...\in N_+\)。
若有 \(\dfrac{a_1}{b_1}=\dfrac{a_2}{b_2}=\dfrac{a_3}{b_3}=...=k\ , k>0\),则称 \(\Alpha\) 与 \(\Beta\) 互为相似序列。
特别的,长度为 \(0\) 的序列不与任何序列成相似序列。
不难证明此定义下序列间的相似具有传递性。
求给定的序列 \(A\) 中有多少个子串与给定序列 \(B\) 互为相似序列。
Data range
数据点 | n | m | 特殊性质 |
---|---|---|---|
1~2 | \(\leq 20000\) | \(\leq 20000\) | 无 |
3~4 | \(\leq 5 \times 10^6\) | \(\leq 5 \times 10^6\) | A |
5~6 | \(\leq 5 \times 10^6\) | \(\leq 5 \times 10^6\) | B |
7~10 | \(\leq 5 \times 10^6\) | \(\leq 5 \times 10^6\) | C |
对于 \(100\%\) 的数据,保证有 \(\forall\ i\in[1,n]\),\(1\leq A_i \leq 7\);\(\forall\ i\in[1,m]\),\(1\leq B_i\leq 7\)。
对于 \(100\%\) 的数据,保证 \(1\leq n,m\leq 5 \times 10^6\)。
特殊性质 A:保证 \(\forall\ i,j\in [1,m],B_i=B_j\)。
特殊性质 B:保证有且仅有一个 \(k\in[1,m-1]\),使得 \(\forall \ i,j\in[1,k]\),\(B_i=B_j\);\(\forall \ i,j\in[k+1,m]\),\(B_i=B_j\)。
特殊性质 C:保证第 \(10\) 个点中 \(n,m \leq 5\times 10^5\),且其他点中连续段仅有不超过 \(100\) 种不同的长度。
Solution - 1
- 对于特殊性质 A。
保证了序列 \(B\) 中只会出现一个数,可以将其缩为一个数,那么对于序列 \(A\) 来讲,只需要找出只包含 \(B_i\) 的子序列的个数即可。
这样的子序列是一个连续的 \(B_i\),令其长度为 \(\ell\),那么这段的贡献就是 \(\dfrac{\ell(\ell + 1)}{2}\)。
- 对于特殊性质 B。
与特殊性质 A 同理,只需要找到 A 中相邻两个数恰好为 \(B_1,B_m\) 的位置,然后统计即可。
序列 \(B\) 中的 \(B_1\) 与 \(B_m\) 同时也可以缩小,但是需要使得 \(B_1\) 和 \(B_m\) 的比例相同,即最简形式(除以 \(\gcd\) 即可)。
Code - 1
// A ...
for (int i = 1; i <= n + 1; i ++ ) {
if (a[i] != a[i - 1]){
if (a[i - 1] == b[1]) ans += ((cnt + 1) * cnt)/2;
cnt = 1;
}
else cnt ++;
}
cout << ans << '\n';
// B ...
r = gcd(k = cur, m - cur);
cur = cnt = 0;
for (int i = 1; i <= n + 1; i ++ ) {
if (a[i] != a[i - 1]) {
if (a[i - 1] == b[m] && lst == b[1])
for (int l = 1;;l ++ )
if (l * k <= cur * r && l * (m - k) <= cnt * r) ans ++;
else break;
lst = a[i - 1];
if (a[i - 1] == b[1]) cur = cnt;
cnt = 1;
}
else cnt ++;
}
cout << ans << '\n';
Solution - 2
- 对于其他的数据,时间复杂度 \(\mathcal O(n\sqrt m)\)。(另外需结合特殊性质)
考虑到对于序列 \(B\) 来讲,将其化为最简的形式会更为方便的计算,并且对答案不会产生影响。(对于一个连续的数的个数除以其 \(\gcd\) 即可)
for (int i = 1; i <= cnt; i ++ )
if (r == -1) r = B[i].tot;
else r = gcd(r, B[i].tot);
for (int i = 1; i <= cnt; i ++ ) B[i].tot /= r;
我们发现,对于这个最简形式,所有的连续的段的倍数所形成的序列[1],都是其满足条件的序列 \(A\)。
那么对于这形成的所有的序列 \(B'\) ,只有长度小于等于序列 \(A\) 的序列,才满足可尝试匹配的条件,这些大约是 \(\sqrt{m}\) 个( 吗?)。
然后对于每个序列 \(B'\),对序列 \(A\) 进行匹配算法。(可以用 \(\text {KMP}\) 匹配算法,或是字符串哈希等等)。
Code - 1 & 2
#include <bits/stdc++.h>
using namespace std;
const int N = 5e6 + 7;
const long long mod = 13537;
inline int read(){
int x = 0; bool f = false; char c = getchar();
for (; !isdigit(c); f |= (c == '-'),c = getchar());
for (; isdigit(c); x = (x<<1) + (x<<3) + (c^48),c = getchar());
return f ? -x : x;
}
inline int gcd(int a, int b) {
while (b ^= a ^= b ^= a %= b);
return a;
}
struct node {
int t, s;
long long tot;
node(){}
node(int a,int b, int c) {
t = a; tot = b; s = c;
}
} w[N], e[N];
int n, m, top;
int cur, r = -1, k, cnt, lst, tmp;
long long hsh[N], p[N], sump[N], tit, hshu, ans, mx;
vector <int> g;
inline long long gethsh(int l, int r) {
if (l > r) return 0;
return hsh[r] - hsh[l - 1] * p[r - l + 1];
}
signed main() {
n = read(); m = read();
p[0] = 1ll; hsh[0] = 0; sump[0] = 1ll;
for (int i = 1; i <= n; i ++ ) {
int x = read();
hsh[i] = hsh[i - 1] * mod + x;
sump[i] = sump[i - 1] + (p[i] = p[i - 1] * mod);
if (x != lst) e[++ top] = node(x, 1ll, i);
else e[top].tot ++;
lst = x;
}
lst = 0;
for (int i = 1; i <= m; i ++ ) {
int x = read();
if (x != lst) w[++ cnt].t = x, w[cnt].tot = 1;
else w[cnt].tot ++;
if (x == w[1].t) cur ++;
lst = x;
}
if (cnt == 1){
for (int i = 1; i <= top + 1; i ++ )
if (e[i - 1].t == w[1].t) ans += ((e[i - 1].tot + 1) * e[i - 1].tot) / 2;
}
else if (cnt == 2) {
r = gcd(w[1].tot, w[2].tot);
for (int i = 1; i <= top + 1; i ++ )
if (e[i - 1].t == w[2].t && e[i - 2].t == w[1].t)
for (int l = 1; ;l ++ )
if (l * w[1].tot <= e[i - 2].tot * r && l * w[2].tot <= e[i - 1].tot * r) ans ++;
else break;
}
else {
e[top + 1].s = n + 1;
for (int i = 1; i <= top; i ++ ) {
if (e[i].t == w[1].t) g.push_back(i);
mx = max(mx, e[i].tot);
}
int sum = 0, ty = g.size();
for (int i = 1; i <= cnt; i ++ )
if (r == -1) r = w[i].tot;
else r = gcd(r, w[i].tot);
for (int i = 1; i <= cnt; i ++ ) sum += (w[i].tot /= r);
for (int l = 1, tmp = sum; tmp <= n && l <= mx; l ++ ) {
hshu = 0;
for (int i = 1; i <= cnt; i ++ ) {
hshu *= p[w[i].tot * l];
hshu += w[i].t * sump[w[i].tot * l - 1];
}
for (int i = 0; i < ty && e[g[i]].s + tmp - 1 <= n ; i ++ ){
if (e[g[i]].tot < w[1].tot * l) continue;
tit = gethsh(e[g[i] + 1].s, e[g[i] + 1].s + tmp - w[1].tot * l - 1) + w[1].t * p[(tmp - w[1].tot * l)] * sump[w[1].tot * l - 1];
if (tit == hshu) ans ++;
}
tmp += sum;
}
}
cout << ans << '\n';
return 0;
}
(存在线性做法。。。
如果最简形式为 \(aab\),那么它的倍数就是 \(aaaabb,aaaaaabbb,\dots\) ↩︎