4月每日总结
4 月 1 日
我尽力,我无悔。
T1
对于那种只输入一两个数的题,首先不要去想正解,先猜半个小时结论看看猜不猜的出来。多半是猜的出来的
可是我写 T2 去了。
发现从 \(0\) 有 \(\frac{1}{n}\) 的概率一发入魂。而剩下 \(\frac{n - 1}{n}\) 的概率就是在 \([1,n - 1]\)。对于每个点都有 \(\frac{1}{n}\) 的概率赢,有 \(\frac{n - 2}{n}\) 的概率留在 \([1,n - 1]\),那么对于每个 \(i \in [1,n - 1]\) 都是一样的。期望是 \(1 + \frac{(n - 1)^2}{n}\)。
T2
赛时想法:设 \(dp_{i,j}\) 表示第 \(i\) 个点取值为 \(j\) 的方案数。显然有:\(dp_{i,j} = \sum\limits_{k = 1}^{a_i}dp_{i - 1,k} \times [k \neq j]\)。复杂度 \(O(nV^2)\),可以通过全局和优化到 \(O(nV)\),然后拿到 \(20\) 的好成绩。
我们发现这玩意可以简化成三个操作:区间取反,区间赋值,区间加。前面两个都是特化的区间乘。我们直接动开线段树,花了 2h 的时间获得的 \(60\) 的好成绩。
正解是这样的:正难则反。我们想要求恰好有 \(0\) 个非法点的情况数,我们就尝试求钦定有 \(i\) 个非法点的情况数 \(F(i)\),然后反演一下得到最终的答案。
考虑如何求 \(F(i)\)。设 \(dp_{i,j}\) 表示前 \(i\) 个数分 \(j\) 段的方案数。显然有 \(F(i) = dp_{i,n - i}\)。有显然的转移:\(dp_{i,j} = \sum\limits_{k = 1}^{i}dp_{k - 1,j - 1} \times \min\limits_{l = k}^{i}a_l\)。复杂度 \(O(n^3)\),可以拿到 \(0\) 分的好成绩。
我们发现我们不关心 \(j\) 的值,只关心它的奇偶性。我们就可以每次转移都乘一个 \(-1\),变成 \(dp_i = \sum\limits_{k = 1}^{i}-dp_{k - 1} \times \min\limits_{l = k}^{i}a_l\)。复杂度 \(O(n^2)\),可以获得 \(60\) 的好成绩。
最后用单调栈优化到 \(O(n)\) 即可通过。
T3
我们发现如果对于一个可行的方案,每个数都异或一个 \(x\),最终答案也可行。且总的异或和也异或了 \(y\)。所以我们不用考虑具体的方案,求出总方案数除以 \(2^k\) 就行。
考虑先填好第二行,方案数 \(2^k \times (2^k - 1) \times (2^k - 2) \times \cdots \times (2^k - n)\),然后钦定第一行有 \(i\) 个与第二行冲突,方案数为 \((2^k - i) \times (2^k - i - 1) \times (2^k - i - 2) \times \cdots \times (2^k - n + 1)\)。直接反演得到恰好有 \(0\) 个与之冲突的方案数。
后日谈
今天就是纯纯的数学题综合训练。于是开始预习复习概率与期望。
我尽力,我无悔。
4 月 2 日
不计代价。
不过我要是真的能做到 不计代价 的学习的话,我就不会那么菜了。
DFS Order 2
我们想到,一个子树内的 dfs 序肯定是连续的,我们考虑跑一个背包。设 \(dp_{u,i}\) 表示 \(u\) 在第 \(i\) 个被访问的方案数。我们先想总共有多少个 dfs 序。那么显然,如果有 \(tot_u\) 个子树,那么就有 \(tot_u !\) 个子树排列的方式。每个子树又有自己的排列方式,乘起来就是了。\(dp_{1,1}\) 就是总的排列方式数。初始化就完了。
考虑如何转移:我们想从 \(dp_{u,i} \to dp_{v,j}\)。我们发现,一次加的肯定是一整个子树。那么我们想:做背包!设 \(tmp_j\) 表示 两个点在 dfs 序上相距 \(j\) 的方案数。转移有显然的 \(dp_{v,j} \leftarrow dp_{u,i} + tmp_{j - i}\)。
但是我们发现:子树之间是不分顺序的,我们转移 \(v\) 的时候还得扣掉 \(v\) 的子树。我们不可能对于每个子树都做个不考虑它的背包,那么复杂度直奔 \(O(n^4)\),显然不可取。
我们想:效仿淀粉质!每次扣掉 \(v\) 的子树所做的贡献,转移完再加回来。我们在每个 \(u\) 的子树转移时设单独的 \(f_{i,j}\)。表示在 \(i\) 个子树内总共选了 \(j\) 个点的方案数。那么初始化有 \(f_{0,0} = 1\)。转移有显然的 \(f_{i,j} \leftarrow f_{i - 1,j - siz_v}\)。
那么我们又如何从 \(f \to tmp \to dp\) 呢?对于 \(f \to tmp\),有 \(tmp_{j + 1} = f_{i,j} \times i! \times (tot_u - i - 1)!\),三项分别表示 \(i\) 个子树内的方案数,\(i\) 个儿子排列的方案数,剩下儿子排列的方案数。
但是我们输出发现:比例正确,值错误。我们直接化到最简。我们发现比例正确但是值不正确的原因是有 \(tmp_i\),所以就是直接化到最简。
Core code:
\\ 预处理
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
原题也做不起吗?
我们考虑 ctj 设 \(dp_{i,j}\) 表示在 \(i\) 个困难之后花费了 \(j\) 分钟的方案数。那么有转移:
具体来讲,就是如果还有可能破纪录,就尝试能不能破纪录。反之直接重开。但是我们发现这玩意有后效性,直接高斯消元。高斯消元肯定是不可取的,毕竟有个 \(\min\) 在那里镇着。我们考虑二分 \(dp_{0,0}\)。如果最终的 \(dp_{0,0}\) 比 \(mid\) 小,就说明有些本该重开的我们选择了继续打,往小二分。反之往大二分。
糖果大战
高斯消元 yyds
游走
此游走非彼游走。
设总共有 \(cnt\) 条路径,总长为 \(len\)。那么直接 dfs 搜出每个以每个点为起点的路径数量和路程总长,一加一除就出来了。
如果这题没模我不炸了?
汉堡 Burger
题意简化:有一个 01 串,0 的数量和 1 的数量各占一半。且最后的两个是连续的 1,求出这么滴的概率。
正难则反。设 \(ans_i\) 为长度为 \(i\) 的不成立的概率。那么显然有 \(\displaystyle ans_n = \frac{\binom{n - 2}{\frac{n}{2} - 1}}{2^{n - 2}}\)。我们发现这题没模,不能预处理阶乘。考虑推式子。推得 \(ans_i = ans_{i - 2} \times \frac{i - 1}{i}\)。答案就是 \(1 - ans_n\)。
后日谈
我都写 Java 了我还管常数吗?
好想念小学时无忧无虑的周末啊。。。
Colourful Bottles
听 dpfs 讲的。
考虑 dp,设 \(dp_i\) 表示以 \(i\) 结尾,满足 \(k\) 连续的最小代价。考虑转移:
其中 \(sumw_i\) 表示 \(w\) 的前缀和。\(sumc_i\) 表示与 \(i\) 颜色相同的前缀和。第二个方程具体来说就是:删掉 \([j,i]\) 之间的所有数,但是同色的不能删,于是把它加回来。而我们不知道 \(j\) 的相同颜色的前驱是啥,于是直接减掉 \(sumc_j - w_j\)。\(j\) 要满足 \([j,i]\) 之间至少有 \(k\) 个同颜色的。
考虑优化。对于每个颜色开一个 \(min\) 数组表示 \(dp_j - sumw_{j - 1} - sumc_j + w_j\) 的最小值。又对于每个颜色开一个队列。当队列长度等于 \(k - 1\) 时,就把对头取出,更新 \(min\) 数组。然后转移 \(dp_i = \min\{dp_{i - 1} + w_i,min_{c_i} + sumw_i + sumc_i\}\)。转移完了扔进队列。
后日谈
明天的模拟赛和重庆八中联考。又可以丢 CW 的脸了。
每次一看到这个高达 \(\color{#ff0000}{16.7\%}\) 的得分率我就头大。明明打的对的题,总是因为一些莫名其妙的愿因挂分。要么是实现太优秀,常数爆炸,要么是没注意细节。
There's no time left.
4 月 8 日
今天是真的不颓。
T1
如果说有这样的 \(a = \{1,2\}\) 和 \(b = \{100,101\}\),显然我们不能先加 \(a_1\),而应该先加 \(a_2\)。而后又看到 \(x\) 可以为负,想到如果说将 \(a_i\) 加上 \(x\) 使得 \(a_i = b_i\) 且 \(a_i > a_{i + 1}\),我们就连一条 \(i + 1 \to i\) 的边。可以证明这是一个有向无环图。因此我们一定有用 \(\sum\limits_{i = 1}^{n}[a_i \ne b_i]\) 次的方法使得 \(a = b\)。
现在我们考虑使代价更小。考虑对于一个 \((a_i - b_i)^2\),要使它变小,我们考率分多次。显然等分最优。赛时想到了做背包 dp,复杂度 \(O(nm^2)\)。可以拿到 \(\color{#5ec05e}{50}\) 的好成绩。看这个绿色就知道确实很好。
正解是整一个大根堆。将每个 \((a_i - b_i)^2\) 多分一个的负贡献插入,比如初始就放每个 \((a_i - b_i)^2\) 从 \(1\) 次到 \(2\) 次的负贡献。取 \(m - \sum\limits_{i = 1}^{n}[a_i \ne b_i]\) 次就好。
没放代码是因为下次再做的时候希望能自己写出来。
T2
听 HD0X 讲的。
看到冒泡排序,想到逆序对。离线,从大到小枚举 \(i\),并枚举每个 \(x = i\) 的查询,计 \(u\) 表示 \(i\) 位置前面的比它大的数量,\(v\) 表示 \(i\) 位置后面的比它大的数量。让我们分类讨论一下(下文的 \(k\) 都指查询的 \(k\),\(id\) 指查询编号,\(pos_i\) 指 \(i\) 的原位置):
- \(k \le u\)
这样的话,\(i\) 前面的会在 \(k\) 次冒泡后跑到 \(i\) 后面去并把 \(i\) 往前挤,\(ans_{id} = pos_i - k\)。 - \(k \le u + v\)
从头开始考虑 \(k\) 次冒泡。每次冒泡,一定会有一个比 \(i\) 更大的数 \(P_j\) 被一个更大的数 \(P_{j + 1}\) 堵住,且位置比 \(P_{j + 1}\) 左一个。
我们掏个样例出来:\(\{4,3,5,1,2\}\)。我们想要查询 \(3\) 在第 \(2\) 次冒泡后的位置。
第 \(1\) 次冒泡:\(\{3,4,1,2,5\}\),\(4\) 被 \(5\) 堵住,位置 \(-1\)。
第 \(2\) 次冒泡:\(\{3,1,2,4,5\}\),\(3\) 被 \(4\) 堵住,位置 \(-1\)。
这样我们看到 \(3\) 在 \(1\) 的位置。这个 \(1\) 是由 \(5\) 最初的位置经过 \(2\) 次 \(-1\) 后得到的。
我们再掏一个样例:\(\{3,4,2,5,1\}\)。我们想要查询 \(2\) 在第 \(3\) 次冒泡后的位置。
第 \(1\) 次冒泡:\(\{3,2,4,1,5\}\),\(4\) 被 \(5\) 堵住,位置 \(-1\)。
第 \(2\) 次冒泡:\(\{2,3,1,4,5\}\),\(3\) 被 \(4\) 堵住,位置 \(-1\)。
第 \(3\) 次冒泡:\(\{2,1,3,4,5\}\),\(2\) 被 \(3\) 堵住,位置 \(-1\)。
\(2\) 在 \(1\) 的位置。这个 \(1\) 是由 \(5\) 最初的位置经过 \(3\) 次 \(-1\) 得到的。
我们发现,因为在这 \(k\) 次里会被堵 \(k\) 次,所以最初的位置(就是上文的 \(5\) 的最初的位置)一定是比 \(i\) 大的数按照原序列的顺序的第 \(k\) 个。而每次被堵会使得下标 \(-1\),所以答案就是比 \(i\) 大的数的第 \(k\) 个的位置减 \(k\)。 - \(k > u + v\)
显然这个时候 \(i\) 归位了,\(ans_{id} = n - u - v\)。
实现可以这样:因为我们从大到小枚举 \(i\),用线段树维护。每次算完 \(i\) 都将线段树 \(pos_i\) 的地方置为 \(1\)。\(u\) 就是 \([1,pos_i]\) 的区间和,\(v\) 就是 \([pos_i,n]\) 的区间和。“比 \(i\) 大的数按照原序列的顺序的第 \(k\) 个”可以通过线段树上二分找到第 \(k\) 个 \(i\)。因为只有 \(k > u\) 时才会询问第 \(k\) 个,所以第 \(k\) 个一定在 \(i\) 原位置的右边。
T3 & T4
补题速度还是太快了一点,没看。
后日谈
之前的考试挂分 \(\color{#ff0000}{50}\) 起步,今天只挂了 \(\color{#5ec05e}{10}\) 分,可喜可贺。
要是 T1 没有被背包误导的话就更好了。好歹有 50 分嘛
今天晚上有 div.3,打不打呢?
4 月 9 日
弄懂每一题。然而我太蠢了
Square Subsequences
谁能给我讲讲 bitset
优化 LCS 啊?
简而言之就是枚举中间的分界点,然后对于两边的子串跑 bitset
优化 LCS。记录最大的 LCS 长度与其对应的 bitset
。然后通过 bitset
反推 dp 数组,从而反推方案。时间复杂度 \(O(\frac{n^3}{\omega} + n^2)\),时限 \(2\) s,在 CF 神机上跑了 \(1.5\) s,换别的 OJ 还不一定跑的完。
然而我太蠢了没弄懂 bitset
优化 LCS。
Adjacent Delete
自从学了 C# 和 Java 以来,代码风格愈发面向对象了。与之而来的还有更大的常数。希望不要搞出在赛场上因为常数太大而 TLE 的事。
考虑没有相邻的限制如何做。显然是最大的 \(\frac{n}{2}\) 个减去最小的 \(\frac{n}{2}\) 个。现在考虑相邻的限制,这个上界也是可行的。我们把每个最大的 \(\frac{n}{2}\) 的标成 +
,最小的那几个标成 -
。显然无论如何都会有一对 +
和 -
相邻。
对于偶数 \(n\) 显然就是那个上界。对于奇数 \(n\),因为会留下一个,留下的这个会将 \(a\) 数组分成两半,而两边都会取完,所以两边的长度肯定都是偶数。所以留下的只能是 \(a_1,a_3,a_5,\dots\),对于两边,就是上文的上界。我们可以用两个 multiset
维护。一个小根的维护大的那几个数,大根的维护小的那那几个数。每次如果插入的 \(x\) 大于等于小根堆的堆顶,就插入小根堆。否则插入大根堆。并维护一个堆内和。
放下我的常数超大代码:
#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 因为鞋子没垫鞋垫导致他极其不舒服。然后今天也是十分颓废,只做了 \(3\) 题。
yzp 说他想回家,我的评价是:住校生是这样的。
不过住校生不开心还可以逃回家,走读生逃回家也没用,因为天天回家,对家已经无感了。不开心的时候只能畅想去到异世界。中二病
4 月 11 日
沟槽的树套树,沟槽的体考。
Two Permutations
我们发现,\(P_i\) 和 \(Q_i\) 是绑定的,不会分开。如果 \(P,Q\) 都无序十分的难做且 \(P,Q\) 的顺序是不变的。我们可以将 \(P\) 排序,然后考虑 \(Q\)。
我们建一个模型:将 \(P,Q\) 和与之对应的 \(R\) 看作上下两排。如果有 \(u < v\),那么就建一条 \(u \to v\) 的边。边可以是横着(\(P,Q\) 内部建边),也可以是竖着 (\(P \to Q\) 或 \(Q \to P\))。非法当且仅当出现了环。
我们考虑 \(Q\) 中的最大值 \(Q_u\)。
- 如果 \(u\) 是向上建边的起点,说明 \(S_u\) 及 \(S\) 从 \(u\) 开始的后缀都是 \(0\),往上建边。因为 \(P\) 已经按照从小到大排序了,后面的一定比前面的大。而 \(Q\) 这里又是最大值,后面一定比 \(Q_u\) 小,那么说明这段后缀都是向上建边。这有什么用?这就说明后面不会成环且 \(S\) 确定了,不用考虑了。
- 如果 \(u\) 是向下建边的终点,只能说明 \(S_u = 1\)。但是因为 \(Q_u\) 是最大值,所以它的边一定是往左右的,不会成环。
所以我们可以把题意转化成这样:有一个序列 \(Q\),每轮可以执行一次两种操作:
- 把 \(Q\) 的最大值 \(Q_u\) 及从 \(u\) 开始的后缀删掉。
- 只删掉 \(Q\) 的最大值 \(Q_u\)。
求出删完 \(Q\) 的方案数。
结论:答案就是 \(Q\) 的上升可空子序列数量。如何理解?我们只考虑操作 1。上升子序列就是一种操作 1 的操作序列。因为每次取走的是一段后缀且 \(Q_u\) 是最大值,所以下一个操作的就只能是 \(u\) 前面的比 \(Q_u\) 小的数。
那有人要问了?操作 2 你没考虑啊?其实也是考虑的。因为是子序列,所以里面可能夹杂着有空位,这些空位就是操作 2 的地方。因为操作 2 的 \(S\) 只能是 \(1\),方案数是 \(1\),所以不用计算。
树状数组优化 dp 即可。
Guess Sequence
我们对于 \(S_i \to T_i\) 建一条边。那么整个图一定是一棵树。如果不是一棵树那么就一定有点没被连边,这个点就可以乱取。
我们考虑对于一个点 \(u\),如果 \(b_u\) 乘上了 \(x\),那么 \(b_v\)(\(v\) 是与 \(u\) 相邻的点)为了乘积不变,就得乘上 \(\frac{1}{x}\)。经过一番推导,我们发现,点 \(v\) 的深度与 \(u\) 的奇偶性相同,\(b_u\) 乘上了 \(x\),\(b_v\) 也得乘上 \(x\),反之乘上 \(\frac{1}{x}\)。这样我们就把 \(n\) 个点分成了两个集合。深度为奇的成一个集合,深度为偶的成另一个集合。
我们发现,如果一个集合的数里有个共同的因数 \(x\),那么就可以把当前集合里的的乘上 \(\frac{1}{x}\),另一个集合里的就乘上 \(x\),可以构造一个新的 \(b\),显然不行。所以我们需要把 \(a\) 分成两个 \(\gcd\) 为 \(1\) 的集合,如果可行就是 Yes
,反之就是 No
。
后日谈
什么两米八三,我才跳了一米八三。
事情的起因是这样的:我们去体考,考立定跳远。前面的贾皓博第一跳只跳了 \(2.4\) 米多。我就在想:是不是成外的测量器具有问题啊?我之前的 \(2.6\) 米是不是不真啊?于是第一跳就全力。大概是腿太粗?力量太大?
跑完 \(1000\) 米直接燃尽了,整个下午都没法学习。
4 月 13 日
一起把周六的日记也写了吧。
考试爆零。机房 rating \(16.7 \to 13.7\),决心 \(-\inf\)。
T1
神秘小 dp,没改,明天改。
T2
想到了可持久化并查集,没想到和那个可持久化并查集一起的 Kruskal 重构树。
我这么菜不适合 Kruskal 这个英文名。
T3
主席书双 \(\log\) 做法还没调出来,单 \(\log\) 做法待会再说。
但是我拿测速代码测了 cwoi 的机子每秒大概 \(4.1 \times 10^8\),而 \(3 \times 10^5\) 的双 \(\log\) 也才 \(1.1 \times 10^8\),没准双 \(\log\) 卡卡常能过?
T4
神经分讨。
考虑 \([l,r]\) 区间内的最大值 \(a_x\) 出现在三个区间中的哪个位置。
- 中间
显然中间对于答案的贡献无论如何都是 \(a_x\),而两边的段随着长度的减小,贡献显然不会增加。那么最优方案就是 \(a_l + a_r + a_x\)。 - 左边
显然左边的代价无论如何都是 \(a_x\)。根据上文的结论,第二个区间的长度越小贡献越小。所以整个段的答案就是 \(a_x + \min\limits_{r' = x + 1}^{r - 1}\{a_{r'} + \max\limits_{i = r' + 1}^{r}\{a_i \}\}\)。我们离线,从 \(1 \to n\) 枚举每个 \(r\) 及其对应的询问。设 \(f_i\) 表示当前 \(r\) 所对应的 \(\min\limits_{r' = x + 1}^{r - 1}\{a_{r'} + \max\limits_{i = r' + 1}^{r}\{a_i \}\}\)。用单调栈 + 线段树维护。具体而言是这样的:维护一个单调递减的单调栈。如果当前的 \(a_r\) 把栈顶 \(stk_{top}\) 弹出了,由于 \(a_{stk_{top}}\) 是之前的 \(r' \in [stk_{top - 1},stk_{top})\) 的 \(\max\limits_{i = r' + 1}^{r}\{a_i \}\),现在被 \(a_r\) 替换了,所以在线段树上的 \([stk_{top - 1},stk_{top})\) 区间加 \(-a_{stk_{top}} + a_{r}\)。还要单独更新 \(r - 1\)。枚举每个 \(r_i\) 是当前枚举的 \(r\) 的询问, 那么答案就是 \(a_x + \min\limits_{i = x + 1}^{r - 1}\{f_i\}\),直接线段树区间查最小值就是了。 - 右边
把整个序列翻转,然后做左边一样的就行。
后日谈
感觉我一事无成。
4 月 14 日
想说点什么但是说不出来。
Transforming Pairs S
也是切上绿题了。
看到这玩意很像 \(\gcd\) 的求法,我们看看能不能往 \(\gcd\) 上面想。
口胡了一个做法:我们钦定 \(c < d\)。首先我们对 \(d\) 进行操作。将 \(d\) 减去 \(\lfloor \frac{d - b}{c} \rfloor \times c\)。这样可以保证 \(d \ge b\)。对 \(c\) 进行同样的操作,减去 \(\lfloor \frac{c - a}{d} \rfloor\),同样为了保证 \(c \ge a\)。
继续口胡的证明:感性理解,我们为了让 \(c\) 和 \(d\) 尽快的减成 \(a\) 和 \(b\),每次减的肯定要尽可能多。于是我们每次进行辗转相减,减到再减一次就会爆炸的程度,每次减得就尽可能多了。
Moo Decomposition G
Java 的常数还是太优秀了。
我们发现 \(L \le 1 \times 10^{18}\),直接把整个序列拎出来做肯定是不现实的。但是我们发现一个性质:每段的方案数相互独立。那么我们就可以把每段的方案数单独求出来然后快速幂了。
考虑如何求每段的方案数:从后往前枚举每个字符,我们设 \(cnt\) 表示当前遇到的 \(\texttt{O}\) 的个数。
- 遇到了个 \(\texttt{O}\)
显然是 \(cnt \leftarrow cnt + 1\)。 - 遇到了个 \(\texttt{M}\)
这个 \(\texttt{M}\) 可以从前面的 \(cnt\) 个 \(\texttt{O}\) 里面任意取 \(k\) 个,方案数乘上 \(\binom{cnt}{k}\)。然后因为用掉了 \(k\) 个 \(\texttt{O}\),我们需要把 \(cnt\) 减去 \(k\)。
Bessie's Function G
一道基环树板子题硬控我一下午。
我也并非人类。
我们发现这玩意可以套一个典中典的模型:对于每个 \(i\),连一条 \(i \to a_i\) 的边。因为总共有 \(n\) 条边,所以一定是一个基环树森林。
我们发现如果要修改 \(a_i\),一定是要把 \(a_i\) 修改成 \(i\),因为这样不仅可以使得 \(i\) 变得满足条件,还能使出边连接到 \(i\) 的点满足条件。但是为了方便 dfs,不可能建内向的基环树,肯定是建外向的基环树,就变成了让 \(a_i = i\) 可以使得 \(i\) 的儿子们也满足条件,我们就可以把题意转化一下:
对于每个点 \(u\),都可以花费代价 \(c_u\) 进行一次操作。操作必须要满足以下条件:如果你不操作那你的所有前驱就必须操作(在树上“前驱”表示儿子,在环上表示上一个节点。),反之你的前驱爱咋咋地。求最小代价。
我们这个题意都把方程给你写出来了。设 \(dp_{u,0/1}\) 表示 \(u\) 操作或者不操作。在树上就是:
在环上也类似。如何处理环上 dp 呢?我们考虑钦定 \(cir_0\) 的状态。比如我们钦定 \(cir_0\) 选,那么 \(dp_{cir_0,1}\) 就设成 \(\inf\),最后统计答案最后一个点 \(u\) 就爱选不选,\(tmp = \min\{dp_{u,0},dp_{u,1}\}\)。反之就是 \(dp_{cir_0,0} = \inf\),\(tmp = dp_{u,1}\)。最后 \(ans\) 加上两个 \(tmp\) 的最小值就是了。
以后有向图找环还是老老实实 dfs 吧。
Shorten the Array
都想到了 \(85\%\) 了为啥不再想一会呢?
#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 日
人不要太贪心。\(110\) pts 达成目标了就行了不要再想更高的分。
机房 rating \(13.7 \to 15.0\)。
T1
考虑对于每个 scc 分别算贡献。因为只有 \(n\) 条边,所以只会形成基环森林,不会形成那些乱七八糟形态的环套环。
- 如果这个 scc 里一个没取,那么对于答案的贡献就是 \(1 \times 2^{n - siz}\),因为有总共的一个 scc,而外面的怎么取都无所谓,方案数是 \(2^{n - siz}\)。
- 否则枚举这个 scc 里取了 \(j\) 个点,那么会形成 \(siz - j\) 个 scc,方案数是 \(\binom{siz}{j}\),外面是方案数还是 \(2^{n - siz}\),那么贡献就是 \((siz - j) \times \binom{siz}{j} \times 2^{n - siz}\)。
因为每个 scc 的点数和是 \(n\),所以时间复杂度 \(O(n)\)。
T2 & T3 & T4
只打了个 T4 的暴力。据说 T4 是线性 dp?
jmr 的话我也信?
后日谈
之前为了抵御我的颓废之心,我就一直在心里给自己说:你看你都菜成这样你还颓废?然而现在是不颓废了,但是这个想法似乎是被深渊侵蚀了一样,考好了依然有,导致考多少都不知足。这是个大问题,不仅让我晚上睡不着觉,重要的是脑子里的奖励机制废了。没有来自内心的动力,我就变成了傀儡。
4 月 16 日
你甘心停留在 C 组吗?
T2
我们发现 \(m \le 1 \times 10^6\),这个 \(q\) 我们直接不管他,我们预处理 \(ans_i\) 表示询问 \(i\) 时的答案,到时候直接 \(O(1)\) 查询就是了。
考虑如何求 \(ans_i\)。发现对于每个 \(i\),如果独立的求显然不好做。考虑如何从 \(ans_{i - 1}\) 递推。我们发现这是一个神经的倍增。而上面的那一半和下面的那一半跳的步数是一样的。考虑求上面的那一半然后乘 \(2\)。
如何求上面那一半呢?考虑何时 \(p\) 会继续往下跳。发现,会造成影响的只有 \(a_1,a_3,a_5,\dots,a_{2^n - 1}\),因为只跳的到这些位置。显然只有 \(O(\log_2 n)\) 个位置。我们直接枚举当前跳到了 \(a_p\)。何时它会继续往下跳,并造成 \(2\) 的贡献?(这里的贡献按 \(2\) 算了就不用执行上文的乘 \(2\) 了)
发现当且仅当 \(a_p \le i\) 的时候会继续往下跳并造成贡献。\(a_p \in [1,i - 1]\) 的我们已经在 \(ans_{i - 1}\) 里算过了,所以我们只考虑 \(a_p = i\)。那么这个位置会造成的贡献就是 \(\binom{i - 1}{p - 1} \times \binom{m - i}{n - p} \times 2\)。具体来说,因为 \(a\) 是单调递增的,所以我们要把 \(i - 1\) 个数取 \(p - 1\) 个填进 \([1,p - 1]\) 这个范围内。后面同理。最后乘上一个 \(2\) 作为贡献,就可以加进 \(ans_i\) 了。
T3
我不想也不能。
这是一个无根树。考虑如果有根(也就是有一个固定的终点怎么做)。我们发现可以做 dp。对于每个节点 \(u\) 记录一个 \(a_u\),里面存两个数,分别是到达 \(u\) 的最快的选手和他所用的时间。转移是平凡的。
考虑没有一个固定的根怎么办。对于一个无根树,我们就可以做换根 dp。考虑如和 \(O(1)\) 换根。我们只用在 \(a_u\) 里多记录几个值。分别是:第一个到达的与他的时间,第二个到达的与他的时间,第一个从哪里来。如何换根呢?我们考虑从 \(u\) 转移 \(v\)(\(v \in son_u\))。我们发现:如果最快的不来自 \(v\),我们就直接拿他来转移 \(v\),反之就拿次快的转移 \(v\)。大力分讨即可。统计答案就离线,然后换根换到 \(t\) 的时候把每个 \(s\) 对应的得分记录一下就行。
别忘了还原。
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
题读错了 + 码力太强。
具体就是:结论:设 \(v_i\) 表示 \(i\) 所获得的总票数。那么有结论:\(x,y\) 能够当选,当且仅当 \(v_x + v_y \ge vmax\),\(vmax\) 表示 \(v\) 的最大值。用 map
维护 \(v\) 里出现的值。set
维护每个值出现的位置。因为 \(v\) 只有 \(O(\sqrt{n})\) 种取值,所以每次直接枚举每个出现次数不为 \(0\) 的每个值,然后双指针找出最大的 \(|x - y|\) 使得 \(v_x + v_y \ge vmax\)。
The Best Subsequence G
\(n \le 1 \times 10^9\),对于反转操作直接动开线段树。
考虑如何查询 \([l,r]\) 区间内的最大字典序子序列。显然是找到一个最右的 \(pos\) 使得 \(cnt_{l,cnt} + r - cnt \ge k\)。\(cnt_{l,cnt}\) 表示 \([l,pos]\) 内的 \(1\) 的个数。这么做的原因是在前面尽量的取到更多更长的 \(1\)。二分找到最右的 \(pos\),维护“将其解释为二进制数时的值”线段树上维护即可。
后日谈
明天继续。
A 掉 T1 就是赢。但是也别因为没切掉 T1 就摆烂。无论怎样都把暴力打完。
4 月 17 日
没挂分,大大的赢
T1
还是没太懂?
T2
我不管,我的时间复杂度是正确的。
得分:\(70\) pts
T3
也是有人改。
高斯消元?反正赛时推式子推了一半推不动了。
得分:\(40\) pts
T4
爆搜获得了 \(20\) pts
tot:\(110\) pts
上 \(100\) 了,大大的赢!
后日谈
今天晚上回家写日记,究其原因是晚上听 yzp 讲 門松,讲的实在是清楚,导致最后一节晚自习没写日记。
明天去问 yza 吧。
4 月 18 日
今天才切了 \(4\) 道题,太颓废了。
門松
总之就是非常神奇。
昨天晚上是因为读错题了导致被硬控一晚上。今天早上是因为码力太强又被硬控一上午。
考虑对于每个偶数 \(i\),那么我们发现:\(\{a_{i - 1},a_i,a_{i + 1} \}\) 形成的三元组状态一定是 \(\{0,1\}\)。对于最终的四个状态,大力分讨即可。
这种大力分讨的题往往都很考验码力。但是我的码力又不行。
Ski Slope S
开启今天的主线。
考虑把询问离线下来。然后把它和边混到一个数组里。用结构体记录三个值:\(\{val,id,type\}\)。其中,\(val\) 表示这条边的难度/这头牛的水平。\(id\) 表示边的终点/询问的 \(id\)。\(type\) 就表示类型。对于每个点 \(u\) 记录一个 \(dis_u\) 表示它到 \(1\) 的距离,\(cnt_u\) 到 \(1\) 有多少条边大于当前的水平。我们从大到小枚举每一条边/每一个查询。
- 如果这是一条边,那么就说明这条边的终点以及它的子树 \(cnt\) 都要加 \(+1\),因为 \(c_i \le 10\),我们用 \(10\) 个
set
维护每个 \(cnt_u = [1,10]\) 的每个点。每次直接暴力 dfs 更新。剪枝:如果点 \(cnt_u > 10\),那么说明 \(u\) 和它的子树无论如何都不可能是答案,直接return
。 - 如果这是一个查询,那么我们就枚举 \(i = [0,c_{id}]\) 的每个值,在 \(set_i\) 里查找 \(dis\) 的最大值所对应的点。
记得把 set
的比较器重载成 \(dis_x < dis_y\)。还有,当有一些边的 \(val\) 和查询的 \(val\) 一样,那么点一定比边优先。
Sequence Construction S
考虑 \(k\) 在二进制下的第 \(i\) 位。如果其为 \(1\),就说明有一个数 \(x\) 的 \(\operatorname{popcount}(x)\) 为 \(2^i\),说明 \(x\) 为 \(2^{2^i} - 1\)。其实也不一定,但是我们按照这个策略构造是对的。
我们枚举 \(k\) 的每一个为 \(1\) 的位。将其记录进答案。并将其从 \(m\) 中减去。如果出现 \(m < 0\),就说明无解,因为我们已经按照最小方案来构造了。
对于 \(m\) 剩下的,如果它为偶数,就在答案序列的末尾添加两个 \(\frac{m}{2}\),这样既能保证和为 \(m\),又能保证异或和不受影响。如果它为奇数,就先加 \(\{1,2\}\),再加 \(\frac{m - 3}{2}\),原因同上。
记得特判 \(m\) 剩下的是 \(1\)。
OohMoo Milk G
显然有 John 和 Nhoj 都会去操作最大的那几个数,于是我们把题意转化为:John 先对于最大的 \(A\) 个数加上 \(D\),然后,Nhoj 去减每个数。但是总次数不能超过 \(B \times D\),单个数减的次数不能超过 \(D\)。
考虑二分。设当前二分到了 \(mid\),chk
时我们把尝试每个 \(i \in [1,A]\) 的 \(a_i\),减到 \(mid\),但是因为有“单个数减的次数不能超过 \(D\)”的限制,所以有些数减不到 \(mid\)。如果总次数大于 \(B \times D\),那么就说明 \(mid\) 小了,往大二分。反之如果有剩余的,就把每个还能减的减到 \(mid - 1\),并计算答案,然后继续往小二分,尝试更大的答案。
后日谈
我要变强!
4 月 20 日
迟早要完
T3
将柿子转化一下,变成 \(\sum|T_i|\times |C_i - f|\),发现这玩意相当于:对于每个 \(i\),有 \(T_i|\) 个 \(C_i\)。求所有数的中位数。直接上离线将 \(C_i\) 离散化。然后权值线段树。具体实现:树上节点存两个信息:\(sumt,sumc\),分别表示该区间的 \(T\) 之和和 \(C\) 之和。中位数直接查询 \(\lceil\frac{tot}{2}\rceil\) 的数就行。设中位数为 \(med\)。那么小于中位数的贡献是 \(med - C\),大于的是 \(C - med\)。我们可以查询 \(i \in [1,med]\) 的 \(C_i\) 之和和 \(i \in (med,n]\) 的 \(C_i\) 之和。这样就能做到 \(O(n\log n)\) 的复杂度。
后日谈
但是我们赛时都没做出来的题,deepseek 想了 \(8\) min 就过了?
吃枣药丸。
4 月 21 日
十分颓废。
Hot Potato
看到:\(n \le 20\),直接状压。一开始设的状态是:\(dp_{s,i}\) 表示已经走过的点的状态是 \(s\),\(i\) 的失败概率。但是我们发现:有了 \(s\),已经可以确定 \(i\) 是否失败了,而且这个状态转移也很麻烦。考虑换一种状态。
我们设 \(dp_{s,i}\) 表示走过的点状态为 \(s\),当前在 \(i\) 的概率。考虑如何转移。我们设当前 \(i\) 有 \(cnt\) 个备选。循环每个与 \(i\) 有边的点 \(j\),那么显然转移有:\(dp_{s \cup \{j\},j} \leftarrow dp_{s,i} \times \frac{1}{cnt}\)。如果 \(cnt = 0\),就把 \(ans_i\) 加上 \(dp_{s,i}\),表示这种状态会输,并不进行转移。
时间复杂度 \(O(n^2 2^n)\),实现不能太劣。
Crashing Competition Computer
考虑设 \(f_i\) 表示不保存打 \(i\) 个字符的期望时间。那么显然有 \(f_i = f_{i - 1} + 1 + p \times f_i\)。转换一下就是 \(f_i = \frac{f_{i - 1} + 1 + p \times r}{1 - p}\)。
设 \(dp_i\) 表示保存打 \(i\) 个字符的期望时间。初始化就是 \(dp_i = f_i\)。转移有 \(dp_i = \min_{j = 0}^{i}\{dp_j + t + f_{i - j}\}\)。表示在 \(j\) 的位置保存并打 \(i - j\) 个字符。
最后答案就是 \(dp_n + t\)。
Dice Grid
唐题。
直接从 \([n,n]\) 开始 bfs 看看能不能给正方体的每个面都染色就行了。
后日谈
今天晚上有久违的 div.2。切掉 T3 就是半赢。切掉 T4 就是赢!
4 月 22 日
大输特输。
Median Splits
如果有 \(a_i \le k\),就把 \(a_i\) 置为 \(1\),反之置为 \(-1\)。这样,如果一段区间 \([l,r]\) 的中位数 \(\le k\) 的充要条件就是 \(\sum_{i = l}^{r}a_i \ge 0\)。如何使三段区间的中位数的中位数 \(\le k\) 呢?显然是有两个区间的中位数 \(\le k\) 就行。直接大力分讨哪两个区间的中位数 \(\le k\)。
- 左边的和中间的区间
维护一个前缀和 \(sum_i\)。如果 \(sum_i \ge 0\),就看看有没有小于等于 \(sum_i\) 的非负数出现在 \(sum\) 里比 \(i\) 靠前的地方 \(j\)。如果有,那么 \([1,j],[j + 1,i],[i + 1,n]\) 就是一组解。直接上权值线段树维护。 - 右边的和中间的区间
reverse
一下 \(a\),然后同上。 - 左边的和右边的区间
在上面的两次计算中,记录一个 \(x,y\) 分别表示两次计算中 \(sum_i\) 最早非负的位置。如果 \(x + y < n\),就说明两个区间中间还能再夹一个区间,就是可行的。
Local Construction
大力分讨 + 差分约束。
好像神明知道我的码力不行,天天给我发大力分讨题。
Soldier Game
考虑枚举最小值。设 \(dp_{l,r,i,j}\) 表示在区间 \([l,r]\) 中,\(l\) 是否和 \(l - 1\) 配对,\(r\) 是否和 \(r + 1\) 配对的最小的最大值。用线段树维护。转移就是 \(dp_{i,j} = \min\{\max(dp_{l,mid,i,k},dp_{mid + 1,r,k,j})\}\),表示在诸多 \(\max(dp_{l,mid,i,k},dp_{mid + 1,r,k,j})\) 里取最小值。用线段树维护。将每种配对方式的战力从小到大排序。每次记录完答案就把值小于当前值的地方置为 \(\inf\)。
小 A 的卡牌游戏
考虑只有两种牌怎么做。把牌按照 \(b_i - a_i\) 排序,然后按照排完序的顺序,先取 \(B\) 个再取 \(A\) 个。想想多加一个怎么做。
我们设 \(dp_{i,j}\) 表示在前 \(i\) 个 3pick 里取了 \(j\) 个 C。转移是显然的。如果我们不选 c,就按照上面的贪心策略转移:如果 \(i - j \le B\),就取 B,反之取 A。
后日谈
今天中午午觉没睡好。下午听 AC 自动机烧烤过度,还去打了个球。晚上回机房直接获得 debuff:疲惫;效果:智力 \(- \inf\),理解能力 \(- \inf\),记忆力 \(- \inf\)。然后晚上硬生生的把 KMP 和 AC 自动机搞懂了。
AC 自动机学习笔记 is coming soon.
4 月 23 日
少年啊成为神话吧!
T1
愿天堂没有 27
。
T2
愿天堂没有码力题。
T3
想到根号分治。我也不知道怎么想到的。
发现我们暴力也具有优越性。因为它的时间复杂度是 \(O(n)\) 的,与 \(m\) 无关。那么在 \(m\) 较大的时候,暴力就是很优的。我们可以把 \(m \ge \sqrt{n}\) 的时候跑暴力。因为至多只有 \(\sqrt{n}\) 个询问拿来跑暴力,所以暴力这块的时间复杂度就是 \(O(n \sqrt{n})\)。
考虑 \(m < \sqrt{n}\) 如何做。考虑维护一个 \(f_{i,j}\) 表示左端点 \(\in [1,x_i]\),右端点 \(\in [x_j,n]\) 的区间数。考虑如何用 \(f\) 计算 \(ans\)。我们可以用容斥原理。首先,考虑一个只覆盖 \([x_i,x_i]\) 的区间。那么只有 \(f_{i,i}\) 能计算它。\(ans\) 加上 \(f_{i,i}\)。继续考虑只覆盖 \([x_i,x_{i + 1}]\) 的区间。那么它会被 \(f_{i,i},f_{i + 1,i + 1}\) 计算它。但是它覆盖了两个点。所以它不能被计算,\(ans\) 加上 \(-2 f_{i,i + 1}\)。经过一番推导,我们发现容斥系数长这个样子:\(1,-2,2,-2,2,\dots\)。我们就可以计算了。如何维护 \(f\)?把每个 \(f_{i,j}\) 看成一次询问。将每次询问离线,有 \(O(n)\) 个询问。用树状数组维护即可,预处理时间复杂度 \(O(n\log n)\),每次询问 \(O(m^2 \log n)\)。我们发现,这是一个二次函数形式的复杂度,要卡满 \(m\) 就得是最大值 \(\sqrt{n}\),但是这样就最多只有 \(\sqrt{n}\) 个询问这样,总的复杂度就是 \(O(n\log n \sqrt{n})\)
照理来说 \(O(n\sqrt{n})\) 都跑不过 \(5 \times 10^5\),但是这玩意带 \(\log\) 都跑得过,数据是不是有点水啊?
后日谈
砸场没砸过,我的自尊心和明日香的自尊心一样,碎了。
昨天晚上的 CF,我居然还没有掉下青,说明青的实力十分的不行,得往蓝冲了。
4 月 25 日
回答我。
e-Government
建出 \(fail\) 树,当我们在字典树上查询文本串时,当前点 \(rt\) 到 \(0\) 在 \(fail\) 树上的路径上的点都代表模式串的一次全新出现。那么这个问题就变成了一个单点修改,链上查询问题。把每次的单点更新影响扩大到整个子树,那么就变成了一个区间修改,单点查询的问题,直接上树状数组维护。
You Are Given Some Strings...
对于文本串和每个模式串,正着建一个 ACAM,把它们 reverse
一下再建一个 ACAM,然后记录文本串的每个前缀的后缀被模式串匹配了多少次,最后枚举分界点就行了。
后日谈
没啥可谈的。
4 月 26 日
不知不觉 4 月都要结束了呢。
要学会爱自己呀。
数数
神经数位 DP + AC 自动机。
数位 DP 经典套路:记录 \(pos\) 表示当前到了多少位,还有 \(lim\) 和 \(zero\) 两个 bool
分别表示有没有顶到上限,有没有前导零。但是这还要记录一个 \(rt\) 表示在字典图上到了哪个点。对于每个点 \(u\) 记录一个 \(ed_u\) 表示 \(u\) 是不是一个模式串的结尾。如果当前要去到的根 \(ed\) 是 \(1\),就不进行转移,否则就 \(rt\) 跳并进行转移。
需要注意的是:在 \(fail\) 树上,如果 \(ed_u\) 为 \(1\),那么 \(u\) 的整个子树的 \(ed\) 也得是 \(1\),在 bfs
处理 \(fail\) 的时候顺便处理了就是。
后日谈
愿此行,终抵群星。
崩铁两周年的主题曲是真的好听啊。
4 月 29 日
昨天晚上打 CF 去了,没写日记。
CF1400 的题都做不对的 OIer 有什么用?
Wonderful Lightbulbs
我们有结论:设最初的灯泡在 \((x,y)\),那么第 \(x\) 行一定有奇数个灯泡在亮,它所在的对角线也一定有奇数个灯泡在亮。
Wonderful Teddy Bears
首先我们把字符映射一下:\(\texttt{B} \to 0,\texttt{P} \to 1\)。考虑 \(4\) 种操作:
- \(\{0,1,0\} \to \{0,0,1\}\)
- \(\{1,0,0\} \to \{0,0,1\}\)
- \(\{1,0,1\} \to \{0,1,1\}\)
- \(\{1,1,0\} \to \{0,1,1\}\)
让我们从逆序对的角度考虑:对于第 \(1,3\) 种操作,只会减少 \(1\) 个逆序对,对于第 \(2,4\) 个操作,会减少 \(2\) 个逆序对。显然我们应该贪心的先做 \(2,4\) 操作直到做不动为止。
我们看何时 \(2,4\) 操作做不动了。当且仅当 \(a\) 变成形如 \(\{0,0,0,\dots ,0,1,0,1,\dots,0,1,0,1,1,1,\dots,1\}\),也就是说,\(a\) 可以被分成一个全为 \(0\) 的前缀和一个全为 \(1\) 的后缀,和一个 \(0,1\) 交替出现的字串。
因为 \(0,1\) 交替出现,我们考虑记录 \(a\) 表示总的 \(0\) 的个数,\(b\) 表示在偶数位置上的 \(0\) 的个数。依然考虑这 \(4\) 种操作:\(2,4\) 操作不会改变 \(b\),\(1,3\) 操作会使 \(b\) 变化(增加或减少)\(1\)。考虑 \(b\) 的最终形态是什么。因为 \(0\) 会全聚在 \(a\) 的前面,所以 \(b\) 的目标是 \(\lfloor \frac{a}{2} \rfloor\)。于是我们可以这样:设总共有 \(x\) 逆序对,先做 \(|b - \lfloor \frac{a}{2} \rfloor|\) 次 \(1,3\) 操作。\(x\) 减少 \(|b - \lfloor \frac{a}{2} \rfloor|\),然后再做 \(\frac{x}{2}\) 次 \(2,4\) 操作。
Unpleasant Strings
看到 \(\sum|t_i| \le 1 \times 10^6\),看能不能搞出一种 \(O(\sum|t_i|)\) 的算法。
我们想到预处理 \(s\) 串上每个下标 \(i\) 的 \(ans_i\),表示以 \(i\) 结尾的子序列最少还要加多少个字符才能不再成为悦耳字符串。
考虑如何预处理。\(i\) 从后往前枚举,记录一个 \(lst_i\) 表示字符 \(i\) 最后出现的地方。我们枚举每个字符。有显然的转移:\(ans_i = \min\{ans_{lst_i} + 1\}\),表示在这个点后面接每个字符的方案。\(lst\) 初始全为 \(n + 1\),且 \(ans_{n + 1} = 0\)。再记录一个 \(son_{i,j}\),每次都把 \(lst\) 给 memcpy
进 \(son_i\)。转移完了再更新 \(lst\)。最后把 \(lst\) memcpy
进 \(son_0\)。
考虑如何处理询问。就像字典树一样,我们记录一个 \(rt\) 表示当前跳到了 \(rt\) 这个点。枚举 \(t\) 里每个字符 \(t_i\),每次跳到 \(son_{rt,t_i}\)。如果说跳着跳着直接跳到 \(n + 1\) 了,就说明它本就不是悦耳字符串,\(ans\) 置为 \(0\) 并 break
。反之如果跳到了 \(t\) 的结尾了都没跳出去,就说明这是一个悦耳字符串,答案即为 \(ans_{rt}\)。
code:
#include<bits/extc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn = 1e6 + 5;
int n,k,q;
char t[maxn],s[maxn];
int son[maxn][26],ans[maxn];
signed main()
{
scanf("%d%d%s",&n,&k,t + 1);
fill(son[0],son[0] + k,n + 1);// 这里直接把 son[0] 当 lst 用了
fill(ans + 1,ans + n + 1,inf);// ans 初始化 inf
for (int i = n; i >= 1; i--)
{
memcpy(son[i],son[0],sizeof(int) * k);// 把 lst memcpy 进 son[i]
for (int j = 0; j < k; j++)
ans[i] = min(ans[i],ans[son[0][j]] + 1);// 转移
son[0][t[i] - 'a'] = i;// 更新 lst
}
scanf("%d",&q);
while (q--)
{
scanf("%s",s + 1);
int rt = 0,len = strlen(s + 1);
for (int i = 1; i <= len && rt <= n; i++)
rt = son[rt][s[i] - 'a'];// 每次跳,如果跳出去了就说明本就不是悦耳字符串。
printf("%d\n",ans[rt]);
}
return 0;
}
Bermuda Triangle
式子推推推推到厌倦
后日谈
去开拓把,即使前方道路未卜。
本文来自博客园,使用 CC BY-NC-SA 4.0 协议。
作者:伊埃斯,转载请注明原文链接:https://www.cnblogs.com/Eous/p/18804960