4月每日总结

4月每日总结

Tue Apr 01 2025
46 分钟
10492 字

4 月 1 日#

我尽力,我无悔。

T1#

对于那种只输入一两个数的题,首先不要去想正解,先猜半个小时结论看看猜不猜的出来。多半是猜的出来的

可是我写 T2 去了。

发现从 001n\frac{1}{n} 的概率一发入魂。而剩下 n1n\frac{n - 1}{n} 的概率就是在 [1,n1][1,n - 1]。对于每个点都有 1n\frac{1}{n} 的概率赢,有 n2n\frac{n - 2}{n} 的概率留在 [1,n1][1,n - 1],那么对于每个 i[1,n1]i \in [1,n - 1] 都是一样的。期望是 1+(n1)2n1 + \frac{(n - 1)^2}{n}

T2#

赛时想法:设 dpi,jdp_{i,j} 表示第 ii 个点取值为 jj 的方案数。显然有:dpi,j=k=1aidpi1,k×[kj]dp_{i,j} = \sum\limits_{k = 1}^{a_i}dp_{i - 1,k} \times [k \neq j]。复杂度 O(nV2)O(nV^2),可以通过全局和优化到 O(nV)O(nV),然后拿到 2020 的好成绩。
我们发现这玩意可以简化成三个操作:区间取反,区间赋值,区间加。前面两个都是特化的区间乘。我们直接动开线段树,花了 2h 的时间获得的 6060好成绩

正解是这样的:正难则反。我们想要求恰好00 个非法点的情况数,我们就尝试求钦定ii 个非法点的情况数 F(i)F(i),然后反演一下得到最终的答案。

考虑如何求 F(i)F(i)。设 dpi,jdp_{i,j} 表示前 ii 个数分 jj 段的方案数。显然有 F(i)=dpi,niF(i) = dp_{i,n - i}。有显然的转移:dpi,j=k=1idpk1,j1×minl=kialdp_{i,j} = \sum\limits_{k = 1}^{i}dp_{k - 1,j - 1} \times \min\limits_{l = k}^{i}a_l。复杂度 O(n3)O(n^3),可以拿到 00 分的好成绩

我们发现我们不关心 jj 的值,只关心它的奇偶性。我们就可以每次转移都乘一个 1-1,变成 dpi=k=1idpk1×minl=kialdp_i = \sum\limits_{k = 1}^{i}-dp_{k - 1} \times \min\limits_{l = k}^{i}a_l。复杂度 O(n2)O(n^2),可以获得 6060 的好成绩。

最后用单调栈优化到 O(n)O(n) 即可通过。

T3#

我们发现如果对于一个可行的方案,每个数都异或一个 xx,最终答案也可行。且总的异或和也异或了 yy。所以我们不用考虑具体的方案,求出总方案数除以 2k2^k 就行。

考虑先填好第二行,方案数 2k×(2k1)×(2k2)××(2kn)2^k \times (2^k - 1) \times (2^k - 2) \times \cdots \times (2^k - n),然后钦定第一行有 ii 个与第二行冲突,方案数为 (2ki)×(2ki1)×(2ki2)××(2kn+1)(2^k - i) \times (2^k - i - 1) \times (2^k - i - 2) \times \cdots \times (2^k - n + 1)。直接反演得到恰好有 00 个与之冲突的方案数。

后日谈#

今天就是纯纯的数学题综合训练。于是开始预习复习概率与期望。

我尽力,我无悔。

4 月 2 日#

不计代价。

不过我要是真的能做到 不计代价 的学习的话,我就不会那么菜了。

DFS Order 2#

我们想到,一个子树内的 dfs 序肯定是连续的,我们考虑跑一个背包。设 dpu,idp_{u,i} 表示 uu 在第 ii 个被访问的方案数。我们先想总共有多少个 dfs 序。那么显然,如果有 totutot_u 个子树,那么就有 totu!tot_u ! 个子树排列的方式。每个子树又有自己的排列方式,乘起来就是了。dp1,1dp_{1,1} 就是总的排列方式数。初始化就完了。

考虑如何转移:我们想从 dpu,idpv,jdp_{u,i} \to dp_{v,j}。我们发现,一次加的肯定是一整个子树。那么我们想:做背包!设 tmpjtmp_j 表示 两个点在 dfs 序上相距 jj 的方案数。转移有显然的 dpv,jdpu,i+tmpjidp_{v,j} \leftarrow dp_{u,i} + tmp_{j - i}

但是我们发现:子树之间是不分顺序的,我们转移 vv 的时候还得扣掉 vv 的子树。我们不可能对于每个子树都做个不考虑它的背包,那么复杂度直奔 O(n4)O(n^4),显然不可取。

我们想:效仿淀粉质!每次扣掉 vv 的子树所做的贡献,转移完再加回来。我们在每个 uu 的子树转移时设单独的 fi,jf_{i,j}。表示在 ii 个子树内总共选了 jj 个点的方案数。那么初始化有 f0,0=1f_{0,0} = 1。转移有显然的 fi,jfi1,jsizvf_{i,j} \leftarrow f_{i - 1,j - siz_v}

那么我们又如何从 ftmpdpf \to tmp \to dp 呢?对于 ftmpf \to tmp,有 tmpj+1=fi,j×i!×(totui1)!tmp_{j + 1} = f_{i,j} \times i! \times (tot_u - i - 1)!,三项分别表示 ii 个子树内的方案数ii 个儿子排列的方案数剩下儿子排列的方案数

但是我们输出发现:比例正确,值错误。我们直接化到最简。我们发现比例正确但是值不正确的原因是有 tmpitmp_i所以就是直接化到最简

Core code:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
\\ 预处理
int dfs1(int u,int fa)
{
    int res = 1;
    siz[u] = 1,tot[u] = 0;
    for (auto &v : g[u])
    {
        if (v == fa)
            continue;
        res = res * dfs1(v,u) % mod;
        siz[u] += siz[v],tot[u]++;
    }
    return res * fac[tot[u]] % mod;
}

\\ 转移
void dfs2(int u,int fa)
{
    memset(f,0,sizeof f);
    f[0][0] = 1;
    for (auto &v : g[u])
    {
        if (v == fa)
            continue;
        for (int i = tot[u]; i >= 1; i--)
            for (int j = siz[u]; j >= siz[v]; j--)
                f[i][j] = (f[i][j] + f[i - 1][j - siz[v]]) % mod;
    }
    for (auto &v : g[u])
    {
        if (v == fa)
            continue;
        for (int i = 1; i <= tot[u]; i++)
            for (int j = siz[v]; j <= siz[u]; j++)
                f[i][j] = (f[i][j] - f[i - 1][j - siz[v]] + mod) % mod;
        memset(tmp,0,sizeof tmp);
        for (int i = 0; i < tot[u]; i++)
            for (int j = 0; j <= siz[u]; j++)
                tmp[j + 1] = (tmp[j + 1] + fac[i] * fac[tot[u] - 1 - i] % mod * f[i][j] % mod) % mod;
        for (int i = 1; i <= n; i++)
            for (int j = 1; i + j <= n; j++)
                dp[v][i + j] = (dp[v][i + j] + dp[u][i] * tmp[j]) % mod;
        for (int i = tot[u]; i >= 1; i--)
            for (int j = siz[u]; j >= siz[v]; j--)
                f[i][j] = (f[i][j] + f[i - 1][j - siz[v]]) % mod;
    }
    for (auto v : g[u])
        if (v != fa)
            dfs2(v,u);
}

\\ 输出化简
for (int i = 1; i <= n; i++)
{
    int sum = 0;
    for (int j = 1; j <= n; j++)
        sum = (sum + dp[i][j]) % mod;
    int tmp = binpow(sum,mod - 2);
    for (int j = 1; j <= n; j++)
        printf("%lld%c",dp[i][j] * dp[1][1] % mod * tmp % mod," \n"[j == n]);
}

Great Expectations#

原题也做不起吗?

我们考虑 ctjdpi,jdp_{i,j} 表示在 ii 个困难之后花费了 jj 分钟的方案数。那么有转移:

dpi,j=pi×dpi+1,j+tim+tim+{min{dp0,0,dpi+1,j+di+tim+di+tim}j+di+nti<rdp0,0otherwise.dp_{i,j} = p_i \times dp_{i + 1,j + tim} + tim + \\ \begin{cases} \min\{dp_{0,0},dp_{i + 1,j + d_i + tim} + d_i + tim\} & j + d_i + n - t_i < r\\ dp_{0,0} & \text{otherwise.} \end{cases}

具体来讲,就是如果还有可能破纪录,就尝试能不能破纪录。反之直接重开。但是我们发现这玩意有后效性,直接高斯消元。高斯消元肯定是不可取的,毕竟有个 min\min 在那里镇着。我们考虑二分 dp0,0dp_{0,0}。如果最终的 dp0,0dp_{0,0}midmid 小,就说明有些本该重开的我们选择了继续打,往小二分。反之往大二分。

糖果大战#

高斯消元 yyds

游走#

此游走非彼游走。

设总共有 cntcnt 条路径,总长为 lenlen。那么直接 dfs 搜出每个以每个点为起点的路径数量和路程总长,一加一除就出来了。

如果这题没模我不炸了?

汉堡 Burger#

题意简化:有一个 01 串,0 的数量和 1 的数量各占一半。且最后的两个是连续的 1,求出这么滴的概率。

正难则反。设 ansians_i 为长度为 ii 的不成立的概率。那么显然有 ansn=(n2n21)2n2\displaystyle ans_n = \frac{\binom{n - 2}{\frac{n}{2} - 1}}{2^{n - 2}}。我们发现这题没模,不能预处理阶乘。考虑推式子。推得 ansi=ansi2×i1ians_i = ans_{i - 2} \times \frac{i - 1}{i}。答案就是 1ansn1 - ans_n

后日谈#

我都写 Java 了我还管常数吗?

好想念小学时无忧无虑的周末啊。。。

4 月 6 日#

沟槽的构造题。

今天早上打 VP,直接被构造题创飞,于是开始练习构造。

好像练了 dp 就只会 dp 一样。其实 dp 也不够会,其他都不会做。

Arcology On Permafrost#

考虑 f(a)f(a) 的最大值。首先一定有 f(a)nm×kf(a) \le n - m \times k,因为一个序列的 MEX\operatorname{MEX} 不可能大于它的长度。还有 f(a)nm+1f(a) \le \lfloor \frac{n}{m + 1} \rfloor,因为为了在 mm 次里,每个数都有剩的,每个数的出现次数必须大于 m+1m + 1

考虑如何构造:

  • nm×k<nm+1n - m \times k < \lfloor \frac{n}{m + 1} \rfloor,那么我们有 i[0,n),ai=imodki \in [0,n),a_i = i \bmod k,这样可以保证无论删掉哪几个子串,最后都会有 ai=ia_i = i,使得 MEX\operatorname{MEX} 最大。
  • nm×knm+1n - m \times k \ge \lfloor \frac{n}{m + 1} \rfloor,我们可以构造 i[0,n),ai=imodnm+1i \in [0,n),a_i = i \bmod \lfloor \frac{n}{m + 1} \rfloor。这样可以保证相同的数字之间至少有 kk 的距离,每个数还有至少 m+1m + 1 个。

Happy Birthday! 3#

神金区间 dp。

自己写的 solution

后日谈#

自己抓了一点构造题来练,我连黄的构造题都做不太出来我还去做蓝色的构造题,我扯吧我。

忽然发现今天好像就做了这几道题,也是十分的颓废。

4 月 7 日#

今天也是不颓

所以到底是颓还是不颓?

Blossom#

没搞懂。

等洛谷上有题解了再去看一遍。

Roller Coaster#

神 tm Adhoc。

考虑对于一个已经选好的路径,我们找到高度相差最大的两个点,显然有结论:在这俩点里转来转去肯定是比当前路径更优的。于是我们直接找到一对 huhv|h_u - h_v| 最大的 u,vu,v,答案就是 huhv2\frac{|h_u - h_v|}{2}

只能说我太蠢了。

Big XOR Game#

自己想到了 75%75\%,还是挺不错的。
下次能想到 100%100\% 就更好了。

想到之前考试有道题。设所有的数异或和为 sumsum,当 sum=0sum = 0 时肯定平局。否则一定在 sumsum 的最高位决出胜负。问题转化为:有 nn 个数,里面有奇数个 11,先后手轮流取,取了奇数个 11 的人获胜。

考虑分类讨论,设有 cntcnt11

有偶数数个 00nn 为奇数)

  • cnt1(mod4)cnt \equiv 1 \pmod 4
    Alice 先取一个 11,之后都和 Bob 取相同的。Alice 取了奇数个 11,Alice 得了 MVP!!!
  • cnt3(mod4)cnt \equiv 3 \pmod 4
    Bob 总和 Alice 取相同的,Bob 取了奇数个 11,Bob 得了 MVP!!!

有奇数个 00nn 为偶数)

  • cnt1(mod4)cnt \equiv 1 \pmod 4
    同上情况 1,必胜。
  • cnt3(mod4)cnt \equiv 3 \pmod 4
    Alice 取了 00,变成上面的情况 2 的后手,必胜。

Bracket Sequence#

唐题。

我们考虑 dp。dpidp_i 表示到 ii 的 h 的数量(其实就是方案数啦)。考虑转移:dpi=dpi1+dpjdp_i = dp_{i - 1} + \sum dp_j。其中 jj 满足 (j,i](j,i] 是一个合法的括号序列。

考虑如何快速求这个 dpj\sum dp_j。我们想到记录一个 belibel_i 表示将 ( 视为 11) 视为 1-1 的前缀和。维护一个 sumjsum_j 表示所有 beli=jbel_i = jdpidp_i 之和。但是我们发现一个问题:这个括号序列:()(),第 33 个会从第 11 个转移而来。但是很显然,)( 并不是一个合法的括号序列。我们考虑如何 ban 掉这种情况。

我们发现出现这种情况当且仅当有 k(j,i]k \in (j,i] 满足 belk<belibel_k < bel_i。我们考虑开一颗线段树。下标为 ii 的地方维护 belibel_i 出现的最后一个地方。对于每个 belbel 开一个队列。在线段树上查询 lstlst 表示 [1,beli1][1,bel_i - 1] 的区间最小值,把队列里在 lstlst 之前的全扔出去,并从 sumbelisum_{bel_i} 中减去。然后转移 dpi=dpi1+sumbelidp_i = dp_{i - 1} + sum_{bel_i}。然后更新线段树,将 ii 压进队列并将 sumbelisum_{bel_i} 加上 dpidp_i

最后的答案就是 dpndp_n

还是 赛格门特吹 好用。

Colourful Bottles#

听 dpfs 讲的。

考虑 dp,设 dpidp_i 表示以 ii 结尾,满足 kk 连续的最小代价。考虑转移:

dpi=min{dpi1+wi直接删掉 idpj1+sumwisumwj1+sumcisumcj+wj不删 idp_i = \min\begin{cases} dp_{i - 1} + w_i & \text{直接删掉 } i \\ dp_{j - 1} + sumw_i - sumw_{j - 1} + sumc_i - sumc_j + w_j & \text{不删 } i \end{cases}

其中 sumwisumw_i 表示 ww 的前缀和。sumcisumc_i 表示与 ii 颜色相同的前缀和。第二个方程具体来说就是:删掉 [j,i][j,i] 之间的所有数,但是同色的不能删,于是把它加回来。而我们不知道 jj 的相同颜色的前驱是啥,于是直接减掉 sumcjwjsumc_j - w_jjj 要满足 [j,i][j,i] 之间至少有 kk 个同颜色的。

考虑优化。对于每个颜色开一个 minmin 数组表示 dpjsumwj1sumcj+wjdp_j - sumw_{j - 1} - sumc_j + w_j 的最小值。又对于每个颜色开一个队列。当队列长度等于 k1k - 1 时,就把对头取出,更新 minmin 数组。然后转移 dpi=min{dpi1+wi,minci+sumwi+sumci}dp_i = \min\{dp_{i - 1} + w_i,min_{c_i} + sumw_i + sumc_i\}。转移完了扔进队列。

后日谈#

明天的模拟赛和重庆八中联考。又可以丢 CW 的脸了

每次一看到这个高达 16.7%\color{#ff0000}16.7\% 的得分率我就头大。明明打的对的题,总是因为一些莫名其妙的愿因挂分。要么是实现太优秀,常数爆炸,要么是没注意细节。

There’s no time left.

4 月 8 日#

今天是真的不颓。

T1#

如果说有这样的 a={1,2}a = \{1,2\}b={100,101}b = \{100,101\},显然我们不能先加 a1a_1,而应该先加 a2a_2。而后又看到 xx 可以为负,想到如果说将 aia_i 加上 xx 使得 ai=bia_i = b_iai>ai+1a_i > a_{i + 1},我们就连一条 i+1ii + 1 \to i 的边。可以证明这是一个有向无环图。因此我们一定有用 i=1n[aibi]\sum\limits_{i = 1}^{n}[a_i \ne b_i] 次的方法使得 a=ba = b

现在我们考虑使代价更小。考虑对于一个 (aibi)2(a_i - b_i)^2,要使它变小,我们考率分多次。显然等分最优。赛时想到了做背包 dp,复杂度 O(nm2)O(nm^2)。可以拿到 50\color{#5ec05e}{50} 的好成绩。看这个绿色就知道确实很好

正解是整一个大根堆。将每个 (aibi)2(a_i - b_i)^2 多分一个的负贡献插入,比如初始就放每个 (aibi)2(a_i - b_i)^211 次到 22 次的负贡献。取 mi=1n[aibi]m - \sum\limits_{i = 1}^{n}[a_i \ne b_i] 次就好。

没放代码是因为下次再做的时候希望能自己写出来。

T2#

听 HD0X 讲的。

看到冒泡排序,想到逆序对。离线,从大到小枚举 ii,并枚举每个 x=ix = i 的查询,计 uu 表示 ii 位置前面的比它大的数量,vv 表示 ii 位置后面的比它大的数量。让我们分类讨论一下(下文的 kk 都指查询的 kkidid 指查询编号,posipos_iii 的原位置):

  • kuk \le u
    这样的话,ii 前面的会在 kk 次冒泡后跑到 ii 后面去并把 ii 往前挤,ansid=posikans_{id} = pos_i - k
  • ku+vk \le u + v
    从头开始考虑 kk 次冒泡。每次冒泡,一定会有一个比 ii 更大的数 PjP_j 被一个更大的数 Pj+1P_{j + 1} 堵住,且位置比 Pj+1P_{j + 1} 左一个。
    我们掏个样例出来:{4,3,5,1,2}\{4,3,5,1,2\}。我们想要查询 33 在第 22 次冒泡后的位置。
    11 次冒泡:{3,4,1,2,5}\{3,4,1,2,5\}4455 堵住,位置 1-1
    22 次冒泡:{3,1,2,4,5}\{3,1,2,4,5\}3344 堵住,位置 1-1
    这样我们看到 3311 的位置。这个 11 是由 55 最初的位置经过 221-1 后得到的。
    我们再掏一个样例:{3,4,2,5,1}\{3,4,2,5,1\}。我们想要查询 22 在第 33 次冒泡后的位置。
    11 次冒泡:{3,2,4,1,5}\{3,2,4,1,5\}4455 堵住,位置 1-1
    22 次冒泡:{2,3,1,4,5}\{2,3,1,4,5\}3344 堵住,位置 1-1
    33 次冒泡:{2,1,3,4,5}\{2,1,3,4,5\}2233 堵住,位置 1-1
    2211 的位置。这个 11 是由 55 最初的位置经过 331-1 得到的。
    我们发现,因为在这 kk 次里会被堵 kk 次,所以最初的位置(就是上文的 55 的最初的位置)一定是比 ii 大的数按照原序列的顺序的第 kk 个。而每次被堵会使得下标 1-1,所以答案就是比 ii 大的数的第 kk 个的位置减 kk
  • k>u+vk > u + v
    显然这个时候 ii 归位了,ansid=nuvans_{id} = n - u - v

实现可以这样:因为我们从大到小枚举 ii,用线段树维护。每次算完 ii 都将线段树 posipos_i 的地方置为 11uu 就是 [1,posi][1,pos_i] 的区间和,vv 就是 [posi,n][pos_i,n] 的区间和。“比 ii 大的数按照原序列的顺序的第 kk 个”可以通过线段树上二分找到第 kkii。因为只有 k>uk > u 时才会询问第 kk 个,所以第 kk 个一定在 ii 原位置的右边。

T3 & T4#

补题速度还是太快了一点,没看。

后日谈#

之前的考试挂分 50\color{#ff0000}{50} 起步,今天只挂了 10\color{#5ec05e}{10} 分,可喜可贺。

要是 T1 没有被背包误导的话就更好了。好歹有 50 分嘛

今天晚上有 div.3,打不打呢?

4 月 9 日#

弄懂每一题。然而我太蠢了

Square Subsequences#

谁能给我讲讲 bitset 优化 LCS 啊?

简而言之就是枚举中间的分界点,然后对于两边的子串跑 bitset 优化 LCS。记录最大的 LCS 长度与其对应的 bitset。然后通过 bitset 反推 dp 数组,从而反推方案。时间复杂度 O(n3ω+n2)O(\frac{n^3}{\omega} + n^2),时限 22 s,在 CF 神机上跑了 1.51.5 s,换别的 OJ 还不一定跑的完。

然而我太蠢了没弄懂 bitset 优化 LCS。

Adjacent Delete#

自从学了 C# 和 Java 以来,代码风格愈发面向对象了。与之而来的还有更大的常数。希望不要搞出在赛场上因为常数太大而 TLE 的事。

考虑没有相邻的限制如何做。显然是最大的 n2\frac{n}{2} 个减去最小的 n2\frac{n}{2} 个。现在考虑相邻的限制,这个上界也是可行的。我们把每个最大的 n2\frac{n}{2} 的标成 +,最小的那几个标成 -。显然无论如何都会有一对 +- 相邻。

对于偶数 nn 显然就是那个上界。对于奇数 nn,因为会留下一个,留下的这个会将 aa 数组分成两半,而两边都会取完,所以两边的长度肯定都是偶数。所以留下的只能是 a1,a3,a5,a_1,a_3,a_5,\dots,对于两边,就是上文的上界。我们可以用两个 multiset 维护。一个小根的维护大的那几个数,大根的维护小的那那几个数。每次如果插入的 xx 大于等于小根堆的堆顶,就插入小根堆。否则插入大根堆。并维护一个堆内和。

放下我的常数超大代码:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include<bits/extc++.h>
#define int long long
using namespace std;
const int maxn = 3e5 + 5;
int n;
int a[maxn];
class two_set
{
    private:
    multiset<int,less<>> mx;// 小根堆维护最大的几个数
    multiset<int,greater<>> mi;// 大根堆维护最小的几个数。
    void balance()
    {
        if (mx.size() > mi.size() + 1)
        {
            _min += *mx.begin();
            _max -= *mx.begin();
            mi.insert(*mx.begin());
            mx.erase(mx.begin());
        }
        if (mi.size() > mx.size())
        {
            _max += *mi.begin();
            _min -= *mi.begin();
            mx.insert(*mi.begin());
            mi.erase(mi.begin());
        }
    }
    public:
    int _min,_max;// 分别表示最小的数的和,最大的数的和。
    void insert(int x)
    {
        if (mx.empty() || x >= *mx.begin())
        {
            _max += x;
            mx.insert(x);
        }
        else
        {
            _min += x;
            mi.insert(x);
        }
        balance();
    }
    void erase(int x)
    {
        auto it = mx.find(x);
        if (it != mx.end())
        {
            _max -= x;
            mx.erase(it);
        }
        else
        {
            _min -= x;
            it = mi.find(x);
            assert(it != mi.end());
            mi.erase(it);
        }
        balance();
    }
}s1,s2;
void solve1()// 起码这里挺简洁的
{
    for (int i = 1; i <= n; i++)
        s2.insert(a[i]);
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        s2.erase(a[i]);
        if (i & 1)
            ans = max(ans,s1._max - s1._min + s2._max - s2._min);
        s1.insert(a[i]);
    }
    printf("%lld",ans);
}
void solve2()
{
    sort(a + 1,a + n + 1);
    int _max = 0,_min = 0;
    for (int i = 1; i <= n >> 1; i++)
        _min += a[i];
    for (int i = (n >> 1) + 1; i <= n; i++)
        _max += a[i];
    printf("%lld",_max - _min);
}
signed main()
{
    scanf("%lld",&n);
    for (int i = 1; i <= n; i++)
        scanf("%lld",a + i);
    if (n & 1)
        solve1();
    else
        solve2();
    return 0;
}

后日谈#

yzp 因为鞋子没垫鞋垫导致他极其不舒服。然后今天也是十分颓废,只做了 33 题。

yzp 说他想回家,我的评价是:住校生是这样的。

不过住校生不开心还可以逃回家,走读生逃回家也没用,因为天天回家,对家已经无感了。不开心的时候只能畅想去到异世界。中二病

4 月 11 日#

沟槽的树套树,沟槽的体考。

Two Permutations#

我们发现,PiP_iQiQ_i 是绑定的,不会分开。如果 P,QP,Q 都无序十分的难做且 P,QP,Q 的顺序是不变的。我们可以将 PP 排序,然后考虑 QQ

我们建一个模型:将 P,QP,Q 和与之对应的 RR 看作上下两排。如果有 u<vu < v,那么就建一条 uvu \to v 的边。边可以是横着(P,QP,Q 内部建边),也可以是竖着 (PQP \to QQPQ \to P)。非法当且仅当出现了环。

我们考虑 QQ 中的最大值 QuQ_u

  • 如果 uu 是向上建边的起点,说明 SuS_uSSuu 开始的后缀都是 00,往上建边。因为 PP 已经按照从小到大排序了,后面的一定比前面的大。而 QQ 这里又是最大值,后面一定比 QuQ_u 小,那么说明这段后缀都是向上建边。这有什么用?这就说明后面不会成环且 SS 确定了,不用考虑了。
  • 如果 uu 是向下建边的终点,只能说明 Su=1S_u = 1。但是因为 QuQ_u 是最大值,所以它的边一定是往左右的,不会成环。

所以我们可以把题意转化成这样:有一个序列 QQ,每轮可以执行一次两种操作:

  1. QQ 的最大值 QuQ_u 及从 uu 开始的后缀删掉。
  2. 只删掉 QQ 的最大值 QuQ_u

求出删完 QQ 的方案数。

结论:答案就是 QQ 的上升可空子序列数量。如何理解?我们只考虑操作 1。上升子序列就是一种操作 1 的操作序列。因为每次取走的是一段后缀且 QuQ_u 是最大值,所以下一个操作的就只能是 uu 前面的比 QuQ_u 小的数。

那有人要问了?操作 2 你没考虑啊?其实也是考虑的。因为是子序列,所以里面可能夹杂着有空位,这些空位就是操作 2 的地方。因为操作 2 的 SS 只能是 11,方案数是 11,所以不用计算。

树状数组优化 dp 即可。

Guess Sequence#

我们对于 SiTiS_i \to T_i 建一条边。那么整个图一定是一棵树。如果不是一棵树那么就一定有点没被连边,这个点就可以乱取。

我们考虑对于一个点 uu,如果 bub_u 乘上了 xx,那么 bvb_vvv 是与 uu 相邻的点)为了乘积不变,就得乘上 1x\frac{1}{x}。经过一番推导,我们发现,点 vv 的深度与 uu 的奇偶性相同,bub_u 乘上了 xxbvb_v 也得乘上 xx,反之乘上 1x\frac{1}{x}。这样我们就把 nn 个点分成了两个集合。深度为奇的成一个集合,深度为偶的成另一个集合。

我们发现,如果一个集合的数里有个共同的因数 xx,那么就可以把当前集合里的的乘上 1x\frac{1}{x},另一个集合里的就乘上 xx,可以构造一个新的 bb,显然不行。所以我们需要把 aa 分成两个 gcd\gcd11 的集合,如果可行就是 Yes,反之就是 No

后日谈#

什么两米八三,我才跳了一米八三。

事情的起因是这样的:我们去体考,考立定跳远。前面的贾皓博第一跳只跳了 2.42.4 米多。我就在想:是不是成外的测量器具有问题啊?我之前的 2.62.6 米是不是不真啊?于是第一跳就全力。大概是腿太粗?力量太大?

跑完 10001000 米直接燃尽了,整个下午都没法学习。

4 月 13 日#

一起把周六的日记也写了吧。

考试爆零。机房 rating 16.713.716.7 \to 13.7,决心 inf-\inf

T1#

神秘小 dp,没改,明天改。

T2#

想到了可持久化并查集,没想到和那个可持久化并查集一起的 Kruskal 重构树。

我这么菜不适合 Kruskal 这个英文名。

T3#

主席书双 log\log 做法还没调出来,单 log\log 做法待会再说。

但是我拿测速代码测了 cwoi 的机子每秒大概 4.1×1084.1 \times 10^8,而 3×1053 \times 10^5 的双 log\log 也才 1.1×1081.1 \times 10^8,没准双 log\log 卡卡常能过?

T4#

神经分讨。

考虑 [l,r][l,r] 区间内的最大值 axa_x 出现在三个区间中的哪个位置。

  • 中间
    显然中间对于答案的贡献无论如何都是 axa_x,而两边的段随着长度的减小,贡献显然不会增加。那么最优方案就是 al+ar+axa_l + a_r + a_x
  • 左边
    显然左边的代价无论如何都是 axa_x。根据上文的结论,第二个区间的长度越小贡献越小。所以整个段的答案就是 ax+minr=x+1r1{ar+maxi=r+1r{ai}}a_x + \min\limits_{r' = x + 1}^{r - 1}\{a_{r'} + \max\limits_{i = r' + 1}^{r}\{a_i \}\}。我们离线,从 1n1 \to n 枚举每个 rr 及其对应的询问。设 fif_i 表示当前 rr 所对应的 minr=x+1r1{ar+maxi=r+1r{ai}}\min\limits_{r' = x + 1}^{r - 1}\{a_{r'} + \max\limits_{i = r' + 1}^{r}\{a_i \}\}。用单调栈 + 线段树维护。具体而言是这样的:维护一个单调递减的单调栈。如果当前的 ara_r 把栈顶 stktopstk_{top} 弹出了,由于 astktopa_{stk_{top}} 是之前的 r[stktop1,stktop)r' \in [stk_{top - 1},stk_{top})maxi=r+1r{ai}\max\limits_{i = r' + 1}^{r}\{a_i \},现在被 ara_r 替换了,所以在线段树上的 [stktop1,stktop)[stk_{top - 1},stk_{top}) 区间加 astktop+ar-a_{stk_{top}} + a_{r}。还要单独更新 r1r - 1。枚举每个 rir_i 是当前枚举的 rr 的询问, 那么答案就是 ax+mini=x+1r1{fi}a_x + \min\limits_{i = x + 1}^{r - 1}\{f_i\},直接线段树区间查最小值就是了。
  • 右边
    把整个序列翻转,然后做左边一样的就行。

后日谈#

感觉我一事无成。

4 月 14 日#

想说点什么但是说不出来。

Transforming Pairs S#

也是切上绿题了。

看到这玩意很像 gcd\gcd 的求法,我们看看能不能往 gcd\gcd 上面想。

口胡了一个做法:我们钦定 c<dc < d。首先我们对 dd 进行操作。将 dd 减去 dbc×c\lfloor \frac{d - b}{c} \rfloor \times c。这样可以保证 dbd \ge b。对 cc 进行同样的操作,减去 cad\lfloor \frac{c - a}{d} \rfloor,同样为了保证 cac \ge a

继续口胡的证明:感性理解,我们为了让 ccdd 尽快的减成 aabb,每次减的肯定要尽可能多。于是我们每次进行辗转相减,减到再减一次就会爆炸的程度,每次减得就尽可能多了。

Moo Decomposition G#

Java 的常数还是太优秀了。

我们发现 L1×1018L \le 1 \times 10^{18},直接把整个序列拎出来做肯定是不现实的。但是我们发现一个性质:每段的方案数相互独立。那么我们就可以把每段的方案数单独求出来然后快速幂了。

考虑如何求每段的方案数:从后往前枚举每个字符,我们设 cntcnt 表示当前遇到的 O\texttt{O} 的个数。

  • 遇到了个 O\texttt{O}
    显然是 cntcnt+1cnt \leftarrow cnt + 1
  • 遇到了个 M\texttt{M}
    这个 M\texttt{M} 可以从前面的 cntcntO\texttt{O} 里面任意取 kk 个,方案数乘上 (cntk)\binom{cnt}{k}。然后因为用掉了 kkO\texttt{O},我们需要把 cntcnt 减去 kk

Bessie’s Function G#

一道基环树板子题硬控我一下午。

我也并非人类。

我们发现这玩意可以套一个典中典的模型:对于每个 ii,连一条 iaii \to a_i 的边。因为总共有 nn 条边,所以一定是一个基环树森林。

我们发现如果要修改 aia_i,一定是要把 aia_i 修改成 ii,因为这样不仅可以使得 ii 变得满足条件,还能使出边连接到 ii 的点满足条件。但是为了方便 dfs,不可能建内向的基环树,肯定是建外向的基环树,就变成了让 ai=ia_i = i 可以使得 ii 的儿子们也满足条件,我们就可以把题意转化一下:

对于每个点 uu,都可以花费代价 cuc_u 进行一次操作。操作必须要满足以下条件:如果你不操作那你的所有前驱就必须操作(在树上“前驱”表示儿子,在环上表示上一个节点。),反之你的前驱爱咋咋地。求最小代价。

我们这个题意都把方程给你写出来了。设 dpu,0/1dp_{u,0/1} 表示 uu 操作或者不操作。在树上就是:

dpu,0=vsonudpv,1dpu,1=vsonumin{dpv,0,dpv,1}\begin{aligned} dp_{u,0} & = \sum_{v \in son_u}dp_{v,1} \\ dp_{u,1} & = \sum_{v \in son_u}\min\{dp_{v,0},dp_{v,1} \} \end{aligned}

在环上也类似。如何处理环上 dp 呢?我们考虑钦定 cir0cir_0 的状态。比如我们钦定 cir0cir_0 选,那么 dpcir0,1dp_{cir_0,1} 就设成 inf\inf,最后统计答案最后一个点 uu 就爱选不选,tmp=min{dpu,0,dpu,1}tmp = \min\{dp_{u,0},dp_{u,1}\}。反之就是 dpcir0,0=infdp_{cir_0,0} = \inftmp=dpu,1tmp = dp_{u,1}。最后 ansans 加上两个 tmptmp 的最小值就是了。

以后有向图找环还是老老实实 dfs 吧。

Shorten the Array#

都想到了 85%85\% 了为啥不再想一会呢?

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include<bits/extc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 2e5 + 5;
int n,k,cnt;
int ch[maxn * 30][2],pos[maxn * 30];
void ins(int x,int id)// 字典树插入
{
    int rt = 1;
    for (int i = 30; i >= 0; i--)
    {
        bool dig = (x >> i) & 1;
        if (!ch[rt][dig])
            ch[rt][dig] = ++cnt;
        rt = ch[rt][dig];
        pos[rt] = max(pos[rt],id);// 维护这个这个子树内的最大 id
    }
}
int que(int x)
{
    int sum = 0,res = 0,rt = 1;
    for (int i = 30; i >= 0; i--)
    {
        bool dig = (x >> i) & 1;
        if (ch[rt][dig ^ 1])
        {
            if ((sum ^ (1 << i)) >= k)
            {
                res = max(res,pos[ch[rt][dig ^ 1]]);
                rt = ch[rt][dig];
                // 如果可以 >= k 就记录答案,然后往另一个子树走,看看能不能更大。
            }
            else
            {
                // 否则肯定要异或。
                sum ^= (1 << i);
                rt = ch[rt][dig ^ 1];
            }
        }
        else if (ch[rt][dig])
            rt = ch[rt][dig];
        else
            break;
    }
    return res;
}
void solve()
{
    for (int i = 1; i <= cnt; i++)
        ch[i][0] = ch[i][1] = pos[i] = 0;
    cnt = 1;
    scanf("%d%d",&n,&k);
    int ans = inf;
    for (int i = 1,x; i <= n; i++)
    {
        scanf("%d",&x);
        ins(x,i);
        if (int tmp = que(x); tmp)
            ans = min(ans,i - tmp + 1);
    }
    if (k == 0)
        return (void)puts("1");
    if (ans == inf)
        puts("-1");
    else
        printf("%d\n",ans);
}
signed main()
{
    int t;
    scanf("%d",&t);
    while (t--)
        solve();
    return 0;
}

后日谈#

今天也是终于把 Java 的传参搞懂了。

4 月 15 日#

人不要太贪心。110110 pts 达成目标了就行了不要再想更高的分。

机房 rating 13.715.013.7 \to 15.0

T1#

考虑对于每个 scc 分别算贡献。因为只有 nn 条边,所以只会形成基环森林,不会形成那些乱七八糟形态的环套环。

  • 如果这个 scc 里一个没取,那么对于答案的贡献就是 1×2nsiz1 \times 2^{n - siz},因为有总共的一个 scc,而外面的怎么取都无所谓,方案数是 2nsiz2^{n - siz}
  • 否则枚举这个 scc 里取了 jj 个点,那么会形成 sizjsiz - j 个 scc,方案数是 (sizj)\binom{siz}{j},外面是方案数还是 2nsiz2^{n - siz},那么贡献就是 (sizj)×(sizj)×2nsiz(siz - j) \times \binom{siz}{j} \times 2^{n - siz}

因为每个 scc 的点数和是 nn,所以时间复杂度 O(n)O(n)

T2 & T3 & T4#

只打了个 T4 的暴力。据说 T4 是线性 dp?

jmr 的话我也信?

后日谈#

之前为了抵御我的颓废之心,我就一直在心里给自己说:你看你都菜成这样你还颓废?然而现在是不颓废了,但是这个想法似乎是被深渊侵蚀了一样,考好了依然有,导致考多少都不知足。这是个大问题,不仅让我晚上睡不着觉,重要的是脑子里的奖励机制废了。没有来自内心的动力,我就变成了傀儡。

4 月 16 日#

你甘心停留在 C 组吗?

T2#

我们发现 m1×106m \le 1 \times 10^6,这个 qq 我们直接不管他,我们预处理 ansians_i 表示询问 ii 时的答案,到时候直接 O(1)O(1) 查询就是了。

考虑如何求 ansians_i。发现对于每个 ii,如果独立的求显然不好做。考虑如何从 ansi1ans_{i - 1} 递推。我们发现这是一个神经的倍增。而上面的那一半和下面的那一半跳的步数是一样的。考虑求上面的那一半然后乘 22

如何求上面那一半呢?考虑何时 pp 会继续往下跳。发现,会造成影响的只有 a1,a3,a5,,a2n1a_1,a_3,a_5,\dots,a_{2^n - 1},因为只跳的到这些位置。显然只有 O(log2n)O(\log_2 n) 个位置。我们直接枚举当前跳到了 apa_p。何时它会继续往下跳,并造成 22 的贡献?(这里的贡献按 22 算了就不用执行上文的乘 22 了)

发现当且仅当 apia_p \le i 的时候会继续往下跳并造成贡献。ap[1,i1]a_p \in [1,i - 1] 的我们已经在 ansi1ans_{i - 1} 里算过了,所以我们只考虑 ap=ia_p = i。那么这个位置会造成的贡献就是 (i1p1)×(minp)×2\binom{i - 1}{p - 1} \times \binom{m - i}{n - p} \times 2。具体来说,因为 aa 是单调递增的,所以我们要把 i1i - 1 个数取 p1p - 1 个填进 [1,p1][1,p - 1] 这个范围内。后面同理。最后乘上一个 22 作为贡献,就可以加进 ansians_i 了。

T3#

我不想也不能。

这是一个无根树。考虑如果有根(也就是有一个固定的终点怎么做)。我们发现可以做 dp。对于每个节点 uu 记录一个 aua_u,里面存两个数,分别是到达 uu 的最快的选手和他所用的时间。转移是平凡的。

考虑没有一个固定的根怎么办。对于一个无根树,我们就可以做换根 dp。考虑如和 O(1)O(1) 换根。我们只用在 aua_u 里多记录几个值。分别是:第一个到达的与他的时间,第二个到达的与他的时间,第一个从哪里来。如何换根呢?我们考虑从 uu 转移 vvvsonuv \in son_u)。我们发现:如果最快的不来自 vv,我们就直接拿他来转移 vv,反之就拿次快的转移 vv。大力分讨即可。统计答案就离线,然后换根换到 tt 的时候把每个 ss 对应的得分记录一下就行。

别忘了还原。

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void dfs2(int u,int fa)
{
    for (auto [s,id] : que[u])
        ans[id] = buc[s];// 桶记录得分
    for (auto [v,w] : g[u])
    {
        if (v == fa)
            continue;
        Klee tu = a[u],tv = a[v];
        buc[a[u].fir.second]--,buc[a[v].fir.second]--;
        if (a[u].from == v)// 如果来自 v 就换成次快的
            a[u].fir = a[u].sec;
        pii tmp = a[u].fir;
        tmp.first += w;
        if (tmp < a[v].fir)// 转移
        {
            a[v].sec = a[v].fir;
            a[v].fir = tmp;
            a[v].from = u;
        }
        else if (tmp < a[v].sec)
            a[v].sec = tmp;
        buc[a[u].fir.second]++,buc[a[v].fir.second]++;
        dfs2(v,u);
        // 别忘了还原
        buc[a[u].fir.second]--,buc[a[v].fir.second]--;
        buc[tu.fir.second]++,buc[tv.fir.second]++;
        a[u] = tu,a[v] = tv;
    }
}

Election Queries G#

题读错了 + 码力太强。

具体就是:结论:设 viv_i 表示 ii 所获得的总票数。那么有结论:x,yx,y 能够当选,当且仅当 vx+vyvmaxv_x + v_y \ge vmaxvmaxvmax 表示 vv 的最大值。用 map 维护 vv 里出现的值。set 维护每个值出现的位置。因为 vv 只有 O(n)O(\sqrt{n}) 种取值,所以每次直接枚举每个出现次数不为 00 的每个值,然后双指针找出最大的 xy|x - y| 使得 vx+vyvmaxv_x + v_y \ge vmax

The Best Subsequence G#

n1×109n \le 1 \times 10^9,对于反转操作直接动开线段树。

考虑如何查询 [l,r][l,r] 区间内的最大字典序子序列。显然是找到一个最右的 pospos 使得 cntl,cnt+rcntkcnt_{l,cnt} + r - cnt \ge kcntl,cntcnt_{l,cnt} 表示 [l,pos][l,pos] 内的 11 的个数。这么做的原因是在前面尽量的取到更多更长的 11。二分找到最右的 pospos,维护“将其解释为二进制数时的值”线段树上维护即可。

后日谈#

明天继续。

A 掉 T1 就是。但是也别因为没切掉 T1 就摆烂。无论怎样都把暴力打完。

4 月 17 日#

没挂分,大大的

T1#

还是没太懂?

T2#

我不管,我的时间复杂度是正确的。

得分:7070 pts

T3#

也是有人改。

高斯消元?反正赛时推式子推了一半推不动了。

得分:4040 pts

T4#

爆搜获得了 2020 pts

tot:110110 pts

100100 了,大大的赢!

后日谈#

今天晚上回家写日记,究其原因是晚上听 yzp 讲 門松,讲的实在是清楚,导致最后一节晚自习没写日记。

明天去问 yza 吧。

4 月 18 日#

今天才切了 44 道题,太颓废了。

門松#

总之就是非常神奇。

昨天晚上是因为读错题了导致被硬控一晚上。今天早上是因为码力太强又被硬控一上午。

考虑对于每个偶数 ii,那么我们发现:{ai1,ai,ai+1}\{a_{i - 1},a_i,a_{i + 1} \} 形成的三元组状态一定是 {0,1}\{0,1\}。对于最终的四个状态,大力分讨即可。

这种大力分讨的题往往都很考验码力。但是我的码力又不行。

Ski Slope S#

开启今天的主线。

考虑把询问离线下来。然后把它和边混到一个数组里。用结构体记录三个值:{val,id,type}\{val,id,type\}。其中,valval 表示这条边的难度/这头牛的水平。idid 表示边的终点/询问的 ididtypetype 就表示类型。对于每个点 uu 记录一个 disudis_u 表示它到 11 的距离,cntucnt_u11 有多少条边大于当前的水平。我们从大到小枚举每一条边/每一个查询。

  • 如果这是一条边,那么就说明这条边的终点以及它的子树 cntcnt 都要加 +1+1,因为 ci10c_i \le 10,我们用 1010set 维护每个 cntu=[1,10]cnt_u = [1,10] 的每个点。每次直接暴力 dfs 更新。剪枝:如果点 cntu>10cnt_u > 10,那么说明 uu 和它的子树无论如何都不可能是答案,直接 return
  • 如果这是一个查询,那么我们就枚举 i=[0,cid]i = [0,c_{id}] 的每个值,在 setiset_i 里查找 disdis 的最大值所对应的点。

记得把 set 的比较器重载成 disx<disydis_x < dis_y。还有,当有一些边的 valval 和查询的 valval 一样,那么点一定比边优先。

Sequence Construction S#

考虑 kk 在二进制下的第 ii 位。如果其为 11,就说明有一个数 xxpopcount(x)\operatorname{popcount}(x)2i2^i,说明 xx22i12^{2^i} - 1其实也不一定,但是我们按照这个策略构造是对的。

我们枚举 kk 的每一个为 11 的位。将其记录进答案。并将其从 mm 中减去。如果出现 m<0m < 0,就说明无解,因为我们已经按照最小方案来构造了。

对于 mm 剩下的,如果它为偶数,就在答案序列的末尾添加两个 m2\frac{m}{2},这样既能保证和为 mm,又能保证异或和不受影响。如果它为奇数,就先加 {1,2}\{1,2\},再加 m32\frac{m - 3}{2},原因同上。

记得特判 mm 剩下的是 11

OohMoo Milk G#

显然有 John 和 Nhoj 都会去操作最大的那几个数,于是我们把题意转化为:John 先对于最大的 AA 个数加上 DD,然后,Nhoj 去减每个数。但是总次数不能超过 B×DB \times D,单个数减的次数不能超过 DD

考虑二分。设当前二分到了 midmidchk 时我们把尝试每个 i[1,A]i \in [1,A]aia_i,减到 midmid,但是因为有“单个数减的次数不能超过 DD”的限制,所以有些数减不到 midmid。如果总次数大于 B×DB \times D,那么就说明 midmid 小了,往大二分。反之如果有剩余的,就把每个还能减的减到 mid1mid - 1,并计算答案,然后继续往小二分,尝试更大的答案。

后日谈#

我要变强!