Skip to content

Commit

Permalink
Content Update:
Browse files Browse the repository at this point in the history
1. Migrate legacy-2018-01-22 and legacy-2019-09-19 from CSDN.
  • Loading branch information
MegaOwIer committed Dec 6, 2023
1 parent e9049e3 commit 09240a8
Show file tree
Hide file tree
Showing 2 changed files with 312 additions and 0 deletions.
228 changes: 228 additions & 0 deletions content/post/legacy-2018-01-22/legacy-2018-01-22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
---
title: "[UOJ 287][WC 2017] 棋盘"
slug: legacy-2018-01-22
date: 2018-01-22
categories:
- ICPC
description: 本文由原 CSDN 博客直接迁移而来,迁移时仅作最基础的格式修缮,不保证其内容格式与本站完全适配。
draft: false
lastmod: 2018-01-22
---

## Description

> 给你一张 $n$ 个点 $m$ 条边的无向图,点编号 $1,2,\cdots,n$,每个点上放有一枚棋子,棋子编号 $0,1,\cdots,n-1$,一次操作是指将编号为 $0$ 的棋子与图上和它所在点有边相连的点上的棋子交换位置。现在有 $q$ 组询问,每次询问给定的局面是否能从给出的初始局面经若干次操作得到。
>
> $n\leq 50,m\leq 100,q\leq 1000$
>
> 时间限制:3s,空间限制:1G
[题目传送门](http://uoj.ac/problem/287)

## Tags

置换与置换群,Schreier-Sims算法

~~没有群的相关知识的同学就可以拿40分裸暴力然后继续切其他题啦~~

## Solution

~~被一道板子题坑了两天~~。。。

根据题目,图上每个点都有两个信息需要维护:点的编号和棋子编号。由于在一个确定的局面下二者之间有一一对应关系,很自然的想法便是把其中一个作为下标,另一个作为值。又由于题目给出的条件几乎都和点编号有关,显然把棋子编号作为下标更加靠谱。

于是我们维护的是一个 $n$ 阶排列 $P$,其中 $P_i$ 表示编号为 $i$ 的棋子所在点的编号,那么每一次操作就是把这个排列的首项 $P_0$ 与某一项交换。

下面来考虑点的编号在转移过程中满足的若干性质。

不难发现这样一件事:如果原图 $G$ 存在一个环 $V_1V_2\cdots V_t$,并且 $0$ 号棋子在这个环中的某一点(不妨设为 $V_1$)上的话,经过 $t$ 次操作使得 $0$ 棋子围着环转一圈之后,原先在 $V_2,V_3,\cdots,V_t$ 上的棋子就会到 $V_t,V_2,\cdots,V_{t-1}$ 上。将这一过程重复若干次便可取遍所有的圆上排列。

而这一过程的本质就是置换
$$
\begin{pmatrix}
V_2 & V_3 & V_4 & \cdots & V_t \newline
V_t & V_2 & V_3 & \cdots & V_{t-1}
\end{pmatrix}
$$

显然,对于每一个简单环,我们都可以得到一个这样的置换。记这些置换组成的集合为 $R$。

不过这样得到的置换有一个问题,那就是我们并没有讨论 $0$ 所在的点的变化。于是我们可以想办法让这一因素从我们的讨论范围中消失——对于每种局面,先把 $0$ 沿一条路径移动到某一个确定的点(不妨设为 $1$ 号点)上,即钦定 $P_0=1$,于是只需讨论排列 $P_1,P_2,\cdots,P_{n-1}$ 之间的转移。可以证明这样做是正确的。

设任意一种局面 $S$ 经此处理之后得到的排列为 $f(S)$,于是问题就转化为对于给定的初始状态 $A$ 和每次给出的状态 $B$,$f(B)$ 能否由 $f(A)$ 经若干 $R$ 中的置换作用之后得到。
如果把排列也看作是置换的话,那就是在问 $f(B)\cdot f(A)^{-1}$ 是否在以 $R$ 为生成集的置换群中。

至此,问题已经被解决的一大半,剩下的就是如何快速判断一个给定的置换是否在以给定集合为生成集的群中。

为了解决这个问题,我们要引入Schreier-Sims算法。

### 太长不看版

自行查阅2014年国家集训队论文《对置换群有关算法的初步研究》,剩下的部分是论文中介绍的算法的应(mú)用(bǎn)。

### 正常版

坑坑坑

## AC Code

时间复杂度:$O(n^5)$(大概吧)

空间复杂度:$O(n^3)$

```c++
#include<bits/stdc++.h>
using namespace std;
const int MX=55;
#define pb push_back

int N,M,Q,pre[MX],f[MX],g[MX],pos[MX],t,ls[MX],idx,m,b[MX];
int lr[MX],rid[MX][MX];
vector<int> G[MX];
struct Permutation
{
int a[MX];
Permutation(){memset(a,0,sizeof(a));}
bool isI()
{
for(int i=1;i<N;i++)if(a[i]!=i)return 0;
return 1;
}
Permutation operator * (Permutation& b)
{
static Permutation ans;
for(int i=1;i<N;i++)ans.a[i]=b.a[a[i]];
return ans;
}
Permutation inverse()
{
static Permutation ans;
for(int i=1;i<N;i++)ans.a[a[i]]=i;
return ans;
}
}s[MX][MX],r[MX][MX],rr[MX][MX],ans;
bool vis[MX],ins[MX];

inline int read()
{
static int x;
static char c;
c=getchar(),x=0;
while(!isdigit(c))c=getchar();
while(isdigit(c))x=x*10+c-'0',c=getchar();
return x;
}
inline void dfs(int u,int f)
{
int w;
pre[u]=f,vis[u]=1,ins[u]=1;
for(auto i:G[u])if(i!=f)
{
if(vis[i])
{
if(!ins[i])continue;
++t;
for(int j=0;j<N;j++)s[1][t].a[j]=j;
w=u;
while(pre[w]!=i)s[1][t].a[w]=pre[w],w=pre[w];
s[1][t].a[w]=u;
}
else dfs(i,u);
}
ins[u]=0;
}
inline void bfs()
{
int u=b[idx],v;
static int q[MX],hd,tl;
static char vst[MX];
static Permutation t;
q[hd=tl=1]=u;
memset(vst+1,0,sizeof(char)*(N-1));
vst[u]=1;
for(int i=1;i<N;i++)r[idx][i].a[1]=0;
for(int i=1;i<N;i++)r[idx][u].a[i]=rr[idx][u].a[i]=i;
lr[idx]=1,rid[idx][1]=u;
while(hd<=tl)
{
u=q[hd++];
for(int i=1;i<=ls[idx];i++)
{
t=r[idx][u]*s[idx][i],v=t.a[b[idx]];
if(!vst[v])
{
vst[v]=1,q[++tl]=v;
r[idx][v]=t,rr[idx][v]=t.inverse();
rid[idx][++lr[idx]]=v;
}
}
}
}
inline void Divide(Permutation& h)
{
for(int i=idx+1;i<=m;i++)if(h.a[b[i]]!=b[i])
{
if(!r[i][h.a[b[i]]].a[1])return;
h=h*rr[i][h.a[b[i]]];
}
}
inline void Schreier_Sims()
{
Permutation h;
bool o;
idx=m=1;
for(int i=1;i<=ls[1];i++)for(int j=1;j<N;j++)
if(s[1][i].a[j]!=j)b[1]=j;
bfs();
while(idx)
{
o=1;
for(int i=1;o&&i<=lr[idx];i++)for(int j=1;j<=ls[idx];j++)
{
h=r[idx][rid[idx][i]]*s[idx][j];
h=h*rr[idx][h.a[b[idx]]];
Divide(h);
if(!h.isI()){o=0;break;}
}
if(o)--idx;
else
{
if(++idx>m)
{
ls[++m]=0;
for(int j=1;j<N;j++)if(h.a[j]!=j)b[m]=j;
}
s[idx][++ls[idx]]=h;
bfs();
}
}
}
inline void Transfer(int *A)
{
int u=-1;
for(int i=0;i<N;i++)if(!A[i])u=i;
while(u)swap(A[u],A[pre[u]]),u=pre[u];
for(int i=0;i<N;i++)pos[A[i]]=i;
memcpy(A,pos,sizeof(int)*N);
}

int main()
{
N=read(),M=read(),Q=read();
for(int i=1,u,v;i<=M;i++)
u=read()-1,v=read()-1,G[u].pb(v),G[v].pb(u);
dfs(0,-1);
if(ls[1]=t)Schreier_Sims();
for(int i=0;i<N;i++)f[i]=read();
Transfer(f);
while(Q--)
{
for(int i=0;i<N;i++)g[i]=read();
Transfer(g);
for(int i=1;i<N;i++)ans.a[f[i]]=g[i];
if(t)Divide(ans);
puts(ans.isI()?"Yes":"No");
}
return 0;
}
```
84 changes: 84 additions & 0 deletions content/post/legacy-2019-09-19/legacy-2019-09-19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Codeforces Gym 100015 简要题解
slug: legacy-2019-09-19
date: 2019-09-19
categories:
- ICPC
description: 本文由原 CSDN 博客直接迁移而来,迁移时仅作最基础的格式修缮,不保证其内容格式与本站完全适配。
draft: false
lastmod: 2019-09-19
---

[提交链接(Codeforces::Gym)](https://codeforces.com/gym/100015)

[参考代码](https://github.com/MegaOwIer/CodeArchive/tree/master/Codeforces_Gym/100001-100200)

## A. Another Rock-Paper-Scissors Problem

归纳一下可以发现这就相当于求 $N$ 在三进制下的表示。

时间复杂度:$O(\log N)$

## B. Ball Painting

用 $f_{i, j}$ 表示有 $j$ 个球被涂黑且这些球总共占用 $i$ 行的方案,枚举最后一个球是否占用了一个新的列可得递推式

$$ f_{i, j} = f_{i, j - 1} \times (2i - j + 1) + f_{i - 1, j - 1} \times 4$$

预处理后直接输出即可。

时间复杂度:$O(N^2)$

## C. City Driving

基环树上最短路。实现起来稍微有点麻烦的板子题。

时间复杂度:$O(N)$

## D. Drunken Walk

令 $f_i, p_i$ 分别表示在原图上从 $i$ 点开始走的期望边数以及从 $1$ 点开始走经过 $i$ 点的概率。

对于一条边 $e(u, v)$,无视掉它后从点 $u$ 出发走过的期望距离从 $f_u$ 变成
$$f'_u = \frac{f_u \times w_u - w_e(f_v + 1)}{w_u - w_e}$$
其中 $w_u$ 表示 $u$ 的所有出边的权值和,$w_e$ 表示边 $e$ 的权值。

最后注意到 $f_u$ 对答案贡献的权重为 $p_u$,因此无视掉边 $e$ 后的期望距离为
$$f_1 + p_u(f'_u - f_u)$$
枚举所有边然后取最大值即可。

时间复杂度:$O(M)$

## E. Empty Triangles

暴力求出所有交点,暴力建边。

注意到抽象成图后每个点的度数恰好为 $4$,因此可以暴力求三元环的个数。

时间复杂度:$O(N^2 \log N)$

## F. Fighting for Triangles

用 $f_S$ 表示当前已经有集合 $S$ 中所有边的情况下先手与后手得分差的最大值。

状压 DP 预处理后直接查询即可。

时间复杂度:$O(N \cdot 2^N)$

## G. Guessing Game

注意到 $a_i + b_j \leq c \Leftrightarrow a_i - (- b_j) \leq c$ 以及 $a_i + b_j \geq c \Leftrightarrow (-b_j) - a_i \leq c$,这是差分约束的形式。于是建图之后判断图中是否存在负环即可。

时间复杂度:$O(Q(N+M))$

## H. Hidden Code

显然两个串做差之后的结果要么是密码串的一个前缀,要么是密码串后面加上加密结果的一个前缀。

枚举长度检查即可。

时间复杂度:$O(Nl^2)$

## I. Identity Checker

想不出来有什么正经做法。反正我是枚举若干个特值检查来的。

0 comments on commit 09240a8

Please sign in to comment.