LGR-144-题解

A. 新概念报数

Description

现在 A 和 B 玩起了报数游戏,但是他们非常喜欢 2 这个数字,于是制定了如下规则:

  • 如果一个数 0a263 满足 popcount(a)3,那么这个数字是非法的,对方需要回答 No,Commander
  • 否则,这个数是合法的,对方需要回答下一个合法的数。

坐在旁边的你当然不知道游戏的乐趣,你只想知道某次报数之后对方应该回答什么。

Solution

其中 popcount(a) 是指 a 的二进制下的 1 的个数,可通过 lowbitlogn 求解,也可以通过倍增的 log(logn) 求解。

  • 如果 popcount=0 时,即 a=0,此时下一个满足条件的数为 1

  • 如果 popcount=1 时,即 a=2x,此时无论在任何位置添加 2y 都可以。当 y=0 时,答案最小,即 a+1

  • 如果 popcount=2 时,

    • a 为奇数时,二进制下的最低位为 1。此时对 a+1,二进制的数将会进位,但是并不会使得其 popcount 变大,所以答案即为 a+1

    • a 为偶数时,二进制下的最低位为 0。此时对 a+1popcount 将会变为 3,不合法。

      考虑奇数时的情况,将 a 的最低位的 1 上面加上 1。满足条件。

    答案可以归结为 a+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 决定创造一个河外塔问题。

既然是河内塔问题的延申,就得有些一样的东西:有三个柱子
ABC ,以及 n 个圆盘,其中编号为 i 的圆盘的半径长为 i,这些圆盘最开始都在 A 上,最终都要顺序(即从上往下从小到大)地移到 C 上。

既然是河内塔问题的延伸,就得有些不同的东西:最开始在 A 上面的圆盘并不是顺序的,由于这个限制,我们也不在意移动过程中的顺序,也就意味着你可以将一个大的圆盘放在小的圆盘上。

但是 X_Xy 很懒,他只想让你操作至多 106 次。

Solution - 1

  • 通过 stack 模拟,时间复杂度 O(n2)

假设现在需要将编号为 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

  • 基数排序解决,时间复杂度为 O(2nlogn)

出现了是“鸽鸽子”(建议 bdfs

Solution - 3

  • 归并排序(分治)解决,时间复杂度为 O(32nlogn)

考虑这样一个过程:

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

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

  3. 然后将柱子 tmp1 上面的数放到柱子 tmp2 上面。

定义几个操作为:

  • S(l,\ r,\ from,\ to,\ tmep,\ type) 表示为将区间在 [l,r] 的数从柱子 from( 借助柱子 temp)移动到柱子 to 上面.

    其中 type={0,1}, 表示到柱子 to 时是逆序排列或是正序排列。

  • F(k,from,to) 表示将 k 个数,从柱子 from 移动到柱子 to 上面,顺序将会相反。

  • D(k,\ from,\ to1,\ to2,\ Δ) 表示为将柱子 from 上的 k 个数字,分别放到柱子 to1to2 上面。

    其中将 Δ 的数放在 to1 上面,大于 Δ 的数放在 to2 上面。

可以发现其答案就是 S(1,\ n,\ A,\ C,\ B,\ 1)

每次操作这样的过程 S(l,\ r,\ from,\ to,\ tmep,\ 1) 可以转化为:

D(r-l+1, from, to, temp, mid)S(l, mid, to, from, tmp, 0)S(mid+1, r, tmp, to, from, 1)F(mid-l+1, from,to)

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

定义:若序列 A

AA...Aa1 BB...Ba2 CC...Ca3 

序列 B

AA...Ab1 BB...Bb2 CC...Cb3 

其中 A,B,C,a1,a2,a3,...,b1,b2,b3,...N+

若有 a1b1=a2b2=a3b3=...=k ,k>0,则称 AB 互为相似序列。

特别的,长度为 0 的序列不与任何序列成相似序列。

不难证明此定义下序列间的相似具有传递性。

求给定的序列 A 中有多少个子串与给定序列 B 互为相似序列。

Data range

数据点 n m 特殊性质
1~2 20000 20000
3~4 5×106 5×106 A
5~6 5×106 5×106 B
7~10 5×106 5×106 C

对于 100% 的数据,保证有  i[1,n]1Ai7 i[1,m]1Bi7

对于 100% 的数据,保证 1n,m5×106

特殊性质 A:保证  i,j[1,m]Bi=Bj

特殊性质 B:保证有且仅有一个 k[1,m1],使得  i,j[1,k]Bi=Bj i,j[k+1,m]Bi=Bj

特殊性质 C:保证第 10 个点中 n,m5×105,且其他点中连续段仅有不超过 100 种不同的长度。

Solution - 1

  • 对于特殊性质 A。

保证了序列 B 中只会出现一个数,可以将其缩为一个数,那么对于序列 A 来讲,只需要找出只包含 Bi 的子序列的个数即可。

这样的子序列是一个连续的 Bi,令其长度为 ,那么这段的贡献就是 (+1)2

  • 对于特殊性质 B。

与特殊性质 A 同理,只需要找到 A 中相邻两个数恰好为 B1,Bm 的位置,然后统计即可。

序列 B 中的 B1Bm 同时也可以缩小,但是需要使得 B1Bm 的比例相同,即最简形式(除以 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

  • 对于其他的数据,时间复杂度 O(nm)。(另外需结合特殊性质)

考虑到对于序列 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;

我们发现,对于这个最简形式,所有的连续的段的倍数所形成的序列,都是其满足条件的序列 A

那么对于这形成的所有的序列 B ,只有长度小于等于序列 A 的序列,才满足可尝试匹配的条件,这些大约是 m 个( 吗?)。

然后对于每个序列 B,对序列 A 进行匹配算法。(可以用 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, ↩︎

posted @   Ciaxin  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示