专栏文章
题解:AT_abc390_d [ABC390D] Stone XOR
AT_abc390_d题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miqd43mx
- 此快照首次捕获于
- 2025/12/04 02:50 3 个月前
- 此快照最后确认于
- 2025/12/04 02:50 3 个月前
题目传送门
拿到一道题,首先思考:
题目是说有 个袋子,每个袋子里有若干石头。我们可以进行任意次数的操作,每次选两个袋子 和 ,把 的所有石头移动到 里。最后,每个袋子的石头数 的异或和有多少种不同的可能值。输出这个数量。
首先,我得理解操作的结果。每次操作可以合并两个袋子,比如把 的石头全给 。经过多次操作后,最终的袋子分布其实是把原来的袋子分成了若干个组,每个组的石头总和被分配到某个袋子中。比如,假设最后剩下 个袋子,那么每个袋子里的石头总和是原来某些袋子的总和。比如,最初的每个袋子 的石头总和会被合并到某个最终的袋子里。
那这个时候,问题可以转化为,如何将所有袋子分成若干个非空的不相交的组,每个组的和作为该组的贡献,然后将这些组的数值异或起来。不同的分组方式会得到不同的异或和。我们需要找出所有可能的分组方式对应的异或值的不同数量。
那问题就转化为,枚举所有可能的分组方式,计算它们的异或和,然后统计不同的值的数量。
但怎么枚举所有可能的分组呢?比如,当 的时候,分组可以是三个单独的,或者两个合并成一个,或者全部合并成一个。不同的分组方式对应不同的异或结果。
那这个时候,每个分组方式可以看作是将原数组 划分成一个集合的子集,每个子集至少有一个元素,且所有子集的并集是全集。每个子集的和作为异或的一个元素。然后,这些子集的和异或的结果就是最终的值。
但是,这样的划分方式可能有多种,比如不同的划分结构。比如,当 时,可能的划分结构包括:三个单独的分组,两个分组(比如袋子 和 合并,袋子 单独),或者一个分组。不同的结构对应不同的异或结果。
那这个时候,问题就转化为,如何枚举所有可能的子集划分,然后计算每个划分对应的异或总和,最后统计不同结果的数量。
但这样的枚举方式在 的时候会不会很困难呢?因为 是 ,可能的划分数是贝尔数,贝尔数的第十二项是 ,这在千万级别。那对于每个划分,我们需要计算异或总和,然后将这些总和存入集合中,最后统计数量。这似乎对于 来说,贝尔数可能超过 的话,可能会有问题,比如时间不够或者内存不足。但是看样例中的第三个测试样例输出是 ,可能实际可行的分组方式并没有那么多?
或者,可能题目中的某些性质可以减少计算量。比如,异或的性质可能允许某些不同的分组方式得到相同的异或结果,因此不需要枚举所有可能的划分,而是可以找到一种更高效的方式。
那回到代码(见下)。它用了深度优先搜索的方式,对于每个袋子,选择它属于哪个组。比如, 数组记录每个袋子被分配到哪个组。例如, 表示第 个袋子被分配到第 个组。这里的组是按顺序编号的,比如第一个袋子必须分到组 ,第二个袋子可以分到组 或者组 ,第三个袋子可以分到已有的组或者新组,等等。这样,不同的 数组的排列对应不同的分组方式,但可能会有重复的情况。比如,组编号的排列顺序不影响分组的实际结构,比如组 和组 的顺序调换,但分组方式相同的话,它们的异或结果是一样的。所以,这样的枚举方式可能会导致重复计算吗?
比如,假设两个分组方式的结构是相同的,只是组的编号不同,那么它们的异或结果可能相同。例如,假设袋子 和袋子 被分到组 ,袋子 被分到组 ,或者袋子 分到组 ,袋子 分到组 ,袋子 分到组 。这种情况下,它们的异或和会是相同的,因为异或的顺序不影响结果。因此,这样的不同 数组对应的分组方式会被视为不同的情况,但实际它们的异或结果相同。那么,在代码中,如何处理这种情况?
看代码中的 函数, 数组存储每个袋子被分配到的组号,比如,对于 的情况,可能的 数组可能是 ,或者 ,这会被视为不同的分组方式吗?是的。但是,在计算异或的时候,组的顺序不影响异或的结果,因为异或是交换的。所以,这两种不同的 数组对应的异或和是一样的。那么,这样就会导致在 数组中存入重复的值,最终通过排序去重来得到正确的数量。
所以,这个算法的思路是枚举所有可能的组分配方式,不管组的顺序如何,然后将它们的异或和存入数组,最后去重统计数量。因为不同的组分配方式可能生成相同的异或结果,所以通过 处理后的结果就是正确的答案数量。
那这个算法的时间复杂度如何?比如,对于每个袋子 ,选择它所在的组的编号。而组的编号不能超过当前的最大值加一。例如,第一个袋子必须分到组 。第二个袋子可以分到组 或组 。第三个袋子可以分到组 、组 或组 ,或者如果前面的组的最大是 ,那么可以分到现有的或者新增的组 。这样,这种枚举方式实际上生成了所有可能的非空划分,其中组的顺序被考虑,但异或的顺序不影响结果。
所以,这样的枚举方式会产生很多重复的分组结构,即不同组的顺序会被视为不同的情况,但它们的异或结果可能相同。所以,这样的枚举方式虽然会生成很多重复的异或结果,但最终通过 去重,得到正确的答案。
例如,对于 的情况,不同的分组方式的数量等于贝尔数,也就是 种可能的分组方式。但通过这种枚举方式,可能生成的 数组的数量比贝尔数大。例如,贝尔数是 ,而该算法枚举的可能更多?
比如, 时,分组方式的数量是 种。但是根据这种枚举方式,每个分组方式对应的组编号可能不同。例如,对于分组方式 ,对应的组编号可以是 、、,或者其他排列吗?
比如,假设第一个袋子分配到组 ,第二个袋子可以选择组 或组 。假设选择组 ,第三个袋子可以选择组 、组 或组 。如果选择组 ,那么分组是三个单独的。或者,对于第三个袋子,如果第二个袋子分配到组 ,第三个袋子可以选择组 的话,那么分组是 ,。或者,如果第三个袋子分配到组 ,那么分组是 ,。所以,这种方式枚举的是所有可能的组分配,其中组的编号是按顺序分配的。因此,这种方式确实枚举了所有可能的分组结构,不管组的顺序如何。例如,对于分组结构是合并袋子 和袋子 的情况,对应的组编号可能是组 (如果第三个袋子分配到组 ),或者组 (如果第三个袋子分配到新的组 ),但异或的结果是相同的,因为组的顺序不影响异或的结果。因此,不同的 数组可能生成相同的异或和,但通过最终的 处理,得到正确的结果。
因此,这个算法的正确性基于:枚举所有可能的分组方式的异或和,然后去重得到不同的值。虽然枚举的方式可能产生不同的 数组,但它们对应的异或和可能相同,但最终会被 处理掉,所以结果正确。
那这个算法的时间复杂度如何?例如,当 时, 的可能情况有多少种?这应该等于递推的贝尔数吗?或者可能更高?
这里,每个袋子 的选择是:可以分配到前面的任何一个已经存在的组(组的编号不超过当前的最大值),或者创建一个新的组(当前最大值 )。例如,对于第 个袋子,它可选的组的数量等于当前的最大组数 。而每次选择的时候, 是当前的最大组号。例如,当处理到第 个袋子时,假设前 个袋子的组分配的最大组号是 ,那么第 个袋子可以选择 到 中的任意一个。
这种递归的方式生成的 数组的数量等于增长数(或者叫做 数,或者排列的贝尔数)。例如,对于 ,这等于 种可能的分配方式。例如,这个数比贝尔数大,因为贝尔数是 种分组方式,而这里可能生成的分配方式数目更大,因为不同的组的顺序被视为不同的情况。例如,在分组结构相同的情况下,不同的组顺序会被视为不同的分配方式,从而导致不同的 数组,但异或结果相同。
例如, 时,该算法枚举的分组方式数目等于 。而贝尔数是 。这说明该算法生成的 数组数目大于等于贝尔数,但每个分组结构对应多个 数组,所以最终生成的异或结果数目可能等于正确的数目,因为不同的结构可能得到不同的结果,但同一结构的不同排列方式可能得到相同的结果,从而被去重处理。
所以,当 时,这个算法的复杂度可能很高。比如, 数是随着 的增大而指数级增长的。比如,当 时, 数是 ?或者可能更高?比如,根据维基百科, 数(有序贝尔数)的第 项是 ?或者可能更高?例如,已知 数的前几项是 。当 时,这个数可能非常大,比如超过 ?如果是这样的话,这样的算法在 的时候可能会超时或者超内存。
例如,样例输入 的输出是 ,这说明当 时,可能的异或结果数目是 。这说明,虽然可能的分组方式数目很大,但不同的分组方式可能生成相同的异或结果,所以最终数目可能较低。
但是,根据代码中的 数组的大小是 。而 变量在 的时候可能会超过这个值,导致数组越界或者错误?
比如,在代码中, 是 。如果当 时, 数是约 ,显然这会超过 的大小,导致数组溢出。这说明,这个代码可能在较大的测试用例中无法通过,比如 时会导致 数组溢出,或者导致运行时间过长。
但是,观察样例输入 的输出是 。这说明,当两个袋子的时候,可能的异或结果数目是 。而根据分组方式,可能只有两种可能:两个袋子各自单独,异或和为 ;或者两个袋子合并,异或和为 。所以,当两个袋子时,可能的异或结果数目是 。这说明,该代码在 时是正确的。
这说明,该代码可能在测试用例的约束下是正确的,但可能对于 的情况无法处理。然而,在题目给出的约束中,,且代码的 数组的大小是 ,那么这可能意味着在某些情况下,例如 时,代码无法处理,但可能实际运行通过了?或者可能存在其他优化?
或者,可能问题的实际可行解数目较小,所以虽然分组方式数目很大,但不同的方式可能生成相同的异或结果,导致 数组中的实际数目不超过 的限制。例如,当 时,可能不同的分组方式的异或结果数目在 以内,所以不会溢出。
或者,可能该代码在 时,实际上生成的 数组的大小不会超过 的限制。比如,当 时, 数是 ,这大约是四百万,而每个分组方式的异或结果可能有很多重复,所以 数组的数目可能远小于这个数值。例如,当每个分组方式的异或结果都是唯一的,那么当 时, 数组需要存储四百万个数,而 是 ,足够容纳。这样,代码是正确的。
那现在,我们需要理解这个代码的具体实现。代码中的 函数是递归的,每次处理一个袋子,选择其所属的组号。 数组保存每个袋子所属的组号。例如, 的值是袋子 被分到的组的编号。当递归到第 个袋子时, 是目前最大的组号。
例如,当处理到第 个袋子时,可以选择将其分配到现有的任何一个组( 到 ),或者创建一个新的组()。然后递归下去。这样,所有可能的组分配方式都被枚举,其中组的顺序是无关的,但不同的分配方式可能生成相同的分组结构,只是组的顺序不同。
当处理到 个袋子后, 数组中的所有元素都被确定。此时,代码中的 数组保存每个组的石头总和。例如, 数组的下标是组号,对应的值是所有分配到该组的袋子的石头总和。然后,将这些组的异或和计算出来,存入 数组中。
例如,假设袋子 和袋子 被分配到组 ,袋子 被分配到组 ,那么 ,。异或和是 。而如果袋子 和袋子 被分配到组 ,袋子 被分配到组 ,那么 ,,异或和还是相同的。所以,这样的不同分配方式会被视为不同的情况,但它们的异或和相同,因此在 数组中会被存入相同的数值,最后通过 处理得到正确的数目。
因此,这个算法的正确性是正确的,因为它枚举了所有可能的组分配方式,然后计算异或和,并通过去重得到数目。但问题是,当 时,这样的枚举是否可行?
比如,对于 ,每个袋子有多个选择,每个选择生成不同的路径。但这样的递归深度是 层,对于每个袋子来说,选择的组数目可能很大。例如,第一个袋子只能选组 。第二个袋子可以选组 或组 。第三个袋子可以选组 、组 或组 ,依此类推。假设每个袋子最多有 组的选择,那么总的时间复杂度可能是 ,但实际上可能更高,比如 ,这在 时是很大的数值。例如, 时,这样的枚举次数可能高达 ,或者更高。这显然会导致超时。
那为什么这个代码能通过呢?比如,可能题目中的测试用例没有 的情况,或者该算法的时间复杂度实际上在 时是可行的?
或者,可能这个问题的正确解法并不是通过枚举所有分组方式,而是利用了异或的某些性质,从而可以快速计算可能的结果数目。比如,将问题转化为线性基的问题,或者通过动态规划的方式。
但是,当前的代码显然是通过枚举所有分组方式,并统计异或结果的数目。这可能适用于 较小的场合,比如 时,可能实际的运行时间在可接受的范围内?
或者,可能该代码的时间复杂度在 时是可行的,因为每组选择的方式数可能被优化。例如,当 时,每个袋子 的选择可能最多有 种组的可能性,那么总的选择次数是 ,这大约是 次,显然超过时间限制。但代码中的 是 ,而 数组的大小是 ,可能无法存储这么大的数目。这显然存在矛盾。
那这代码是否真的能处理 的情况?
可能,这个问题的正确解法是基于分组方式的异或和的性质,允许通过某种数学方式快速计算可能的结果数目,而不是枚举所有可能的分组方式。例如,每个分组的异或和可以看作是某些线性基的组合,从而可以用动态规划来跟踪可能的异或结果。
但代码中的做法显然不是这样,而是直接枚举所有分组方式,这在 时可能不可行。所以,这说明该代码可能仅在 较小的情况下通过,而无法处理 的情况,但题目中的测试用例可能没有达到 的极限情况?
或者,可能这个问题的正确解法确实是枚举所有分组方式,但通过某种剪枝或优化,使得在 时也能快速处理?
例如,可能,虽然分组方式的数量是贝尔数的量级,但当 时,贝尔数大约是 ,这可能在时间允许的范围内?比如,贝尔数 是 ,所以当 时,代码中的 函数会被调用约 次。每次调用会处理到 层,当 时,总的操作次数是大约 次,这可以处理的。例如,每个递归调用的操作是 时间吗?比如,在递归到 层时,循环次数取决于 的当前值。例如,对于每个袋子 ,循环次数是 ,其中 是之前的组数。这可能平均下来每个递归调用的循环次数是较小的。例如,当 时,每个递归调用的循环次数可能平均为 次。这样,总的操作次数可能在合理的时间范围内。
或者,可能这个代码的时间复杂度是 ,其中 是贝尔数。当 时, ,那么总操作次数大约是 次,这可能可以通过。例如,每个递归调用的处理时间非常短,没有复杂的运算,因此总的时间可能在 秒内。
因此,这个代码的思路是可行的,并且能够处理 的情况,因为贝尔数的数量级是百万级别,而不是十亿级别。所以,这样的枚举方式在题目给出的约束下是可行的。
综上所述,该代码的正确性在于枚举所有可能的组分配方式,计算每个分组方式的异或和,并通过去重统计数目。虽然不同的组分配方式可能生成相同的异或和,但通过最后的排序和去重处理,得到正确的结果。该算法的时间复杂度在 时是可行的,因为贝尔数的增长在可接受的范围内。
根据问题描述,我们需要确定在进行任意次数的石子转移操作后,所有可能的不同异或和的数量。每个操作允许我们将一个袋子的所有石子转移到另一个袋子。
方法思路
问题分析:
每次操作将两个袋子的石子合并,最终剩下的每个袋子中的石子数是若干原始袋子石子数的总和。
不同分组的石子总和异或后得到的可能值的数量即为所求。
关键观察:
不同的分组方式会产生不同的异或和。我们需要枚举所有可能的分组方式。
每个袋子可以被分配到任意一个组,组的顺序不影响异或结果,因此需要去重处理。
算法选择:
使用深度优先搜索()枚举所有可能的分组方式。
对每个分组方式计算异或和,最后通过排序和去重统计不同结果的数量。
代码
CPP#include <bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2.6e7;
int n,cnt,a[13],b[13],opt[13],ans[MAXN];
void dfs(int tmp,int maxn){
if(tmp==n){
memset(b,0,sizeof(b));
for(int i=1;i<=n;i++)b[opt[i]]+=a[i];
int res=0;
for(int i=1;i<=n;i++)res^=b[i];
ans[++cnt]=res;
return;
}
for(int i=1;i<=maxn+1;i++){
opt[tmp+1]=i;
dfs(tmp+1,max(i,maxn));
}
}
signed main(){
cin >> n;
for(int i=1;i<=n;i++)cin >> a[i];
opt[1]=1;
dfs(1,1);
sort(ans+1,ans+cnt+1);
cnt=unique(ans+1,ans+cnt+1)-ans-1;
cout << cnt;
return 0;
}
补充解释
- 变量定义:
-
设置为足够大的值以存储可能的异或结果。
-
数组存储输入的每个袋子的石子数。
-
数组记录每个袋子被分配到的组号。
-
数组存储所有可能的异或结果。
- 函数:
-
表示当前处理的袋子索引, 表示当前最大的组号。
-
递归枚举每个袋子可能被分配到的组号(现有组或新组)。
-
当处理完所有袋子后,计算当前分组的异或和并存入结果数组。
- 主函数:
-
读取输入并初始化第一个袋子的组号。
-
调用 枚举所有分组方式。
-
对结果数组排序并去重,最后输出不同结果的数量。
该方法通过 枚举所有可能的分组方式,并利用异或的性质和去重操作高效地统计不同异或值的数量。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...