[luogu12542] [APIO2025] 排列游戏 - 交互 - 博弈 - 分类讨论 - 构造
传送门:https://www.luogu.com.cn/problem/P12542
题目大意:给定一个长为 n n n 的排列和一张 m m m 个点 e e e 条边的简单连通图。每次你可以在图上每个点设置一个 0 ∼ n − 1 0\sim n-1 0∼n−1、两两不同的权值发给交互库,交互库会从图中选择一条边,然后取出两个端点上的权值,作为排列的两个下标并进行交换。你可以随时停止游戏。
你要先求出:在两人的最优策略下,最终排列有多少个数归位(即 p [ i ] = i p[i]=i p[i]=i),你要让这个值最大化,交互库要让它最小化;并跟交互库玩这个游戏,在 3 n 3n 3n 次操作内至少达到这个最优解。
数据范围: m ≤ n ≤ 400 m\le n\le 400 m≤n≤400。
Warning:本题思维复杂性较高,阅读本题解之前请确保你处于大脑清醒的状态。
先看一些简单的 Case:
m = 2 m=2 m=2
相当于你可以每次直接交换排列的两个位置,那么就非常简单了:你只需要贪心地每次把一个数归位,至多 n − 1 n-1 n−1 次操作就可以让整个排列全部归位。
e > m e>m e>m
这是本题的第一个重要的观察:为什么 e > m e>m e>m 这个看起来很复杂的情况会放在第二个子任务而且只有 6 6 6 分?
答:此时你无能为力,初始排列直接就是最优解。
为什么?我们直接证明一个更强的结论:只要图中有 ≥ 3 \ge 3 ≥3 度的点,你就没有任何办法了。
考虑图中一个点 x x x 被设置为权值 t [ x ] t[x] t[x],如果选择的一条边与 x x x 相连,什么情况下会对答案产生正的贡献?只有两种情况:将 t [ x ] t[x] t[x] 这个数从别的位置交换回来;或者将 p [ t [ x ] ] p[t[x]] p[t[x]] 这个数交换到了它正确的位置上去。
由于图中所有的点权是两两不同的,因此上述两种情况在一个点 x x x 处都至多只有一条边满足。换句话说对于任意点而言,其相连的边中至多只有两条会让答案增加。
因此,如果图中存在 ≥ 3 \ge 3 ≥3 度的点,交互库就一定能选择其一条邻边,使得操作之后答案严格不增。因此你的一切努力都是徒劳的。
那么问题来了:一张简单连通图没有 ≥ 3 \ge 3 ≥3 度的点,有哪几种可能呢?只有两种可能:链或环。
此外,容易看出当未归位的点数不超过 m − 1 m-1 m−1 时,也无法更进一步了,因为将已经归位的点加入图中是没有意义的。这作为平凡的情形,后文中将不再讨论。
e = m − 1 e=m-1 e=m−1
链的情况,我们开始进行一些真正接近题目本质的分析。
对于任意排列 p p p 而言,可以建一张 n n n 个点的图,从每个点 i i i 向 p [ i ] p[i] p[i] 连边,那么得到的一定形如若干个环。其中大小为 1 1 1 的环表示已经归位的点,我们不去管它;考虑其它的环我们怎么处理。
假设我们交换了两个位置 ( i , j ) (i,j) (i,j),实际上就是在图中交换了这两个点的出边,对环结构的影响如下:
-
如果 i , j i,j i,j 原本不在同一个环上,这次交换会将两个环直接合并;
-
如果 i , j i,j i,j 原本在同一个环上,假设环大小为 p p p,两点在环上的距离为 q q q,则这次交换会将环拆分为两个环,大小分别为 q q q 和 p − q p-q p−q。
-
特别地,交换同一个环上相邻的两个位置会增加一个归位的点(因为多了一个大小为 1 1 1 的环)。
然后我们有如下几种操作: -
“强制归位”:对于一个大小 ≥ m \ge m ≥m 的环,只需要顺次选择环上 m m m 个相邻的点,交互库就必然要选择两个相邻点交换使得多一个点归位;
-
“强制合并”:对于两个大小加起来 ≥ m \ge m ≥m 的环,可以在一个环上顺次选一些相邻的点,再在另一个环上顺次选一些相邻的点,这样交互库就要么选择一个环上的相邻两个点(这会让一个点归位)要么选择两个环上的点(这会合并这两个环)。这一操作也可以推广到多个环上,只要这些环大小之和 ≥ m \ge m ≥m,就逼迫交互库选择其中相邻两个环进行合并。
基于这两种操作我们可以轻松解决链的情况:只需要先将所有环全都合并起来,再每次强制归位一个点,就能操作到仅剩 m − 1 m-1 m−1 个点未归位。
e = m e=m e=m
接下来是环的情况,在展开更详细的分类讨论之前,首先要说明环相对于链的一些不同之处。
-
对于“强制合并”影响不大,只不过在多个环时让交互库多了一种将首尾两个环合并的选择;
-
然而对于“强制归位”的影响是关键的,因为这相当于在链的情形中增加了允许交互库选择链的首尾两个点,这会导致环断成两段而不是恰好拆出一个点。因此如果你还想达到强制归位的效果,就只能在大小恰好等于 m m m 的环上执行。
-
那么遇到更大的环该怎么办?为此我们必须开发出一种“强制拆分”操作:对于某个你想要的拆分距离 d d d,如果我们能在当前这个环上选取一系列点,使得(包括首尾在内,下同)相邻两个点的距离要么是 1 1 1 要么是 d d d,那么交互库为了不增加归位的点数就必须选择两个间距为 d d d 的点。如下图就是 d = 2 d=2 d=2 的情形,分为 m m m 是偶数/奇数两种情形,对应的选取序列分别为 0 , 2 , 4 , … , m − 2 , m − 1 , m − 3 , … , 1 0, 2, 4, \dots, m-2, m-1, m-3, \dots, 1 0,2,4,…,m−2,m−1,m−3,…,1 和 0 , 2 , 4 , … , m − 1 , m − 2 , m − 4 , … , 1 0, 2, 4, \dots, m-1, m-2, m-4, \dots,1 0,2,4,…,m−1,m−2,m−4,…,1。需要注意的是,这样的操作并不是我们随心所欲的,需要根据 m , d m,d m,d 和具体的环大小构造方案。
再讨论一些比较平凡的界: -
对于剩余点数小于 m m m 的情形,显然没法继续操作了;
-
对于剩余点数恰为 m m m 的情形,可以将所有环合并成一个之后归位一次,得到剩余 m − 1 m-1 m−1 个点即为最优;
-
对于剩余点数恰为 m + 1 m+1 m+1 的情形,也没法更进一步了,因为想要更进一步就必须先搞出大小为 m m m 的环,但若果真如此那么剩下一个点已经自动归位了,于是就矛盾了。
那么问题来了:当剩余点数更多时,是否总能操作到只剩 m + 1 m+1 m+1 个点?我们需要进一步分类讨论。
e = m = 奇数 e=m=\texttt{奇数} e=m=奇数
先看 m m m 是奇数的情形。基于上面的“强制拆分”操作,我们知道一个大小为 m + 2 , m + 4 , … m+2, m+4,\dots m+2,m+4,… 的环总能通过每次拆下两个点最终达到 m m m,从而可以进行一次归位。也就是说任何大小至少为 m m m 的奇环都能带来 1 1 1 的贡献。
那么偶环呢?很遗憾,对于偶环我们又无能为力了。具体而言,考虑当前排列里的奇环总数(包括大小为 1 1 1 的奇环),我们可以说明这个数量是无法增加的。
证明很简单:如果想使得奇环的数量增加,唯有将一个偶环拆分成两个奇环。然而如果我们将偶环进行间隔二染色,再选出 m m m 个点填入图中时,你会发现一定有相邻两个点是同色的。于是交互库只要选这两个点,就会将原先的偶环断成两个偶环。
基于上述分析,当前排列中奇环的数量就成了答案的上界。对于 m = 3 m=3 m=3 的情形,我们可以直接对所有的奇环每次进行 − 2 -2 −2 操作,直到变成 3 3 3 之后进行一次归位,就达到了这一最优解。
然而,当 m m m 是更大的奇数时,可能面临这样的问题:有一系列小于 m m m 的奇环,我们不能直接对其进行归位操作,只能先与其他环合并;然而合并两个奇环的操作是我们必须尽量避免的,因为这会使得答案上界 − 2 -2 −2。
所以我们可以优先取出一个最大的奇环,首先考虑将其与所有的偶环合并,直到大小不小于 m m m 为止;若合并上所有偶环之后大小仍然小于 m m m,我们就不得不从大到小选取若干个奇环进行合并,直到合并出不小于 m m m 的奇环为止。
注意到一旦合并出了第一个不小于 m m m 的奇环,后续就不再需要进行两个奇环之间的合并了,因为对其进行拆分和归位操作之后会得到大小为 m − 1 m-1 m−1 的偶环,在操作其他奇环时可以先将其与这个 m − 1 m-1 m−1 合并,以得到足够大的奇环执行后续操作;操作最终又会得到一个 m − 1 m-1 m−1 ,循环使用。
于是我们就完成了 m m m 为奇数的情形,注意到操作次数显然是够用的。
e = m = 4 e=m=4 e=m=4
来到了 m m m 是偶数的情形,为了更加清晰,我们先看一下 m = 4 m=4 m=4 的简单情形。
此时的理论上限是剩余 5 5 5 个点,这是可以达到的,接下来将给出一系列操作:
- 对于 4 4 4 元环可以归位一个点;
- 对于 ≥ 6 \ge 6 ≥6 的环,可以从中拆下来一个 4 4 4 元环,选取点的序列为 0 , 1 , 5 , 4 0, 1, 5, 4 0,1,5,4;
- 对于 5 5 5 元环,不能直接进行操作,必须先与任意一个环合并;
- 对于两个 2 2 2,可以合并成一个 4 4 4;
- 对于两个 3 3 3,可以合并成一个 6 6 6;
- 尽量不要合并 2 2 2 和 3 3 3,因为得到 5 5 5 是不优的。
请注意上面的第二个操作,它可以扩展为一般的偶数 m m m:对于任意 ≥ 3 m 2 \ge {3m \over 2} ≥23m 的环而言,都可以拆出一个 m m m,选取点的序列为 0 , 1 , 2 , … , m 2 − 1 , 3 m 2 − 1 , 3 m 2 − 2 , … , m 0, 1, 2, \dots, {m \over 2} - 1, {3m \over 2} - 1, {3m \over 2} - 2, \dots, m 0,1,2,…,2m−1,23m−1,23m−2,…,m。如下图分别为 m = 4 , 6 m=4, 6 m=4,6 的情形。
容易看出,只要按照上述操作序列依次执行,除合并 2 2 2 和 3 3 3 外,每 3 3 3 步之内一定能进行一次归位操作(可以自行验证)。因此只要我们不去合并 2 2 2 和 3 3 3(这在剩余点数多于 5 5 5 时总是可以的),就能一直操作下去直到只剩 5 5 5 个点为止,操作次数也是够用的。
e = m = 偶数 e=m=\texttt{偶数} e=m=偶数
终于到了最复杂的情形,我们首先要看一下 m m m 是一般的偶数相比 m = 4 m=4 m=4 时有哪些变化:最重要的变化是“强制拆分一个 m m m”只能适用于 ≥ 3 m 2 \ge {3m \over 2} ≥23m 的环(至少我并没有想到直接的适用于更小的环的拆分方案),因此我们需要重新设计将大小在 m + 2 ∼ 3 m 2 − 1 m+2 \sim {3m \over 2}-1 m+2∼23m−1 之间的环拆分成 m m m 的方案。
- 对于大小为 m + 2 , m + 4 , … m+2,m+4,\dots m+2,m+4,… 的环,仍然可以每次拆分一个 2 2 2 出来,直到变成 m m m;
- 对于大小为 m + 3 , m + 5 , … m+3,m+5,\dots m+3,m+5,… 的环,每次拆一个 2 2 2 只能最终达到 m + 3 m+3 m+3,再拆成 m + 1 m+1 m+1 是没有意义的。因此我们必须要设计从 m + 3 m+3 m+3 直接拆出一个 3 3 3 的策略。
具体方法是对 m m m 按照 m o d 6 \mod 6 mod6 分类讨论:
- m m o d 6 = 0 : 0 , 1 , 2 , 5 , 8 , … , m − 1 , m − 2 , m − 3 , m − 6 , m − 5 , m − 8 , m − 9 , … , 4 , 3 m \mod 6=0: 0, 1, 2, 5, 8, \dots, m-1, m-2, m-3, m-6, m-5, m-8, m-9, \dots, 4, 3 mmod6=0:0,1,2,5,8,…,m−1,m−2,m−3,m−6,m−5,m−8,m−9,…,4,3;
- m m o d 6 = 2 : 0 , 1 , 4 , 7 , … , m − 1 , m − 2 , m − 3 , m − 6 , m − 5 , m − 8 , … , 2 , 3 m \mod 6=2: 0, 1, 4, 7, \dots, m-1, m-2, m-3, m-6, m-5, m-8, \dots, 2, 3 mmod6=2:0,1,4,7,…,m−1,m−2,m−3,m−6,m−5,m−8,…,2,3;
- m m o d 6 = 4 : 0 , 3 , 6 , … , m − 1 , m − 2 , m − 3 , m − 6 , m − 5 , m − 8 , … , 2 , 1 m \mod 6=4: 0, 3, 6, \dots, m-1, m-2, m-3, m-6, m-5, m-8, \dots, 2, 1 mmod6=4:0,3,6,…,m−1,m−2,m−3,m−6,m−5,m−8,…,2,1。
分类讨论看着很繁琐,其实本质都是同一种方案:先 3 3 3 个 3 3 3 个往前跳,再按照 − 1 , − 3 , + 1 , − 3... -1, -3, +1, -3... −1,−3,+1,−3...的模式往回跳。下图中展示了 m = 6 , 8 , 10 m=6,8,10 m=6,8,10 的情形。另外,这种拆出 3 3 3 个点的构造对于更大的环而言也是适用的。
再加上对小环的合并操作,我们便可以达到剩余 m + 1 m+1 m+1 的最优解。但操作次数是否满足要求?
容易想到的操作序列如下:
- 如果存在一个 m m m,就进行一次归位;
- 如果有两个环大小之和为 m m m,合并之;
- 如果最大环 ≥ 3 m 2 \ge {3m \over 2} ≥23m,拆出一个 m m m;
- 如果最大环 ≥ m + 2 \ge m+2 ≥m+2 但 < 3 m 2 < {3m \over 2} <23m,拆出一个 2 2 2 或 3 3 3;
- 如果最大的环 = m + 1 =m+1 =m+1,就将其随便合并一个环(例如次大环);
- 如果最大的环 < m <m <m,就从大到小开始合并;例外是如果它与次大环加起来等于 m + 1 m+1 m+1,就去考虑合并更小的环,除非别无选择。
然而,这样实际上并不能达到 3 n 3n 3n 的操作次数限制!考虑当前剩余的一系列环为 m − 1 , 2 , 2 , … , 2 m-1, 2, 2, \dots, 2 m−1,2,2,…,2,将执行如下一系列操作:
- 将 m − 1 m-1 m−1 和 2 2 2 合并为 m + 1 m+1 m+1;
- 将 m + 1 m+1 m+1 和 2 2 2 合并为 m + 3 m+3 m+3;
- 将 m + 3 m+3 m+3 拆分为 m m m 和 3 3 3;
- 对 m m m 执行一次归位变为 m − 1 m-1 m−1;
- 将 m − 1 m-1 m−1 和 3 3 3 合并为 m + 2 m+2 m+2;
- 将 m + 2 m+2 m+2 拆分为 m m m 和 2 2 2;
- 对 m m m 执行一次归位变为 m − 1 m-1 m−1。
总共使用 7 7 7 次操作进行了两次归位,除了 2 2 2 环少了一个外其他均不变,可知这样总的操作次数将达到 3.5 n 3.5n 3.5n 级别。
怎么办?容易发现这一操作序列即使处理 m = 4 m=4 m=4 也是不行的,需要回看当时是怎样避开这一问题的:现在相当于每次将 3 3 3 和 2 2 2 合并,然而这是我们不希望的,而当时是通过将两个 2 2 2 合并来避免了这一点。
这启发我们如果场上有 m − 2 m-2 m−2,就可以将其与 2 2 2 合并从而直接得到 m m m。然而 m − 1 m-1 m−1 是容易得到的( m m m 进行归位一次之后就天然得到 m − 1 m-1 m−1),但 m − 2 m-2 m−2 却并不容易得到。
再回看 m = 4 m=4 m=4 的情形,发现可以通过操作制造 2 2 2:将两个 3 3 3 合并得到 6 6 6,再将 6 6 6 拆分成 4 4 4 和 2 2 2。
因此,对于更大的 m m m,如果场上有两个 m − 1 m-1 m−1,就可以先合并得到 2 m − 2 2m-2 2m−2,再通过一次拆分得到 m m m 和 m − 2 m-2 m−2。这也正是这道题的最后一个关键处。而两个 m − 1 m-1 m−1 怎么得到?当然是从最初的一系列小环合并而来(或从更大的环上拆下来)。
总结上述的全过程,可以得到如下操作流程:
- 如果有 m m m,归位之;
- 如果有两个环大小之和为 m m m,合并之;
- 如果最大环 ≥ 3 m 2 \ge {3m \over 2} ≥23m,拆出一个 m m m;
- 如果最大环 ≥ m + 2 \ge m+2 ≥m+2 但 < 3 m 2 < {3m \over 2} <23m,拆出一个 2 2 2 或 3 3 3;
- 如果最大环 = m + 1 =m+1 =m+1,随便找一个环(如次大环)合并;
- 如果最大环 = m − 1 =m-1 =m−1 且次大环 = m − 1 =m-1 =m−1,合并二者;
- 如果最大环 = m − 1 =m-1 =m−1 且次大环 = m − 2 =m-2 =m−2,找出第三大环:如果为 2 2 2,则与 m − 2 m-2 m−2 合并(避免与 m − 1 m-1 m−1 合并得到 m + 1 m+1 m+1),否则与 m − 1 m-1 m−1 合并;
- 如果最大环 = m − 1 =m-1 =m−1 且次大环 < m − 2 <m-2 <m−2,就从次大环开始顺次合并(除非次大环与所有更小的环加起来已经不到 m m m 了,就将最大环与次大环合并);
- 如果最大环 < m − 1 <m-1 <m−1,就从最大环开始顺次合并。
分析总的操作次数:
我们要用 3 n 3n 3n 步归位不超过 n − m n-m n−m 个点,因此除了 “每 3 3 3 步归位一个点”以外,还有 3 m 3m 3m 步的冗余。
首先是最开始要合并出两个 m − 1 m-1 m−1,即使最初所有的环全是 2 2 2,这一步的额外开销不会超过 m m m;
其次进行主操作流程,直至剩余 2 m 2m 2m 个点以前一直能维持“(均摊下来)每 3 3 3 步归位一个点”:只需额外分析当 m − 1 m-1 m−1 合并上一个不太大的环导致要进行一系列拆 2 , 3 2, 3 2,3 的操作,注意到这每一个 2 2 2 或 3 3 3 都能在后续步骤中省下来一步(例如 m − 2 m-2 m−2 和 2 2 2 仅需两步就能归位一个点; m − 1 , m − 2 , 3 m-1, m-2, 3 m−1,m−2,3 可以通过 5 5 5 步归位两个点),因此之前拆出来的操作都能被均摊掉;
最后是剩余 2 m 2m 2m 个点之后,必须回滚到之前的操作流程。采用回先前 3.5 3.5 3.5 步归位一个点的流程,加上可能有额外的 m 2 m \over 2 2m 步拆分 2 , 3 2, 3 2,3 的操作,总的额外开销不超过 m m m。
综上,在“每 3 3 3 步归位一个点”以外,总的额外开销不会超过 2 m 2m 2m,于是本题终于彻底做完了。
#include <bits/stdc++.h>
using namespace std;
int Bob(std::vector<int> t);
int m,e,n,nwas,cnt;
vector<int> u,v,p;
vector<int> vis,deg;
vector<vector<int>> cycles;
vector<int> cyc_id, cyc_size, cyc_pos; // 一个点所在的环编号、大小以及它在环上的位置
vector<int> cyc_list,cyc_odd,cyc_even; // 按从大到小的顺序排序后的所有环,后面两个是所有的奇环和偶环
vector<int> dfn,map_g; // dfn是图上节点编号的映射,map_g是dfn的逆
vector<vector<int>> edges; // 图上的边
vector<int> oper,oper_tmp; // 操作序列
void get_cycle(){ // 生成排列里所有的环cycles.clear();cyc_list.clear();cyc_odd.clear();cyc_even.clear();nwas = 0;memset(vis.data(),0,sizeof(int) * n);for(int i = 0;i < n;++i) if(!vis[i]){if(i == p[i]){ // 把已经归位的去掉++nwas;continue;}vector<int> q; q.clear(); q.push_back(i); vis[i] = 1;for(int j = p[i];!vis[j];j = p[j]){q.push_back(j); vis[j] = 1;}int cycle_id = cycles.size(); // 当前环的编号cyc_list.push_back(cycle_id);for(int j = 0;j < q.size();++j){cyc_id[q[j]] = cycle_id; // 所在环编号cyc_size[q[j]] = q.size(); // 所在环大小cyc_pos[q[j]] = j; // 所在环上的位置}cycles.push_back(q);}// 按环长排序sort(cyc_list.begin(),cyc_list.end(),[=](int x,int y){return cycles[x].size() > cycles[y].size();}); // 将环按奇偶分类for(int i = 0;i < cyc_list.size();++i){int x = cyc_list[i];if(cycles[x].size() % 2) cyc_odd.push_back(x);else cyc_even.push_back(x);}
}
int nwdfn;
void dfs(int x){map_g[x] = nwdfn;dfn[nwdfn] = x;++nwdfn;for(int i = 0;i < edges[x].size();++i){int y = edges[x][i];if(map_g[y] != -1) continue;dfs(y);}
}
bool chk_deg(){oper.resize(m); memset(oper.data(),0,sizeof(int) * m);oper_tmp.resize(m); memset(oper_tmp.data(),0,sizeof(int) * m);deg.resize(m); memset(deg.data(),0,sizeof(int) * m);edges.resize(m);for(int i = 0;i < m;++i) edges[i].clear();for(int i = 0;i < e;++i){++deg[u[i]];++deg[v[i]];edges[u[i]].push_back(v[i]);edges[v[i]].push_back(u[i]);}for(int i = 0;i < m;++i) if(deg[i] >= 3) return 0;// 求出图上点编号的映射关系map_g.resize(m); memset(map_g.data(),-1,sizeof(int) * m);dfn.resize(m); memset(dfn.data(),-1,sizeof(int) * m);nwdfn = 0;if(e == m - 1){ // 链的情况,要从1度点开始dfsfor(int i = 0;i < m;++i) if(deg[i] == 1){dfs(i);break;}}else dfs(0);return 1;
}int get_ans(){get_cycle();int ans = nwas;if(!chk_deg()) return ans; // 有3度及以上的点就GG了if(m == 2) return n; // 2个点if(ans > n - m) return ans; // ans已经很大了if(e == m - 1) return n - m + 1; // 链if(ans == n - m) return n - m + 1; // 剩下刚好m个点没归位if(m % 2 == 0) return n - m - 1; // 如果是偶数,则一定能剩下m+1个点int nwsz = 0;for(int i = 0;i < cycles.size();++i){if(cycles[i].size() % 2 == 0) nwsz += cycles[i].size(); // 累加上所有偶环的大小else ++ans; // 一个奇环意味着答案能+1}// 要从大到小检查这些奇环,直到能合并出来一个>=m的为止bool fg = 0;for(int i = 0;i < cyc_odd.size() && nwsz < m;++i){int x = cyc_odd[i];if(fg){ // 这个奇环已经要被合并了nwsz += cycles[x].size();fg = 0;}else if(cycles[x].size() + nwsz < m){ // 要付出2的代价合并两个奇环ans -= 2;nwsz += cycles[x].size();fg = 1;}else break;}return ans;
}
inline void add_node(int &nw, int x){oper[dfn[nw++]] = x;} // 注意要套一层dfn
inline void add_node_cyc(int &nw, int x,int j){add_node(nw,cycles[x][j]);
}
void get_m(int x){ // 从第x个环上切下长为m的一段来// 0 1 2 ... m/2-1 3m/2-1 3m/2-2 ... m+1 mint nw = 0;for(int i = 0;i < m / 2;++i) add_node_cyc(nw,x,i);for(int i = m / 2 - 1;i >= 0;--i) add_node_cyc(nw,x,i + m);
}
void get_3(int x){ // 从第x个环上切下长为3的一段来int j;int nw = 0;for(j = 0;(m - j) % 3 != 1;++j){ // 前面%3多出来的部分add_node_cyc(nw,x,j);}for(;j < m;j += 3){ // 间隔3个往前调add_node_cyc(nw,x,j);}int fg = -1;for(j = m - 2;j > 0;j -= 3 + fg){ // 从m-2开始,按照-1 -3 +1 -3 -1...的模式往回跳add_node_cyc(nw,x,j);add_node_cyc(nw,x,j + fg);fg *= -1;}
}
void get_2(int x){ // 从第x个环上切下长为2的一段来int nw = 0;// 1 3 5 ... m-1 m-2 m-4 ... 2 0// 正着走,间隔2个放一个for(int j = 1;j < m;j += 2){add_node_cyc(nw,x,j);}// 倒着走,间隔2个放一个for(int j = m - 2;j >= 0;j -= 2){add_node_cyc(nw,x,j);}
}
void gen_merge2(int x,int y){ // 合并两个环,大小之和至少是mint nw = 0;for(int j = 0;nw + 1 < m && j < cycles[x].size();++j){ // 第一个环最多放m-1个点add_node_cyc(nw,x,j);}for(int j = 0;nw < m && j < cycles[y].size();++j){add_node_cyc(nw,y,j);}
}
void run(){ // 生成下一个操作序列,存在oper里if(m == 2){ // 把第一个i!=p[i]的强制归位for(int i = 0;i < n;++i) if(p[i] != i){oper[0] = i;oper[1] = p[i];return;}}if(e == m - 1 || nwas == n - m){// 如果是链,或者剩余未归位的总点数恰好等于m,可以直接这么干int nw = 0;// 从大到小遍历所有的环,把点顺次加进操作序列for(int i = 0;nw < m && i < cyc_list.size();++i){int x = cyc_list[i];for(int j = 0;nw < m && j < cycles[x].size();++j){add_node_cyc(nw,x,j);}}return;}int xx = -1;for(int i = 0;i < cycles.size();++i) if(cycles[i].size() == m){xx = i;break; // 找到了一个大小为m的环}if(xx != -1){ // 如果存在一个环刚好大小为m,就可以拆出来一个点int nw = 0;for(int j = 0;j < cycles[xx].size();++j){add_node_cyc(nw,xx,j);}return;}if(m % 2 == 1){ // 奇环// 由于还能继续操作,一定还有奇环int x = cyc_odd[0];int nw = 0;if(cycles[x].size() > m){ // 第一个奇环足够大// 0 2 4 ... m-1 m-2 m-4 ... 3 1// 正着走,间隔2个放一个for(int j = 0;j < m;j += 2){add_node_cyc(nw,x,j);}// 倒着走,间隔2个放一个for(int j = m - 2;j > 0;j -= 2){add_node_cyc(nw,x,j);}return; }if(cycles[x].size() == m){ // 第一个奇环刚好是m,就强制拆出来一个点for(int j = 0;j < cycles[x].size();++j){add_node_cyc(nw,x,j);}return;}// 第一个环不够大// 先把第一个环全都加进去for(int j = 0;nw < m && j < cycles[x].size();++j){add_node_cyc(nw,x,j);}// 然后把偶环加进去for(int i = 0;nw < m && i < cyc_even.size();++i){int x = cyc_even[i];for(int j = 0;nw < m && j < cycles[x].size();++j){add_node_cyc(nw,x,j);}}// 最后再加入其余的奇环for(int i = 1;nw < m && i < cyc_odd.size();++i){int x = cyc_odd[i];for(int j = 0;nw < m && j < cycles[x].size();++j){add_node_cyc(nw,x,j);}}}else{ // 偶环int x = cyc_list[0];int nw = 0;vector<int> sz;sz.resize(m);memset(sz.data(),-1,sizeof(int) * m);for(int i = 0;i < cycles.size();++i) if(cycles[i].size() < m){int nwsz = cycles[i].size();if(sz[m - nwsz] != -1){ // 这两个环的大小之和刚好是m,合并之int y = sz[m - nwsz];gen_merge2(i,y);return;}sz[nwsz] = i;}if(cycles[x].size() == m - 1){ // 第一个环是m-1,特殊情况int y = cyc_list[1];if(cycles[y].size() == m - 1){ // 第二个环也是m-1,那么把他俩合并gen_merge2(x,y);return;}int tot_nxt = 0;for(int j = 1;j < cyc_list.size();++j) tot_nxt += cycles[cyc_list[j]].size();if(tot_nxt >= m){int z = cyc_list[2];if(cycles[y].size() + cycles[z].size() == m + 1){ // 这种情况让z和x合并gen_merge2(x,y);}else{// 把剩下的都合并到y上for(int j = 1;nw < m && j < cyc_list.size();++j){int w = cyc_list[j];for(int k = 0;k < cycles[w].size() && nw < m;++k){add_node_cyc(nw,w,k);}}}return;}// tot_nxt<m的情况不归这里考虑,回归到普通情形 }if(cycles[x].size() < m){ // 第一个环不够大// 从大到小遍历所有的环,把点顺次加进操作序列for(int i = 0;nw < m && i < cyc_list.size();++i){int x = cyc_list[i];for(int j = 0;nw < m && j < cycles[x].size();++j){add_node_cyc(nw,x,j);}}return;}if(cycles[x].size() == m + 1){ // 第一个环刚好是m+1,消除不了,要融合进去下一个环int y = cyc_list[1];gen_merge2(x,y);return;}if(cycles[x].size() >= 3 * m / 2){ // 第一个环特别大,允许拆出一个mget_m(x);return;}if(cycles[x].size() != m + 2 && cycles[x].size() != m + 4){ // 精细构造拆出一个3// update:每次拆下来两个点会有问题,因为每次干掉一个点之后剩下的是m-1,就意味着还需要合并两个2才行// 所以这里尽可能用了每次拆下来3个点的操作get_3(x);return;}// 第一个环足够大而且不是m+3,可以每次拆下来两个点get_2(x);}
}
int Alice(int _m, int _e, std::vector<int> _u, std::vector<int> _v, int _n, std::vector<int> _p){m = _m, e = _e, n = _n;u = _u, v = _v, p = _p;vis.resize(n); memset(vis.data(),0,sizeof(int) * n);cyc_id.resize(n); memset(cyc_id.data(),0,sizeof(int) * n);cyc_size.resize(n); memset(cyc_size.data(),0,sizeof(int) * n);cyc_pos.resize(n); memset(cyc_pos.data(),0,sizeof(int) * n);int final_ans = get_ans();cnt = 0;while(nwas < final_ans){++cnt;run();int res = Bob(oper);swap(p[oper[u[res]]], p[oper[v[res]]]);get_cycle(); // 重新生成环}return final_ans;
}
相关文章:
[luogu12542] [APIO2025] 排列游戏 - 交互 - 博弈 - 分类讨论 - 构造
传送门:https://www.luogu.com.cn/problem/P12542 题目大意:给定一个长为 n n n 的排列和一张 m m m 个点 e e e 条边的简单连通图。每次你可以在图上每个点设置一个 0 ∼ n − 1 0\sim n-1 0∼n−1、两两不同的权值发给交互库,交互库会…...
图像处理基础知识
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 信息是自然界物质运动总体的一个重要方面,人们认识世界和改造世界就是要获得各种各样的图像信息,这些信息是人类获得外界信息的主要来源。大约有70%的信息是通过人眼获得的。近代科学研…...
使用MybatisPlus实现sql日志打印优化
背景: 在排查无忧行后台服务日志时,一个请求可能会包含多个执行的sql,经常会遇到SQL语句与对应参数不连续显示,或者参数较多需要逐个匹配的情况。这种情况下,如果需要还原完整SQL语句就会比较耗时。因此,我…...
HarmonyOS5云服务技术分享--ArkTS开发Node环境
✨ 你好呀,开发者小伙伴们!今天我们来聊聊如何在HarmonyOS(ArkTS API 9及以上)中玩转云函数,特别是结合Node.js和HTTP触发器的开发技巧。文章会手把手带你从零开始,用最接地气的方式探索这个功能࿰…...
水利数据采集MCU水资源的智能守护者
水利数据采集仪MCU,堪称水资源的智能守护者,其重要性不言而喻。在水利工程建设和水资源管理领域,MCU数据采集仪扮演着不可或缺的角色。它通过高精度的传感器和先进的微控制器技术,实时监测和采集水流量、水位、水质等关键数据&…...
深度学习之用CelebA_Spoof数据集搭建一个活体检测-用MNN来推理时候如何利用Conan对软件包进行管理
我为什么用Conan 前面的文章:深度学习之用CelebA_Spoof数据集搭建一个活体检测-训练好的模型用MNN来推理有提到怎么使用MNN对训练好的模型进行推理,里面并没有提到我是怎么编译和进行代码依赖包的管理的详细步骤,在这里我是用的是Conan:一个…...
深入解剖 G1 收集器的分区模型与调优策略
JVM 垃圾收集系列之三 | 高并发低延迟系统的首选 GC 解法! 一、为什么我们需要 G1 垃圾收集器? 在传统 GC(如 CMS)中,我们常常面临的问题是: GC 停顿不可预测(Stop-The-World)内存…...
兰亭妙微・UI/UX 设计・全链路开发
【遇见专业设计,共筑卓越产品】 在数字化浪潮中,界面是产品与用户对话的第一窗口。 兰亭妙微(蓝蓝设计),自 2008 年深耕 UI/UX 领域,以清华团队为核心,16 年专注软件与互联网产品的界面设计开…...
Babylon.js学习之路《六、材质与纹理:为模型赋予真实的表面效果》
文章目录 1. 引言:材质与纹理的重要性1.1 材质与纹理的核心作用 2. 基础材质:StandardMaterial2.1 材质属性详解2.2 实战:创建金属材质 3. 纹理贴图:从基础到高级3.1 基础纹理映射3.2 多纹理混合技术 4. 高级材质:PBRM…...
飞致云旗下开源项目GitHub Star总数突破150,000个
2025年5月19日,中国领先的开源软件提供商飞致云宣布,其旗下开源项目在代码托管平台GitHub上所获得的Star总数已经超过150,000个。基于在开源领域的长期耕耘和探索,飞致云的开源势能不断增强,获得第一个五万GitHub Star用时89个月&…...
萌新联赛第(三)场
C题 这道题用暴力去写想都不要想,一定超时,于是我们需要优化,下面是思路过程: 如图,本题只需找到x的因数个数和(n-x)的因数个数,这两个相乘,得到的就是对于这个x来说组合的个数,且x…...
cplex12.9 安装教程以及下载
cplex 感觉不是很好找,尤其是教育版,我这里提供一个版本,在下面的图可以看到,不仅可以配置matlab,也可以配置vs,现在拿vs2017来测试一下,具体文件的文件有需要的可以复制下面的链接获取 我用网盘分享了「c…...
Pycharm-jupyternotebook不渲染
解决方案: https://youtrack.jetbrains.com/issue/PY-54244 import plotly.io as pio pio.renderers.default "vscode"...
layui 介绍
layui(谐音:类 UI) 是一套开源的 Web UI 解决方案,采用自身经典的模块化规范,并遵循原生 HTML/CSS/JS 的开发方式,极易上手,拿来即用。其风格简约轻盈,而组件优雅丰盈,从源代码到使用…...
大数据相关操作
大数据相关操作 一、环境配置 1、修改主机名 #修改主机名 hostnamectl set-hostname master2、固定IP地址 # 进入修改 sudo vim /etc/netplan/01-network-manager-all.yaml# 修改配置文件 # Let NetworkManager manage all devices on this system network:version: 2rend…...
谷歌宣布推出 Android 的新安全功能,以防止诈骗和盗窃
在上周二的 Android Show 上,也就是Google I/O 开发者大会之前,谷歌宣布了 Android 的全新安全和隐私功能。这些新功能包括对通话、屏幕共享、消息、设备访问和系统级权限的全新保护。谷歌希望通过这些功能保护用户免遭诈骗,在设备被盗或被攻…...
WSL虚拟机整体迁移教程(如何将WSL从C盘迁移到其他盘)
文章目录 WSL虚拟机迁移教程一、查看当前主机的子系统二、导出 WSL 子系统三、将打包好的文件发送给另一个人四、在另一台机器导入并恢复子系统五、附加命令六、注意事项和导出文件信息6.1 注意事项6.2 导出文件信息使用 wsl --export 命令导出整个 WSL 子系统时,它…...
汽车区域电子电气架构(Zonal E/E)的统一
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界…...
开源一个记账软件,支持docker一键部署
欢迎来到我的博客,代码的世界里,每一行都是一个故事 🎏:你只管努力,剩下的交给时间 🏠 :小破站 开源一个记账软件,支持docker一键部署 项目简介功能特性技术栈快速开始环境要求运行步…...
新能源汽车焊接智能节气阀
在新能源汽车产业迅猛发展的浪潮中,制造工艺的优劣直接关系到车辆的性能、安全与市场竞争力。焊接,作为新能源汽车生产流程里的关键一环,无论是构建车身框架,还是连接电池模组,其质量的好坏都起着决定性作用。而在焊接…...
React 第四十四节Router中 usefetcher的使用详解及注意事项
前言 useFetcher 是 React Router 中一个强大的钩子,用于在不触发页面导航的情况下执行数据加载(GET)或提交(POST)。 一、useFetcher 应用场景: 1、后台数据预加载(如鼠标悬停时加载数据&…...
33、魔法防御术——React 19 安全攻防实战
一、奥术护盾(基础防御) 1. 敏感数据加密术 // cryptoUtils.js - 数据加密工具export const encrypt (data) > {// 实际项目应使用Web Crypto API或crypto-jsreturn btoa(encodeURIComponent(data));};export const decrypt (data) > {try {…...
NVM 安装与配置指南
简介 Node Version Manager(NVM)是一个常用的 Node.js 版本管理工具,可用于在开发过程中方便地切换不同版本的 Node.js。通过 NVM,用户可以根据项目需求选择不同的 Node.js 版本,而无需手动安装和卸载多个版本的 Node…...
SpringMVC04所有注解按照使用位置划分| 按照使用层级划分(业务层、视图层、控制层)
目录 一、所有注解按照使用位置划分(类、方法、参数) 1. 类级别注解 2. 方法级别注解 3. 参数级别注解 4. 字段/返回值注解 二、按照使用层级划分(业务层、视图层、控制层) 1、控制层(Controller Layer&#x…...
【数据库】-1 mysql 的安装
文章目录 1、mysql数据库1.1 mysql数据库的简要介绍 2、mysql数据库的安装2.1 centos安装2.2 ubuntu安装 1、mysql数据库 1.1 mysql数据库的简要介绍 MySQL是一种开源的关系型数据库管理系统(RDBMS),由瑞典MySQL AB公司开发,目前…...
MySQL与Redis一致性问题分析
一、一致性问题概述 1.1 什么是一致性问题? 在数据库-缓存架构中,当MySQL中的数据(最新值)与Redis缓存中的数据(缓存旧值)出现差异时,由于程序总是优先读取Redis缓存,就会导致应用…...
Xshell传输文件
新建文件 点击新建 完善主机地址 然后输入我们的远端服务器的SSH协议 一般的是这样的ssh -p 44562 rootregion-1.autodl.com 由于Xshell比较特殊我们输入ssh rootregion-1.autodl.com 44562这样的形式 然后输入服务器的密码即可...
怎样用 esProc 为大主子表关联提速
类似订单和明细表这样的主子表关联比较常见,在 SQL 中,这种关联用 JOIN 实现,在两个表都很大的情况下,常常出现计算速度非常慢的现象。 如果预先将主子表都按照主键有序存储,就可以使用归并算法实现关联。这种算法只需…...
打卡day31
文件的规范拆分和写法 知识点回顾 规范的文件命名规范的文件夹管理机器学习项目的拆分编码格式和类型注解 作业:尝试针对之前的心脏病项目,准备拆分的项目文件,思考下哪些部分可以未来复用。 导入依赖库 # 忽视警告 import warnings warn…...
编译原理的部分概念
解释程序:边解释边执行:不断读取源程序的语句,解释语句,读取此语句需要的数据,根据执行结果读取下一条语句,继续解释执行,直到返回结果。 编译程序:将源程序完整地转换成机器语言程…...
Java中字符串(String类)的常用方法
以下是Java中字符串(String类)的常用方法分类详解,包含核心方法说明和示例代码: 一、字符串基础信息 方法说明示例输出length()返回字符串长度"Hello".length()5isEmpty()判断字符串是否为空(长度是否为0&a…...
什么是 ERP,中国企业如何科学应用 ERP
中国企业在引入 ERP 系统过程中,常因盲目跟风大型企业选型、忽视自身业务适配性,导致系统功能过剩、实施成本高企、员工接受度低等问题,最终造成项目成功率不足 10%。因此,理性认知 ERP 的价值定位与本土化实施路径,成…...
使用SQLite Expert个人版VACUUM功能修复数据库
使用SQLite Expert个人版VACUUM功能修复数据库 一、SQLite Expert工具简介 SQLite Expert 是一款功能强大的SQLite数据库管理工具,分为免费的个人版(Personal Edition)和收费的专业版(Professional Edition)。其核心功…...
同源策略深度防御指南:CSP 高级应用与企业微信全场景适配(含 report-uri 实战)
一、CSP 核心指令权威解析与企业微信适配 内容安全策略(CSP)通过Content-Security-Policy响应头实现资源加载的细粒度控制,其核心指令与企业微信场景强相关: 1.1 frame-ancestors:iframe 嵌入源控制 权威规范&#…...
【AGI】大模型微调技术-四大微调框架
【AGI】大模型微调技术-四大微调框架 (1)微调基础概念介绍1.1 微调基本概念1.2 全量微调与高效微调1.3 模型微调的优劣势分析1.4 高效微调与LoRA、QLoRA (2)高效微调的应用场景(3)流微调工具介绍3.1 unslot…...
小白编程学习之巧解「消失的数字」
一、引言:一个看似简单的「找不同」问题 今天遇到一道有趣的算法题:给定一个含 n 个整数的数组 nums,其中每个元素都在 [1, n] 范围内,要求找出所有在 [1, n] 中但未出现在数组中的数字。 这让我想起小时候玩的「找错题」游戏 —…...
在 Git 中添加子模块(submodule)的详细步骤
在 Git 中添加子模块(submodule)的详细步骤如下: 1. 添加子模块 命令格式: git submodule add <仓库URL> [目标路径]仓库URL:子模块的 Git 仓库地址(HTTP/SSH 均可)。目标路径ÿ…...
瑞萨单片机笔记
1.CS for CC map文件中显示变量地址 Link Option->List->Output Symbol information 2.FDL库函数 pfdl_status_t R_FDL_Write(pfdl_u16 index, __near pfdl_u08* buffer, pfdl_u16 bytecount) pfdl_status_t R_FDL_Read(pfdl_u16 index, __near pfdl_u08* buffer, pfdl_…...
单片机复用功能重映射Remap功能
目录 一、查看“DS5319 stm32f10x中等密度mcu数据手册(英文)”手册 二、查看“RM0008 STM32F10xxx参考手册(中文)”手册 三、重映射(Remap)功能程序编写 自己学习过程中容易遗忘的知识点,记录…...
小白入门FPGA设计,如何快速学习?
很多刚入门的小伙伴,初次听说FPGA(现场可编程门阵列),脑子里只有一个字:玄! 什么“时序逻辑”“Verilog”“Vivado”,仿佛一夜之间掉进了电子黑魔法的深坑。 但真相是—— FPGA,其实…...
友思特应用 | LCD显示屏等玻璃行业的OCT检测应用
导读 光学相干层析成像(OCT)是一种非侵入式光学成像方法,提供微米尺度的空间分辨率,能够生成内部结构截面图像。自20世纪90年代初发明第一台OCT以来,它在眼科领域得到了广泛应用,并成为临床诊断的黄金标准之一。除了在生物医学领…...
Python的sys模块:系统交互的关键纽带
Python的sys模块:系统交互的关键纽带 对话实录 小白:(挠头)我知道 Python 能做很多事,可怎么让它和计算机系统‘交流’呢,比如获取系统信息、处理命令行参数? 专家:(微…...
若依项目集成sentinel、seata和shardingSphere
集成组件包括MySQL分库分表及读写分离、seata以及Sentinel 若依项目文档连接 代码下载地址 需要结合ruoyi代码配合看,前提是熟悉基本代码结构,熟悉feign调用和基础网关配置等。 采用的版本信息 <java.version>1.8</java.version> <spr…...
张 推进对话式心理治疗:SOULSPEAK的聊天机器人
SOULSPEAK的聊天机器人 利用大语言模型(LLM)来提供低成本的心理治疗服务,旨在解决传统心理咨询在隐私、成本和可及性方面的不足。以下是核心内容的通俗解读: 1. 研究背景:传统心理治疗的困境 问题:全球心理健康问题日益严重(如焦虑、抑郁人数激增),但传统心理咨询受…...
java中的Filter使用详解
Filter(过滤器)是 Java Web 开发的核心组件之一,用于在请求到达 Servlet 或响应返回客户端之前进行拦截和处理。以下是其核心功能、使用方法和实际场景的详细解析: 一、Filter 的作用与原理 核心作用 Filter 充当请求与响应之间的…...
BERT 作为Transformer的Encoder 为什么采用可学习的位置编码
摘要 BERT 在位置编码上与原始 Transformer 论文中的 sin/cos 公式不同,选择了可学习(learned)的位置嵌入方案。本文将从 Transformer 原始位置编码选项入手,分析 BERT 选择 learned positional embeddings 的四大核心原因&#x…...
Vue百日学习计划Day43-45天详细计划-Gemini版
Day 43: Composable 函数基础与抽取简单逻辑 (~3 小时) 本日目标: 理解 Composable 函数的概念、优势,并学会如何将简单的、无状态的逻辑抽取为 Composable。所需资源: Vue 3 官方文档 (组合式函数): https://cn.vuejs.org/guide/reusability/composables.html 学…...
Kotlin 协程 (二)
Kotlin 协程提供了丰富的功能,能够高效地处理并发和异步任务。以下是对 Kotlin 协程中常见概念和功能的详细讲解,包括它们的定义、作用、使用场景以及最佳实践。 1. 协程核心概念 1.1 CoroutineScope 定义:CoroutineScope 是协程作用域的抽…...
Linux 下 rsync 工具详解与实用指南
Linux 下 rsync 工具详解与实用指南 一、什么是 rsync? rsync(remote sync)是 Linux/Unix 系统下常用的数据同步和备份工具。它可以高效地在本地与远程主机之间同步文件和目录,支持增量同步、断点续传、权限保留等功能ÿ…...
2025年医美行业报告60+份汇总解读 | 附 PDF 下载
原文链接:https://tecdat.cn/?p42122 医美行业在消费升级与技术迭代的双重驱动下,已从边缘市场逐步走向主流。数据显示,2024 年中国医美市场规模突破 3000 亿元,年复合增长率达 15%,但行业仍面临正品率不足、区域发展…...