某金牌训练营Day11
A
首先我们有一类转移是继承最大的一个子串
然后第二类转移就变成了
对于一个k级字符串,开头和结尾有一个k-1级字符串,然后我们要找到这样一个满足条件k-1级字符串
考虑rightpos集合,那么我们一定有k-1级的字符串rightpos完全包含第k级字符串的
具体怎么做呢?
考虑在树上dp,对于u的父亲f,查找这个串是否合法
即查找这个串的所有rightpos中有没有一个是父亲节点代表的串,这个可以直接树上dfs实现,复杂度还是
或者说是可以卡到,举例bbbbbbbba(重复2e4次)

你会发现每个点的查询都会尽量先查小的,然后再去查询大的,但是因为有些没有
但是仔细一想,我们只需要使用每个串的一个判断即可,因为不管从哪个位置开始,串都是一样的
如果不合法,相当于u要继承f的答案
然后我们就能发现这里有坑...我们要查找的是最高的祖先f同时其等级为k-1,因为串越短越可能出现
所以要用并查集缩点
使用线段树合并维护rightpos即可,可持久化形式
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 7;
int n;
char s[MAXN];
int rt[MAXN], rc, f[MAXN];
const int MAXT = 1e7 + 7;
namespace seg {
#define mid ((l+r)>>1)
int ls[MAXT], rs[MAXT], T;
inline void mdf(int &k, int l, int r, int P) {
if(!k)k = ++T;
if(l == r)return ;
if(P <= mid)mdf(ls[k], l, mid, P);
else mdf(rs[k], mid + 1, r, P);
return ;
}
inline int qry(int k, int l, int r, int L, int R) {//查询区间L,R是否有数
if(!k)return 0;
if(L <= l && r <= R)
return 1;
if(R <= mid)return qry(ls[k], l, mid, L, R);
else if(L > mid)return qry(rs[k], mid + 1, r, L, R);
return qry(ls[k], l, mid, L, R) | qry(rs[k], mid + 1, r, L, R);
}
inline int dfs(int k, int l, int r, int p, int g, int o) {
if(!k)return 0;
if(l == r)
return qry(p, 1, n, l - g + o, l - 1);
return dfs(ls[k], l, mid, p, g, o) == 1 ? 1 : dfs(rs[k], mid + 1, r, p, g, o);
}
inline int merge(int x, int y) {
if(!x || !y)return x | y;
int k = ++T;
ls[k] = merge(ls[x], ls[y]);
rs[k] = merge(rs[x], rs[y]);
return k;
}
#undef mid
}
int T, lst, ch[MAXN][26], len[MAXN], fa[MAXN], c[MAXN], a[MAXN], dp[MAXN];
inline void ins(int c) {
int p = lst, np = lst = ++T;
int q, nq;
len[np] = len[p] + 1;
seg::mdf(rt[np], 1, n, rc);//这个节点挂去
for(; p && !ch[p][c]; p = fa[p])ch[p][c] = np;
if(!p) {
fa[np] = 1;
return ;
}
q = ch[p][c];
if(len[q] == len[p] + 1) {
fa[np] = q;
return ;
}
nq = ++T;
len[nq] = len[p] + 1;
fa[nq] = fa[q];
memcpy(ch[nq], ch[q], sizeof(ch[q]));
fa[np] = fa[q] = nq;
for(; ch[p][c] == q; p = fa[p])ch[p][c] = nq;
}
inline void solve() {
for(int i = 1; i <= T; ++i)c[len[i]]++;
for(int i = 1; i <= n; ++i)c[i] += c[i - 1];
for(int i = 1; i <= T; ++i)a[c[len[i]]--] = i;
for(int i = T; i >= 1; --i) {
int u = a[i];
rt[fa[u]] = seg::merge(rt[fa[u]], rt[u]);//直接合并!!!
}
int ans = 0;
for(int i = 2; i <= T; ++i) {
int u = a[i];
dp[u] = dp[fa[u]];
if(fa[u] == 1) {
dp[u] = 1;
f[u] = u;//操操操
continue;
}
if(seg::dfs(rt[u], 1, n, rt[f[fa[u]]], len[u], len[f[fa[u]]]))dp[u] = dp[fa[u]] + 1, f[u] = u;//操操操
else f[u] = f[fa[u]];
ans = max(ans, dp[u]);
}
printf("%d\n", ans);
return;
}
int main() {
freopen("level2.in", "r", stdin);
freopen("level1.out", "w", stdout);
scanf("%s", s + 1);
n = strlen(s + 1);
lst = T = 1;
for(int i = 1; i <= n; ++i) {
rc = i;
ins(s[i] - 'a');
}
solve();
return 0;
}
B
我不能操作一整个连通块,然后最小化删掉每个点之后最大连通块大小
max为最大的大小,max'为次大大小,min为最小的大小
每次操作一定是从最大的拿出来放到最小的,操作后能成为答案的只有max,max',min
不难发现我们要分出最接近同时不超过大小的子树
但是这实际上是萎的,我们考虑二分答案mid,范围在,然后想让最小子树和最大子树都不超过mid,要去除的子树大小就是
如果存在一个子树在这个范围内就合法了,现在就想看这个子树存不存在
- 最大连通块在下面
直接预处理即可
线段树合并得到子树内的值域线段树,然后每个都查询一次即可,复杂度
当然也可以主席树,相当于二维数点,查询区间有没有一个数
最大连通块在上面
坏了啊
- 这条链的信息都会被修改,2. 都会减少这个点子树的大小
同时这个点子树内的信息都不能使用
建立值域线段树,然后dsu on tree去除掉子树信息就能解决掉子树内信息不能用
但是这条链坏掉了...
我们dfs的过程中栈中一直保留了这条链,因此我们在整体的值域线段树扣掉很简单
但是这一部分要单独查询,容易发现相当于全局修改标记,即查询,只有这样的才是合法的!
因此我们可以开一个set类似的结构维护dfs栈内信息就可以支持了
老师说树状数组,因为要支持区间,也行吧
因为本质上我们要最小化的过程在于找到最接近的一个数,因此找前驱后继也是可得
C
不难发现我们对于所有素数幂都能有唯一的对应形式,比如a+b=算a次+算b次,同时都不取也有唯一的对应形式
约数和是
推式子
不难发现设,这个东西也是积性函数,可以线性筛出来然后直接暴力统计
预处理方法就是考虑那个约数的计算式子
现在每次多一个质因数x,k会多2
突然发现我们能够预处理:
这个式子预处理方法就是枚举一个d,然后枚举d的倍数,然后我们发现和上一个d的倍数只差项,直接前缀和即可
然后回答询问直接暴力即可
为什么不能整除分块?因为我们k的取值对于内层是有影响的,而如果想合并起来,要才行
这样还是不太行
考虑设定一个阈值k,对于小于阈值的我们直接暴力处理,
然后对于大于阈值的,我们可以发现不会很多,可以直接暴力处理
这个怎么处理?考虑枚举i,j,然后对于一个l,你会发现这个l可以暴力计算
这样的复杂度在枚举i,j时是再乘上一个k就是
紧接着我们会发现对于一次询问枚举一下i,j的取值就能单次
钦定,可以得到的复杂度做法
期望100pts
实际上我们对于后面的部分整除分块可以做到
但是这玩意还是k取最快??应该是太小了吧,如果Q,N同阶就是最快了吧
看看代码吧,没啥细节...但是你要想到整除分块后面一部分挺难的
//O(Qk+Q\sqrt n+ n^2/k)
#include<bits/stdc++.h>
#define vi vector<int>
#define ll long long
#define pb push_back
#define pii pair<int,int>
using namespace std;
const int P = (1 << 30);
const int MAXN = 2e5 + 7;
const int N = 2e5;
const int B = 3200;
int n, m;
int d[MAXN], d2[MAXN], pri[MAXN], tot, num[MAXN], isp[MAXN], q, mu[MAXN], ans;
vi mp[N / B + 1][N / B + 1];//冲
vi v[MAXN];
//死亡开数组方法:
//pair映射vi,命秒没
inline int add(int x, int y) {
x += y;
if(x >= P)x -= P;
return x;
}
inline void init() {
d[1] = 1;
mu[1] = 1;
d2[1] = 1;
for(int i = 2; i <= N; ++i) {
if(!isp[i]) {
pri[++tot] = i;
d[i] = 2;
d2[i] = 3;
num[i] = 1;//!!!2
mu[i] = -1;
}
for(int j = 1; j <= tot && i * pri[j] <= N; ++j) {
isp[i * pri[j]] = 1;
if(i % pri[j] == 0) {
d[i * pri[j]] = d[i] / (num[i] + 1) * (num[i] + 2);
d2[i * pri[j]] = d2[i] / (2 * num[i] + 1) * (2 * num[i] + 3);
num[i * pri[j]] = num[i] + 1;
break;
}
d[i * pri[j]] = d[i] * d[pri[j]];
d2[i * pri[j]] = d2[i] * d2[pri[j]];
num[i * pri[j]] = 1;
mu[i * pri[j]] = -mu[i];
}
}
for(int p = 1; p <= N; ++p) {
mu[p] = (mu[p] + P) % P;
v[p].pb(1ll * d2[p] * d[1] % P);
for(int n = 2; n * p <= N; n ++) {
v[p].pb(add(v[p][v[p].size() - 1], 1ll * d2[n * p] * d[n] % P));
}
}
}
inline void init2() {
for(int i = 1; i <= N / B; ++i) {
for(int j = i; j <= N / B; ++j) {//这个也要-1...
ll lst = 1ll * mu[B + 1] * v[B + 1][i - 1] % P * v[B + 1][j - 1] % P, as = 0;
mp[j][i].pb(lst);
for(int d = B + 2; d * j <= N; ++d) {
as = (lst + 1ll * mu[d] * v[d][i - 1] % P * v[d][j - 1] % P) % P;
mp[j][i].pb(as);
lst = as;
}
}
}
return;
}
inline void solve1() {
for(int i = 1; i <= min(m, B); ++i) {
ans = add(ans, 1ll * v[i][n / i - 1] * mu[i] % P * v[i][m / i - 1] % P); //直接得到!!
}
}
inline void solve2() {
for(int r = B + 1, l = B + 1; l <= m; l = r + 1) {
r = min(n / (n / l), m / (m / l));
if(l - 2 - B == -1)ans = add(ans, mp[(n / l)][(m / l)][r - B - 1]);
else ans = add(ans, (mp[(n / l)][(m / l)][r - B - 1] - mp[(n / l)][(m / l)][l - 2 - B] + P) % P);
}
return ;
}
signed main() {
freopen("math.in", "r", stdin);
freopen("math.out", "w", stdout);
init();
init2();
scanf("%d", &q);
while(q-- > 0) {
scanf("%d%d", &n, &m);
ans = 0;
if(n < m)swap(n, m);
solve1();
if(m <= B) {
printf("%d\n", ans);
continue;
}
solve2();
printf("%d\n", ans);
}
return 0;
}
莫队做法
这个和EI那个组合数区间和很像,用莫队规划路径然后移动

懂了,我k取