0%

思路讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
// 矩阵快速幂:必须是方阵
template <class T>
Matrix<T> mat_pow(Matrix<T> base, long long exp) {
assert(base.n == base.m);
Matrix<T> res = Matrix<T>::identity(base.n);
while (exp > 0) {
if (exp & 1) res = res * base;
base = base * base;
exp >>= 1;
}
return res;
}

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

这是问题的简单版本。版本之间唯一的区别在于nnmm的上限。在这个版本中,n500n \le 500m500m \le 500

你有一群nn只圣诞驯鹿。第ii只驯鹿的力量是2ci2^{c_i}

kk只圣诞驯鹿组的运载能力计算如下:

  • 驯鹿的力量按非递增顺序排序。我们把排序后的力量列表表示为c1,c2,,ckc'_1, c'_2, \dots, c'_k,其中cici+1c'_i\ge c'_{i+1}

  • 然后,这群驯鹿的运载能力等于c1+c22+c34++ck2k1c'_1 + \lfloor\frac{c'_2} {2}\rfloor + \lfloor\frac{c'_3} {4}\rfloor + \dots + \lfloor\frac{c'_k} {2^{k - 1}}\rfloor

注意,一些驯鹿可能对群体的运载能力没有贡献。

你需要处理三种类型的查询:

  1. 添加一只力量为2x2^x的驯鹿到群中;

  2. 从驯鹿群中移除一只力量为2x2^x的驯鹿;

  3. 计算选择一些驯鹿(可能是所有)的方式数量,使得所选群体的运载能力至少为xx

如果群中有多只力量相同的驯鹿,它们被视为不同的驯鹿。例如,如果你有两只力量各为11的驯鹿,并且需要计算选择运载能力至少为11的群体的方式数量,那么有33种选择方式:选择第一只驯鹿,选择第二只驯鹿,或者选择它们两只。


思路讲解

这个全部都和 2 的次方有关系,我们不自觉的就会往二进制的方向想,但是问题在于,我们如何处理这个进位问题?

等等,有没有一种可能,我们不需要处理进位问题c1+c22+c34++ck2k1c'_1 + \lfloor\frac{c'_2}{2}\rfloor + \lfloor\frac{c'_3}{4}\rfloor + \dots + \lfloor\frac{c'_k}{2^{k - 1}}\rfloor,由于这个 C 是非严格递减的,即便在全部相等的前提下,因为右移作用越来越强烈,其为 1 的地方也在不断往后移动,因此,其实不会发生进位。

那么既然不会发生进位,第一个堵点就消失了。

接着我们就手玩一下样例,用组合计数的方式解决一下这个问题。

注意,在设计这个计数算法的过程当中,尽量让每层保持独立,即该层只考虑选了该层的数字的情况,这样子前面的数字因为选了他们的组合已经选过了,就不用考虑了,会轻松一点。

不过这道题目能不能做到这一点呢?就让我们看看解法吧。(是可以的,但需要一个关键观察,特别是对于前缀相等情况)

为了方便描述这个过程,令 freqifreq_i 为输入数组中 i 的频数,preipre_i 为 i 位置前(包括自己)最晚的 1 出现位置(描述的是查询数字 x),remrem 这个数字指的是 i 之后所有的数字的频数之和。

答案 ans0ans\gets0,等于(前缀相同)情况下的辅助统计量 eqAns1eqAns\gets1

其实,在做这道题目的时候,我也不止一次的想过,要是这道题目是 >x>x 就好了。那所以说,我们为什么不这么做呢?eqAnseqAns 维护等于情况,ansans 维护严格大于情况,最后 ansans+eqAnsans\gets ans+eqAns 不就行了?

从高到低遍历,遇到一位 freqi>0freq_i>0,有这么几种情况。

  • Case 1:i=preii=pre_i
    • 如果 freqi=1freq_i=1,不需要进行任何操作。
    • 如果 freqi>1freq_i>1,这个是最复杂的情况。面对这种情况,我还想过记忆化搜索,dp 等等,以解决这个问题,不过最后发现,其实都不用,只需要一个关键观察:即需要选择多少个 i 达到这个等于状态是唯一确定的。需要选择的数量,就是(自这位开始的)连续 1 的数量,令其为 conti1\text{conti1}
      • 首先,如果你选择的数量超过了(自这位开始的)连续 1 的数量,那么你就已经超过了,超过的时候至少要驻守 conti1+1\text{conti1}+1 个,才能保证超过,当然,除了 iconti1=0i-\text{conti1}=0 的特殊情况,这种特殊情况因为相等,所以只能更新 eqAnseqAns***。***当然,写出这里的统计式子需要有这么一个想法,就是把 remrem 和这个前面这部分分开来看。

iconti1=0:eqAnseqAns×(j=contifreqiCfreqij)×2remiconti1>0:ansans+eqAns×(j=conti+1freqiCfreqij)×2remeqAnseqAns×Cfreqiconti1i-\text{conti1}=0:eqAns\gets eqAns\times(\sum^{freq_i}_{j=\text{conti}}C_{freq_i}^{j})\times2^{rem} \\ i-\text{conti1}>0:ans\gets ans+eqAns\times(\sum^{freq_i}_{j=\text{conti+1}}C_{freq_i}^{j})\times2^{rem} \\ eqAns\gets eqAns \times C^{conti1}_{freq_i}

  因为 $\text{conti1}$ 很小,这个式子中间的求和可以用从全组合减去东西的方式优化,达到 $O(W) $ 级别。
- 那么,你如果选的数量小于连续 1 的数量,那么你直接就没有资格相等(以第一个样例为例,$2\ 1\ 1$,只能凑出 $5={(101)}_{2}$,没法凑出 $6={(110)}_{2}$ 这样连续的 1,因为 2 的数量没有达到这个连续段 1 的数量要求)。因此如果 i 的频数小于连续的 1 的数量,直接 break 整个计数过程,并且使得  $eqAns\gets 0$。
- 当然,我们还需要进行**全局右移操作**,即:
1
2
3
4
5
6
auto right_move=[&](ll step) {   //  传入参数 step = conti1
for (int j=1;j<i;++j) {
freq[max(0ll,j-step)]+=freq[j];
freq[j]=0;
}
};
  • Case 2:i>preii>pre_i
    注意,思路要清楚,ansans 一定要保证这个答案 >x>xeqAnseqAns (统计的)一定要保证前缀严格相等。还是一样,i=0i=0 代表运载能力为 0,只能更新 eqAnseqAnsi>0i>0 的时候更新 ansansj=1j=1 开始是因为至少你要选一个才超过是吧。

i=0:eqAnseqAns×2freqii>0:ansans+eqAns×(j=1freqiCfreqij)×2remi=0:eqAns\gets eqAns \times 2^{freq_i} \\ i>0:ans\leftarrow ans+eqAns\times(\sum^{freq_i}_{j=1}C_{freq_i}^{j})\times2^{rem}

如果说 freqi=0freq_i=0,需要进行这个合法性检验,即如果 i=preii=pre_i,直接 break 整个计数过程,并且使得 eqAns0eqAns\gets 0

⚠️ 注意:我们从高到低遍历,我们要遍历到 -1(就是代表这些驼鹿的运载能力归零了),因此,我们会给整个系统加一个偏移量,0 代表指数为 -1,1 代表为 0,以此类推。

⚠️ 注意:所有的 break 之前,都要使得 eqAns0eqAns\gets 0

AC代码

AC,解法可以通过 F2。

https://codeforces.com/contest/2182/submission/357349404


心路历程(WA,TLE,MLE……)

一开始的算法得出来的答案不太对,因为计数条件和题目规定有不同,题目的 C 数组是排好序的,因此就不在乎顺序了,求的就是组合数。

image

image

  • 如果 freqi>1freq_i>1,还好办吗?我们先想一种,即 ansans+2rem×freqians\leftarrow ans+2^{rem}\times freq_i,毕竟有 freqifreq_i 种选择嘛,这种自然是没什么问题。但是,选了一个 i 以后,其他的 i 我们直接放任不管吗?这是不行的,我们还需要进行右移操作,即 freqi1freqi1+freqi1freq_{i-1}\gets freq_{i-1}+freq_i-1(先右移,再统计这个 remrem 数量)。

  • 如果 freqi>1freq_i>1,这个是最复杂的情况,我们需要求出来新的 eqAnseqAnseqAnseqAns×freqieqAns\gets eqAns \times freq_i,然后,我们要进行右移操作,我们进行的是全局右移操作,即对  i<i\forall\ i'< ifreqi1freqi1+freqifreq_{i'-1}\gets freq_{i'-1}+freq_{i'}freqi1freqi1+freqi1freq_{i-1}\gets freq_{i-1}+freq_i-1

image

  • Case 2:i=preii=pre_i
    • 如果 freqi=1freq_i=1,不需要进行任何操作。
    • 如果 freqi>1freq_i>1,这个是最复杂的情况。我们发现,这里,如果我们选了第一个数,可以下放 freqi1freq_{i}-1 个数字到 freqi1freq_{i-1},但是选了第二个数字以后,只能下放 freqi2freq_{i}-2 个数字到 freqi1freq_{i-1},这种下放,好像不能用公式法做出来,难道每一个都要重新算吗?
      • 其实我们会有一个感觉,就是好像,这个是可以记忆化搜索,或者说是 dp 的。可以发现我们的这个状态由这几个因素决定:
        image
      • 其实我们会发现这个 dp 和询问的 x 没什么关系,可以放在一起做?不过你说还有操作增删操作?呃,这个确实是一个问题,不过由于整个东西是离线的,我们可以得到每个位置的 freqifreq_i 的最大值,按照这个 dp,就不会出现问题了。
      • 不过还有一个问题,就是如何将查询做到 O(1)O(1) 呢?其实这个的话前缀和即可。不过折腾来,

⚠️ 注意:注意有限右移操作和全局右移操作。

那么这个上面是我们脑内模拟了一下这个过程,得出来的计数结论,我们实现一下,看看对不对?

哈哈,我们会发现,这样子操作,得出来的答案不对,这是为什么呢?

以这个第一个样例的第一个询问为例,我们会发现,其实这个答案并不在乎顺序

那我们的计算要怎么样搞?其实,我们发现我们上面的思路还是比较混乱的,即有的时候认为是组合,有的时候又认为是排列。

不过,我们发现,题目中的 cici+1c'_i\ge c'_{i+1} 是非常重要的,这意味着,只要是为了这个相等作用,无论

⚠️ 注意:我们从高到低遍历,我们要遍历到 -1(就是代表这些驼鹿的运载能力归零了),因此,我们会给整个系统加一个偏移量。

rem=0:ansans+eqAns×((2rem+freqiconti1++2rem)+(2rem+freqiconti11++2rem)++2rem)rem0:ansans+eqAns×((2rem+freqiconti11++2rem)+(2rem+freqiconti12++2rem)++2rem)rem=0:ans\gets ans+eqAns\times((2^{rem+freq_{i}-\text{conti1}}+\cdots+2^{rem})+ \\ (2^{rem+freq_{i}-\text{conti1}-1}+\cdots+2^{rem})+\cdots+2^{rem}) \\ rem≠0:ans\gets ans+eqAns\times((2^{rem+freq_{i}-\text{conti1}-1}+\cdots+2^{rem})+ \\ (2^{rem+freq_{i}-\text{conti1}-2}+\cdots+2^{rem})+\cdots+2^{rem})

  • Case 1:i>MSBi>\text{MSB}(MSB 即查询数字 x 最大二进制位的位数)
    • 如果 freqi=1freq_i=1,那么好办,直接 ansans+2remans\leftarrow ans+2^{rem} 即可。
    • 如果 freqi>1freq_i>1,还好办吗?这里需要有这么一个想法,就是把 remrem 和这个前面这部分分开来看,即:

ansans+2rem+freqi1+2rem+freqi2++2remans\leftarrow ans+2^{rem+freq_{i}-1}+2^{rem+freq_{i}-2}+ \cdots+ 2^{rem}

这个式子这样子理解,首先,这个数组 C 是倒序排序的,即 $c'_i\ge c'_{i+1}$,因此,选了排在第一个的数,就有 $2^{rem+freq_{i}-1}$ 种组合(全组合的这个公式),选了排在第二个的数,就只有 $2^{rem+freq_{i}-2}$ 种组合了(没法选第一个数),一次类推,就得到了这个式子。之后,我们就当 i 这个数字不存在就行了。

思路讲解

image

你可以发现,这个式子可以写成这样:

2c1+2c22^{c_1}+2^{c_2}

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

当然,我们首先看到这道题目,会想到暴力。

但是暴力显然不行,先不管这个东西要怎么求,存储上,你存都存不下来。

既然这个暴力是不行的,我们就想到,这个图上面的路径太多了,有什么东西两点之间只有一条路径呢?其实就是树。

那么这种比较复杂的题目,特别是比较复杂的图论题目,一般都要划分阶段。

因此我们就要想,这个,如何把这节点一点一点的加入这个图当中?

可以的,兄弟,是可以的。

我们假设我们现在有一张图,我们可以先把到这张图距离为 i 的点加入进来,再把 i+1 的点加入进来,… 。

我们可以使用 kruskal 进行实现,kruskal 的基本过程就是按从小到大排序,加入到图中,使用并查集判环。

划分阶段其实也很简单,就是两张图把距离为 i 的点加入进来,停住。干一些事情,再继续加入距离为 i+1 的点,… 。

我们把一个中间状态摘出来看,如果原来的图是一样的(同构的),加入进来的点的并查集划分是一样的,那么两张图就是同构的。

这么说有点抽象。比如说蓝色的点是原来的图,红色的点是新加进来的点,不难发现,这两张图是同构的。因为只要并查集划分相同,新点之间的这个 dis 就是 i,老点到新点之间的 dis 也都是 i。

image

然后我们就来到了最后一个问题,怎么样快速判断( O(1)O(1) )两个图的并查集划分是相同的那?呃,其实不是那么难的。我们请出我们的老盆友,哈希。

一个联通块的签名可以理解为 有什么点。有什么点的话,我们可以给每个点整一个随机值(mt19937_64),然后一个联通块有什么点,就相当于其所有拥有的点的随机值的异或起来。

把这个好几个联通块组合(异或)起来,就可以得到目前的这个并查集划分状态,然后直接比较这个哈希值即可。

AC代码

https://qoj.ac/submission/1415583

心路历程(WA,TLE,MLE……)

merge 的逻辑比较复杂,容易写错,之后可以使用,Old,New,这样的命名,避免混淆。

1
2
3
4
5
6
7
8
9
10
11
void merge(ll a, ll b) {
ll faa = find(a), fab = find(b);
if (faa == fab) return;
S ^= mix(Sum[faa], siz[faa]);
S ^= mix(Sum[fab], siz[fab]);
fa[faa] = fab;
siz[fab] += siz[faa];
Sum[fab] ^= Sum[faa];
S ^= mix(Sum[fab], siz[fab]);
return;
}

思路讲解

dij 不能用,因为边太多了。

因为边权只有0 和 1,可以使用位集 bfs。

AC代码

AC

https://qoj.ac/submission/1411426

判断一下是否可以两次抵达,196ms

不判断两次抵达,1309ms

https://qoj.ac/submission/1410868

心路历程(WA,TLE,MLE……)