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)\)

考虑这样一个过程:

  1. 对于一段区间 \(\left[l,r\right]\),我们可以先将 \([l,mid]\) 的数排完序放到柱子 \(\text{tmp1}\) 上面,并且是逆序排列。

  2. 再将区间 \(\left(mid,r\right]\) 的数排完放到柱子 \(\text{tmp2}\) 上面,并且是顺序排列。

  3. 然后将柱子 \(\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)}\) 可以转化为:

\[D\,\text{(r-l+1, from, to, temp, mid)}\to S\,\text{(l, mid, to, from, tmp, 0)}\to\\ S\,\text{(mid+1, r, tmp, to, from, 1)}\to F\,\text{(mid-l+1, from,to)} \]

\(\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\)

\[\underbrace{AA...A}_{a_1\textsf 个}\ \underbrace{BB...B}_{a_2\textsf 个}\ \underbrace{CC...C}_{a_3\textsf 个}\ \dots \]

序列 \(\Beta\)

\[\underbrace{AA...A}_{b_1\textsf 个}\ \underbrace{BB...B}_{b_2\textsf 个}\ \underbrace{CC...C}_{b_3\textsf 个}\ \dots \]

其中 \(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;
}

(存在线性做法。。。



  1. 如果最简形式为 \(aab\),那么它的倍数就是 \(aaaabb,aaaaaabbb,\dots\) ↩︎

posted @ 2023-07-09 14:50  Ciaxin  阅读(18)  评论(0编辑  收藏  举报