Str 真题解(置换)

题面

对于字符串 s 定义一个变换 f(s) 表示, 1k|s|/2 ,将 s 中从后往前第 k 个字符插入从前往后第 k 个字符和第 k+1 个字符之间后得到的字符串,例如 abcdef 变换后得到 afbecd .

现在给出用 f(s) 变换过 k 次以后的字符串(即执行 ks:=f(s)),求变换之前的字符串 .

(别骂我,这是原题面)

3|s|103,1k109 .

置换

这里没有群论

这里没有群论 .

这里的置换是狭义的,正经置换看 OI-Wiki(内含 Burnside,慎入)

置换

一个置换 p 定义为一个排列,置换相当于一个运算,将原来在位置 i 的东西变到位置 pi .

置换的乘法(复合)

你对一个东西施加置换 p 然后施加置换 q,就相当于施加 pq .

显然新的置换 h=pq 由下式直接表示:

hi=qpi

置换乘法的单位元

恒等置换 id 定义为 idi=i .

显然任何置换 p 满足 pid=idp,于是 id 就是 的单位元 .

置换乘法的结合律

结合律:

pqr=p(qr)

为啥?

这里我们用函数表示下标,因为下标实在太多了 .

(pqr)(i)=p(q(r(i)))=p((qr)(r)=(p(qr))(i)

Q.E.D. 是不是很显然 .

置换快速幂

有单位元,有结合律,显然可以 log 快速幂吧 .

置换求乘法逆

显然求逆就是把置换逆过来了(类似反函数?)(从施加的角度看,真的很显然)

原来是 ipi,现在就是 pii .

直接模拟算就完了 .

真题解

题目要求的 f 可以看做一个置换 p .

于是 (pk)1 就是我们要对字符串 s 施加的置换 .

直接算出来,时间复杂度 O(|s|logk) .

一种可能的代码实现

因为求逆可以先求也可以后求甚至可以直接拿眼看出来,所以可能的代码实现有很多 .

这里是先快速幂再求逆,应该是好理解的 .

// 增加了注释
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
#include <set>
#include <bitset>
#include <cassert>
using namespace std;
const int N = 1555;
int k;
string s;
struct perm // 置换
{
	perm(int x=1){n=x; for (int i=1; i<=x; i++) a[i] = i;} // 初始化为恒等置换
	int operator [](const unsigned& id)const{return a[id];}
	int& operator [](const unsigned& id){return a[id];} // 元素
	perm operator * (const perm& rhs)const // 乘法
	{
		assert(n == rhs.n);
		perm x(n);
		for (int i=1; i<=n; i++) x[i] = rhs[a[i]];
		return x;
	}
	perm& operator *= (const perm& rhs){return *this = *this * rhs;}
	perm inv() // 求逆
	{
		perm ans(n);
		for (int i=1; i<=n; i++) ans[a[i]] = i;
		return ans;
	}
	inline void prt() // debug
	{
		for (int i=1; i<=n; i++) printf("%d ", a[i]);
		puts("");
	}
	inline size_t size()const{return n;}
private:
	int n, a[N];
};
perm qpow(perm a, int n) // 快速幂
{
	perm ans(a.size());
	while (n)
	{
		if (n&1) ans *= a;
		a *= a; n >>= 1;
	} return ans;
}
perm create(int n) // 生成题目说的变换 f
{
	perm ans(n);
	int ptr1 = 1, ptr2 = n, cc = 0;
	while (ptr1 <= ptr2){ans[++cc]=ptr1; if (cc<n) ans[++cc]=ptr2; ++ptr1; --ptr2;}
	return ans;
}
int main()
{
	scanf("%d", &k);
	cin >> s; int l = s.length(); s = "$" + s;
	perm ans = qpow(create(l), k).inv();
	for (int i=1; i<=l; i++) putchar(s[ans[i]]); // 对 s 施加置换
	puts("");
	return 0;
} 

关于循环节做法

看起来这个东西有循环节?打了一发直接过了,出题人可真 sb 炸弹熊

实际上我们可以证明这个东西有循环节且循环节是不大于 |s| 的 .

从置换的角度考虑,如果 i 能到 pi 就连一条 ipi 的有向边 .

n 个点 n 条边显然可以构成一个内向基环树森林,运算相当于在图上走一次,一直走肯定能走到环上 .

这说明任何置换 p 的方幂都是有循环节的 .

然而这题里的 p 更加特殊:

咕咕咕

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