做题笔记 III

\(1 \sim 100\) 的题目在 做题笔记 II

\(\texttt{Le0**an}\):我写了四篇做题笔记、一篇生成函数详解和一篇模拟赛复盘了!

\(\texttt{xl****13}\):我写了零篇做题笔记了!!!111


\(101 \sim 125\)

\(\color{blue}(101)\) ARC172E Last 9 Digits

难度 \(^*2400\)数论

抽象题。

有一个结论,对于 \(k \ge 2\),若 $x \bmod 2 \ne0 $ 且 \(x \bmod 5 \ne 0\)\(n^n \equiv x \pmod {10^k}\) 一定有唯一解。可以考虑打表验证。

考虑如果 \(n^n \equiv x \pmod {10^k}\),则一定 \(n^n \equiv x \pmod {10^{k-1}}\)。那么我们从 \(k=2\) 开始,先暴力枚举找到 \(n^n \equiv x \pmod {10^{2}}\)\(n\),然后考虑 \(n,n+10^k,n+2\cdot 10^k,\ldots,n+9\cdot10^k\) 一定是 \(n^n \equiv x \pmod {10^{k+1}}\) 的所有可能解,暴力检验即可。时间复杂度 \(\mathcal O(\log^2 x)\),带 \(10\) 倍常数。

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 09:42:18
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,x,pw[11],d;
int qp(int a,int b,int mod){
    int r = 1;
    for(;b;b >>= 1,a = 1ll * a * a % mod) if(b & 1) r = 1ll * r * a % mod;
    return r;
}void los(){
    cin >> x; int ans = -1;
    for(int i = 0;i <= 99;i ++) if(qp(i,i,100) == x % 100) ans = i;
    for(int i = 3;i <= 9;i ++)
        for(int j = 0;j < 10;j ++)
            if(d = ans + j * pw[i - 1],qp(d,d,pw[i]) == x % pw[i]) ans = d;
    cout << ans << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    pw[0] = 1;
    for(int i = 1;i <= 9;i ++) pw[i] = pw[i - 1] * 10;
    for(cin >> T;T --;) los();
}

\(\color{blue}(102)\) ABC313G Redistribution of Piles

难度 \(^*2800\)数学

首先先进行一次 \(2\) 操作再进行 \(1\) 操作没有意义,我们的操作序列形如 \(11\ldots 122\ldots 2\)

考虑对 \(a\) 升序排序,如果当前所有数字都大于 \(0\),那你先进行一次 \(1\) 再进行一次 \(2\) 也没有意义。设当前 \(a_{i-1}\) 已经等于 \(0\),则背包里有 \(s = a_{i-1}(n-i+1)+\sum \limits_{j=1}^{i-1} a_j\) 个数字。设对 \(a_i\) 操作 \(k\) 次,则和会增加 \(k(n-i+1)\),容易发现这些操作后序列互不相同。也就是说,我们会得到 \(\sum \limits_{k=1}^{a_i-a_{i-1}} \lfloor\dfrac{s+k(n-i+1)}{n} \rfloor\),可以用 atcoder::floor_sum 求解。时间复杂度 \(\mathcal O(n \log m)\)。有一些实现细节。

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 10:54:20
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5,mod = 998244353;
using namespace std;
#include <atcoder/all>
using Z = atcoder::modint998244353;
int T,n,a[N];
void los(){
    cin >> n; Z ans = 0; ll sum = 0;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    sort(a+1,a+n+1); ans = a[1] + 1;
    for(int i = 1;i < n;i ++)
        sum += a[i],
        ans += atcoder::floor_sum(a[i + 1] - a[i] + 1,n,n - i,sum + 1ll * a[i] * (n - i)),
        ans -= atcoder::floor_sum(1,n,n - i,sum + 1ll * a[i] * (n - i)),
        ans += a[i + 1] - a[i];
    cout << ans.val();
}signed main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(103)\) AT_toyota2023spring_final_c Count Dividing XOR

难度 \(^*2100\)暴力;数学

容易发现 \(a \oplus b \ge b - a,a \oplus b | b - a\),也即 \(a \oplus b = b - a\)。又因为 \(a \oplus b | a,a \oplus b | b\),那么枚举 \(b - a\)\(a,b\) 都是 \(b -a\) 的倍数,由调和级数,\(a\) 的数量是 \(\mathcal O(n \log n)\) 级别的,可以通过。

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 14:39:34
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
ll l,r,T;
void los(){
    cin >> l >> r; ll ans = 0;
    for(int i = 1;i <= r - l + 1;i ++)
        for(ll j = 1ll * (l + i - 1) / i * i + i;j <= r;j += i)
            ans += (j ^ (j - i)) == i;
    cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(104)\) CF1007C Guess two numbers

难度 \(^*2900\)构造;交互;倍增

很厉害的倍增。容易发现若答案是 \((a,b)\),你询问了 \((x,y)\),如果得到 \(1\) 说明 \(a > x\),得到 \(2\) 说明 \(b > y\),将点放在平面上,可以看作得到 \(1,2\) 可以排除一个矩形。

问题是得到 \(3\) 我们只能排除右上角,会得到 L 形。我们考虑倍增。设当前位置为 \((p,q)\),偏移量为 \((x,y)(x,y \ge 1)\),每次如果得到 \(1,2\) 则对应维度加倍,否则两维减半。这样的倍增在遇到 \(3\) 时很容易回退。显然这样的询问次数是 \(\mathcal O(\log n)\) 级别,虽然常数较大但是不会超过 \(8\) 倍。

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 15:46:12
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
ll T,n,x = 1,y = 1,p,q,fk;
void los(){
    for(cin >> n;;){
        cout << p + x << " " << q + y << endl,cin >> fk;
        if(!fk) return ;
        if(fk == 1) p += x,x = min(x * 2,n - p);
        if(fk == 2) q += y,y = min(y * 2,n - q);
        if(fk == 3) x /= 2,y /= 2,x = max(x,1ll),y = max(y,1ll);
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(105)\) CF949E Binary Cards

难度 \(^*2600\)搜索

观察一些基本性质。

  • 性质一:我们不会同时选 \(k,k\) 或者 \(k,-k\)
    对于第一种情况,选择 \(k,2k\) 更优;对于第二种情况,选择 \(2k,-k\) 更优。

从小到大枚举 \(k\),那么我们考虑到 \(2^k\) 时所有数字都是 \(2^{k}\) 的倍数。如果它不是 \(2^{k+1}\) 的倍数,我们就要进行操作。注意到操作完后所有数字都是 \(2^{k+1}\) 的倍数,那么全都除 \(2\) 后去重,数组规模减半,递归处理。由于只会递归 \(\log\) 层,去重后每一层的所有元素数量和是 \(\mathcal O(a_i)\) 级别,总复杂度 \(\mathcal O(a_i \log a_i)\)

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 16:24:08
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n; vector<int> a,ans,res;
void dfs(vector<int> a,int d){
    sort(a.begin(),a.end()),a.erase(unique(a.begin(),a.end()),a.end());
    if(a.size() == 1 && !a[0]){
        if(res.size() > ans.size()) res = ans;
        return;
    }int fg = 0;
    if(d > 20) return ;
    for(int i : a) if(i % 2) fg = 1;
    if(!fg){for(int &i : a) i /= 2; dfs(a,d + 1);}
    else{
        vector<int> b = a;
        for(int &i : a) i = (i + abs(i % 2)) >> 1; 
        ans.push_back(-(1 << d)),dfs(a,d + 1);
        ans.pop_back(),ans.push_back((1 << d));
        for(int &i : b) i = (i - abs(i % 2)) >> 1; dfs(b,d + 1);
        ans.pop_back();
    } 
}
void los(){
    for(int i = 1;i <= 30;i ++) res.push_back(i);
    cin >> n; a.resize(n);for(int &i : a) cin >> i; dfs(a,0);
    cout << res.size() << "\n";
    for(int i : res) cout << i << " ";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(106)\) ABC317G Rearranging

难度 \(^*2600\)网络流;二分图;图论建模

赛时写的贪心没过,赛后六个月终于来补题了()

建一张二分图,左边是行,右边是数。考虑初始状态下,对于 \(a_{i,j}\),把左边 \(i\) 和右边 \(a_{i,j}\) 连边跑最大匹配。我们把得到的这个匹配放在第一列,即如果第 \(i\) 行匹配 \(j\),那么 \(ans_{i,1} = j\)。显然这样建图对于每一行只会选择一个数字 \(j\)。然后我们把这些 \(a_{i,j}\) 删掉,重新跑最大匹配,确定第二列,以此类推跑 \(m\) 遍即可得到答案。时间复杂度 \(\mathcal O(nm^2 \sqrt n)\)

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 18:39:35
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5,M = 1e2+5;
using namespace std;
int T,n,m,cnt,head[N],vis[N],dep[N],x,s,t,w,cur[N],ans[M][M];
struct edge{int to,nxt,w;}e[N*4]; multiset<int> st[M];
void adde(int u,int v,int w){e[++cnt] = {v,head[u],w},head[u] = cnt;}
void add(int u,int v,int w){adde(u,v,w),adde(v,u,0);}
bool bfs(){
    memset(dep,-1,sizeof(dep)); queue<int> q; q.push(s),dep[s] = 0;
    while(q.size()){
        int u = q.front(); q.pop();
        for(int i = head[u],v;v = e[i].to,i;i = e[i].nxt) if(e[i].w && dep[v] == -1) 
            dep[v] = dep[u] + 1,q.push(v);
    }return dep[t] != -1;
}int dfs(int u,int f){
    if(u == t) return f; int a = 0;
    for(int &i = cur[u],v;v = e[i].to,i;i = e[i].nxt){
        if(e[i].w && dep[v] == dep[u] + 1)
            if(w = dfs(v,min(f-a,e[i].w)),e[i].w -= w,e[i^1].w += w,a += w,a == f) return f;
    }if(!a) dep[u] = -1; return a;
}void dinic(){for(;bfs();dfs(s,1e9)) for(int i = s;i <= t;i ++) cur[i] = head[i];}
void los(){
    cin >> n >> m,s = 0,t = 2 * n + 1;
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) cin >> x,st[i].insert(x);
    for(int k = 1;k <= m;k ++){
        for(int i = s;i <= t;i ++) cur[i] = head[i] = 0; cnt = 1;
        for(int i = 1;i <= n;i ++) for(int j : st[i]) add(i,j + n,1);
        for(int i = 1;i <= n;i ++) add(s,i,1),add(i + n,t,1);
        dinic();
        for(int u = 1;u <= n;u ++)  for(int i = head[u],v;v = e[i].to,i;i = e[i].nxt)
            if(!e[i].w && v > n && v <= n * 2) st[u].erase(st[u].find(v - n)),ans[u][k] = v - n;
    }cout << "Yes\n";
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) cout << ans[i][j] << " \n"[j == m];
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(107)\) ABC287G Balance Update Query

难度 \(^*1800\)贪心;线段树

这也能蓝是吧。

考虑我们会选价值前 \(x\) 大的物品,线段树维护每个价值物品的数量,\(a_i\) 的值域较大需要动态开点。

时间复杂度 \(\mathcal O(n \log V)\)

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 19:27:18
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,ls[N*40],rs[N*40],t[N*40],tot,x,y,rt,op,m,bl[N],val[N]; ll sm[N*40];
void upd(int &s,int l,int r,int x,int k){
    if(!s) s = ++tot; if(l == r) return t[s] += k,sm[s] += 1ll * l * k,void();
    int mid = (l + r) / 2;
    if(x <= mid) upd(ls[s],l,mid,x,k); else upd(rs[s],mid+1,r,x,k); 
    t[s] = t[ls[s]] + t[rs[s]],sm[s] = sm[ls[s]] + sm[rs[s]];
}ll qry(int s,int l,int r,int k){
    if(l == r) return 1ll * k * l;
    int mid = (l + r) / 2,tot = t[rs[s]];
    if(k <= tot) return qry(rs[s],mid+1,r,k);
    else return sm[rs[s]] + qry(ls[s],l,mid,k - tot);
}void los(){
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> x >> y,bl[i] = x,val[i] = y,upd(rt,0,1e9,x,y);
    for(cin >> m;m --;){
        if(cin >> op >> x,op == 1) cin >> y,upd(rt,0,1e9,bl[x],-val[x]),upd(rt,0,1e9,bl[x] = y,val[x]);
        else if(op == 2) cin >> y,upd(rt,0,1e9,bl[x],y - val[x]),val[x] = y;
        else cout << (t[1] >= x ? qry(1,0,1e9,x) : -1) << "\n";
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(108)\) ABC300G P-smooth number

难度 \(^*1500\)搜索

事实上直接爆搜就能过。

预处理质数和其次幂,从后往前枚举每一个质数的次数,当 \(p=2\) 是直接返回 \(\log\) 的值。注意到极端数据答案只有 \(2\times 10^9\),去除掉 \(p=2\) 的部分情况数量会更少。\(4\) 秒还是能跑过的。

\(3.1\) 秒喜提最劣解 /cf

代码
/**
*    author: sunkuangzheng
*    created: 20.02.2024 19:59:49
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
using tril = __int128;
const int N = 5e5+5;
using namespace std;
int T,P; ll n,ans; vector<int> pr; tril pw[105][42];
void dfs(int d,ll pd){
    if(!d) return ans += __lg(n / pd) + 1,void();
    for(int i = 0;i <= 60;i ++){
        if(pw[pr[d]][i] * pd > n) break;
        dfs(d - 1,pd * pw[pr[d]][i]);
    }
}void los(){
    cin >> n >> P;
    auto ck = [&](int x){for(int i = 2;i * i <= x;i ++) if(x % i == 0) return 0; return 1;};
    for(int i = 2;i <= P;i ++) if(ck(i)) pr.push_back(i);
    for(int i = 1;i <= 100;i ++){
        pw[i][0] = 1;
        for(int j = 1;j <= 40;j ++) 
            if(pw[i][j-1] * i >= 1e18) break; else pw[i][j] = pw[i][j-1] * i;
    }dfs(pr.size() - 1,1),cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(109)\) ABC299G Minimum Permutation

难度 \(^*2200\)线段树;树状数组;构造;贪心

看到字典序,考虑逐位确定。每次要找到一个区间 \([l,r]\) 满足 \(l-1\) 是选择的上一个数字的位置,\(r\) 最最大的下标满足 \(r \sim n\) 出现了所有没有选的数字,并选择其中没选过的最小的数字。容易发现 \(r\) 随着选的数字数量增加会单调右移。那么用线段树维护区间 \(\min\) 即可,选过的数字就赋成 \(\inf\)。时间复杂度 \(\mathcal O(n \log n)\)

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
#define int long long
const int N = 2e5+5;
int t[N*4],tg[N*4],pos[N*4],a[N],n,m,vis[N]; vector<int> g[N];
void pp(int s){t[s] = min(t[s*2],t[s*2+1]),pos[s] = t[s*2] <= t[s*2+1] ? pos[s*2] : pos[s*2+1];}
void cg(int s,int k){t[s] += k,tg[s] += k;}
void pd(int s){cg(s*2,tg[s]),cg(s*2+1,tg[s]),tg[s] = 0;}
void build(int s,int l,int r){
    if(l == r) return t[s] = a[l],pos[s] = l,void();
    int mid = (l + r) / 2; build(s*2,l,mid),build(s*2+1,mid+1,r),pp(s);
}void upd(int s,int l,int r,int ql,int qr,int k){
    if(ql <= l && r <= qr) return cg(s,k);
    int mid = (l + r) / 2; pd(s);
    if(ql <= mid) upd(s*2,l,mid,ql,qr,k);
    if(qr > mid) upd(s*2+1,mid+1,r,ql,qr,k);
    pp(s);
}pair<int,int> qry(int s,int l,int r,int ql,int qr){
    if(ql <= l && r <= qr) return {t[s],pos[s]};
    int mid = (l + r) / 2; pd(s);
    if(qr <= mid) return qry(s*2,l,mid,ql,qr);
    if(ql > mid) return qry(s*2+1,mid+1,r,ql,qr);
    return min(qry(s*2,l,mid,ql,qr),qry(s*2+1,mid+1,r,ql,qr));
}struct fktr{
    int t[N],re;
    void upd(int x,int p){for(;x;x -= x & -x) t[x] += p;}
    int qry(int x){for(re = 0;x <= n;x += x & -x) re += t[x]; return re;}
}tr;
void printarr(int s,int t,int *a){
    cerr << "{";
    for(int i = s;i <= t;i ++) cerr << a[i] << ",}"[i == t];
}signed main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> m; 
    for(int i = 1;i <= n;i ++) cin >> a[i],g[a[i]].push_back(i);
    build(1,1,n);
    for(int i = n;i >= 1;i --) if(!vis[a[i]]) tr.upd(i,1),vis[a[i]] = 1;
    int r = 1,l = 1;
    while(m --){
        while(r <= n && tr.qry(r) > m) r ++;
        auto [x,y] = qry(1,1,n,l,r - 1);
        cout << x << " ";
        for(auto i : g[x]) upd(1,1,n,i,i,n);
        tr.upd(g[x].back(),-1);
        upd(1,1,n,1,y,n),l = y + 1;
    }
}

\(\color{blue}(110)\) ABC257G Prefix Concatenation

难度 \(^*2100\)字符串;KMP

\(s\)\(t\) 拼起来,每次要做的是选一个前缀的 border。容易证明每次选最长的一定不劣,KMP 求出来即可。时间复杂度 \(\mathcal O(|s| + |t|)\)

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
const int N = 1.1e6+5; string s,t; int a[N],n,m;
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> s >> t,m = s.size(),s += '#',s += t,n = s.size(),s = " " + s;
    int j = 0;
    for(int i = 2;i <= n;i ++){
        while(j && s[j + 1] != s[i]) j = a[j];
        if(s[j + 1] == s[i]) j ++; a[i] = j;
    }int d = n,ans = 0;
    while(ans <= n + 5 && d >= m + 2) ans ++,d -= a[d];
    if(ans >= n + 5) cout << -1; else cout << ans;
}

\(\color{blue}(111)\) ABC253G Swap Many Times

难度 \(^*1800\)模拟

考虑如果连续操作了 \((x,x+1),(x,x+2),\ldots,(x,n)\),相当于对 \([x,n]\) 进行一次循环移位。那么只需要找到 \(l,r\) 对应的 \(x\),两端的暴力做,中间的相当于循环移位了 \(x_r - x_l\) 次。模拟即可,时间复杂度 \(\mathcal O(n)\)

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
const int N = 2e5+5; ll l,r,op[N]; int n,a[N],b[N];
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> l >> r;
    for(int i = 1;i <= n;i ++) a[i] = i,op[i] = op[i - 1] + n - i;
    int k = lower_bound(op+1,op+n+1,l) - op,p = upper_bound(op+1,op+n+1,r) - op - 1,ti = p - k;
    if(ti == -1){
        for(int i = l - op[p];i <= r - op[p];i ++) swap(a[k],a[i + k]);
        for(int i = 1;i <= n;i ++) cout << a[i] << " ";
        return 0;
    }for(int i = n - (op[k] - l + 1) + 1;i <= n;i ++) swap(a[i],a[k]);
    for(int i = 1;i <= n;i ++) b[i] = a[i];
    for(int i = 1;i <= ti;i ++) b[i + k] = a[n - i + 1];
    for(int i = ti + 1;i + k <= n;i ++) b[i + k] = a[k + i - ti];
    for(int i = 1;i <= r - op[p];i ++) swap(b[p + 1],b[i + p + 1]);
    for(int i = 1;i <= n;i ++) cout << b[i] << " ";
}

\(\color{blue}(112)\) ABC293G Triple Index

难度 \(^*2300\)莫队

莫队板子,增减 \(x\) 会让已经存在的两个 \(x\) 的二元组增减贡献,即变化量是 \(\dfrac{ct_x \cdot (ct_x - 1)}{2}\)。时间复杂度 \(\mathcal O(n \sqrt n)\)

代码
/**
*    author: sunkuangzheng
*    created: [] cnblogs
**/
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
const int N = 2e5+5,B = 500;
struct qu{int l,r,id;}q[N];
int n,Q,a[N],ct[N]; ll res,ans[N];
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> Q;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    for(int i = 1;i <= Q;i ++) cin >> q[i].l >> q[i].r,q[i].id = i;
    sort(q+1,q+Q+1,[&](qu x,qu y){return x.l / B == y.l / B ? x.r < y.r : x.l < y.l;});
    int L = 1,R = 0;
    auto ins = [&](int R){res += 1ll * ct[a[R]] * (ct[a[R]] - 1) / 2,ct[a[R]] ++;};
    auto del = [&](int R){ct[a[R]] --,res -= 1ll * ct[a[R]] * (ct[a[R]] - 1) / 2;};
    for(int i = 1;i <= Q;i ++){
        auto [l,r,id] = q[i];
        while(R < r) ins(++ R);
        while(L > l) ins(-- L);
        while(R > r) del(R --);
        while(L < l) del(L ++);
        ans[id] = res;
    }for(int i = 1;i <= Q;i ++) cout << ans[i] << "\n";
}

\(\color{blue}(113)\) ABC272G Yet Another mod M

难度 \(^*2200\)摩尔投票;随机化

考虑两个数 \(x,y\) 如果有 \(x \equiv y \pmod m\),则 \(m | abs(x - y)\)。绝对众数通常可以随机化,我们每次随机两个下标 \(x,y\),暴力检验 \(x - y\) 的所有约数,找绝对众数可以采用摩尔投票法。考虑这样的正确率:\(x,y\) 同时是答案的绝对众数的概率是 \(\dfrac{1}{4}\),那么随 \(30\) 次正确率就有 \(99.98\%\),在没有多测的情况下可以接受。时间复杂度 \(\mathcal O(knd(a_i))\)\(k = 30\) 是随机化次数。当然这东西显然跑不满。

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
const int N = 2e5+5;
mt19937 rd(time(0));
int a[N],n,b[N];set<int> s;
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n;
    auto ck = [&](int k){
        if(k <= 2) return (bool)0;
        int ct = 0,p = 0;
        for(int i = 1;i <= n;i ++){
            b[i] = a[i] % k;
            if(b[i] == p) ct ++;
            if(!ct) p = b[i];
            if(b[i] != p) ct --;
        }ct = 0;
        for(int i = 1;i <= n;i ++) ct += (b[i] == p);
        return ct > n / 2;
    };
    for(int i = 1;i <= n;i ++) cin >> a[i];
    for(int _ = 1;_ <= 30;_ ++){
        int p = rd() % n + 1,q = rd() % n + 1,d = abs(a[p] - a[q]);
        for(int i = 1;i * i <= d;i ++){
            if(d % i) continue;
            if(!s.count(i)) if(s.insert(i),ck(i)) return cout << i,0;
            if(!s.count(d / i)) if(s.insert(d / i),ck(d / i)) return cout << d / i,0;
        }
    }cout << -1;
}

\(\color{blue}(114)\) ABC308G Minimum Xor Pair Query

难度 \(^*2400\)01 trie;线段树分治

每次插入 \(x\),求序列里选择 \(x,y\)\(x \oplus y\)\(\min\)

这个显然是 01 trie 板子,但是带删最小值不好维护,那就线段树分治。时间复杂度 \(\mathcal O(n \log n \log V)\)

代码
/**
*    author: sunkuangzheng
*    created: 21.02.2024 13:31:07
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 3e5+5;
using namespace std;
int T,n,tot,ch[N*31][2],siz[N*31],ans = (1ll << 31) - 1,id[N],op,x; 
vector<int> t[N*4]; map<int,vector<int>> mp;
void ins(int x,int p){
    int s = 0;
    for(int i = 30;i >= 0;i --){
        int k = (x >> i) & 1;
        if(!ch[s][k]) ch[s][k] = ++tot;
        s = ch[s][k],siz[s] += p;
    }
}int qry(int x){
    int ans = 0,s = 0;
    for(int i = 30;i >= 0;i --){
        int k = (x >> i) & 1;
        if(siz[ch[s][k]]) s = ch[s][k];
        else s = ch[s][!k],ans += (1 << i);
    }return ans;
}void upd(int s,int l,int r,int ql,int qr,int k){
    if(ql <= l && r <= qr) return t[s].push_back(k),void();
    int mid = (l + r) / 2; 
    if(ql <= mid) upd(s*2,l,mid,ql,qr,k); if(qr > mid) upd(s*2+1,mid+1,r,ql,qr,k);
}void dfs(int s,int l,int r){
    int p = ans,mid = (l + r) / 2; 
    for(int x : t[s]) ans = min(ans,qry(x)),ins(x,1);
    if(l == r){if(id[l]) cout << ans << "\n";}
    else dfs(s*2,l,mid),dfs(s*2+1,mid+1,r);
    for(int x : t[s]) ins(x,-1); ans = p;
}void los(){
    cin >> n;
    for(int i = 1;i <= n;i ++){
        cin >> op;
        if(op == 1) cin >> x,mp[x].push_back(i);
        if(op == 2) cin >> x,upd(1,1,n,mp[x].back(),i-1,x),mp[x].pop_back();
        if(op == 3) id[i] = 1;
    }for(auto [x,y] : mp) for(int i : y) upd(1,1,n,i,n,x);
    dfs(1,1,n);
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(115)\) ABC242G Range Pairing Query

难度 \(^*2300\)莫队

莫队可以 \(\mathcal O(q \sqrt n)\) 直接做,不知道有没有 poly log 做法。

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
const int N = 1e5+5,B = 1000;
struct qu{int l,r,id;}q[N*10];
int n,Q,a[N],ct[N],res,ans[N*10];
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> a[i]; cin >> Q;
    for(int i = 1;i <= Q;i ++) cin >> q[i].l >> q[i].r,q[i].id = i;
    sort(q+1,q+Q+1,[&](qu x,qu y){return x.l / B == y.l / B ? x.r < y.r : x.l < y.l;});
    int L = 1,R = 0;
    auto ins = [&](int R){res += !((++ct[a[R]]) & 1);};
    auto del = [&](int R){res -= !((ct[a[R]]--) & 1);};
    for(int i = 1;i <= Q;i ++){
        auto [l,r,id] = q[i];
        while(R < r) ins(++ R);
        while(L > l) ins(-- L);
        while(R > r) del(R --);
        while(L < l) del(L ++);
        ans[id] = res;
    }for(int i = 1;i <= Q;i ++) cout << ans[i] << "\n";
}

\(\color{blue}(116)\) ABC285G Tatami

难度 \(^*2500\)网络流;二分图;图论建模

发现难点在于怎么处理 \(\texttt{?}\),如果直接在原图上跑最大流,由于一个 \(\texttt{?}\) 和自己匹配和和另一个 \(2\) 匹配收益相同,我们难以判断是否有解。考虑拆点,左部 \(2,\texttt{?}\) 向右边 \(2,\texttt{?}\) 连边,左边 \(1\) 向右边 \(1\) 连边跑最大流,时间复杂度 \(\mathcal O(nm \sqrt {nm})\)

代码
/**
*    author: sunkuangzheng
*    created: 21.02.2024 14:55:13
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
#include <atcoder/all>
using namespace atcoder;
using ll = long long;
const int N = 3e2+5;
#define debug(x,y,z) cout << x << " " << y << " " << z << "\n"
using namespace std;
int T,n,m,s,t,dx[5] = {0,0,1,0,-1},dy[5] = {0,1,0,-1,0},ok[N]; string c[N];
void los(){
    cin >> n >> m,s = 0,t = 2 * n * m + 1;
    for(int i = 1;i <= n;i ++) cin >> c[i],c[i] = " " + c[i];
    mf_graph<int> g(2 * n * m + 2);
    auto id = [&](int i,int j,int k){return (i - 1) * m + j + k * n * m;};
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++){
        g.add_edge(s,id(i,j,0),1),g.add_edge(id(i,j,1),t,1);
        if(c[i][j] != '2') g.add_edge(id(i,j,0),id(i,j,1),1);
        if(c[i][j] == '1') continue;
        for(int k = 1;k <= 4;k ++){
            if(int ax = i + dx[k],ay = j + dy[k];ax >= 1 && ax <= n && ay >= 1 && ay <= m && c[ax][ay] != '1')
                g.add_edge(id(i,j,0),id(ax,ay,1),1);
        }
    }int f = g.flow(s,t);
    cout << (f == n * m ? "Yes\n" : "No\n");
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(117)\) ABC239G Builder Takahashi

难度 \(^*2300\)网络流;最小割

就是最小割板子吧。点意义上的最小割解决方案是拆点。输出答案在残量网格上找到 \(s\) 能到的点,如果 \(u \to v\) 这条边 \(u\) 可达,\(v\) 不可达那么这条边在最小割里。

代码

/**
* author: sunkuangzheng
* created: 21.02.2024 16:39:31
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
#include <atcoder/maxflow>
using namespace atcoder;
const int N = 5e5+5;
using namespace std;
int T,n,m,s,t,u,v,x;
void los(){
cin >> n >> m,s = 1,t = 2 * n;
mf_graph g(2 * n + 1);
while(m --) cin >> u >> v,g.add_edge(u + n,v,1e18),g.add_edge(v + n,u,1e18);
for(int i = 1;i <= n;i ++) cin >> x,g.add_edge(i,i + n,(i == 1 || i == n ? 1e18 : x));
cout << g.flow(s,t) << "\n";
vector f1 = g.min_cut(s); vector ans;
for(int u = 1;u <= n;u ++) if(v = u + n,f1[u] && !f1[v]) ans.push_back(u);
cout << ans.size() << "\n";
for(int i : ans) cout << i << " ";
}int main(){
ios::sync_with_stdio(0),cin.tie(0);
for(T = 1;T --😉 los();
}

\(\color{blue}(118)\) ABC243G Sqrt

难度 \(^*2100\)动态规划,DP

考虑朴素的 \(\mathcal O(n)\) 动态规划:设 \(f_{i,j}\) 表示第 \(i\) 个位置结尾填 \(j\) 的方案数量,\(f_{i,j} = \sum \limits_{k=j^2}^n f_{i-1,k}\)

当然优化至 \(\mathcal O(\sqrt n)\) 是简单的,我们发现从 \(f_2\) 开始 \(j\) 的上界只有 \(\sqrt n\)。但是 \(n\) 太大这个复杂度仍然过不了。

注意到 \(f_{2}\) 的值只有 \(0,1\),我们大可不必记录。在计算 \(f_3\) 的值时需要 \(f_2\) 的区间和,直接计算即可。时间复杂度 \(\mathcal O(T \sqrt[4]{n})\)

代码
/**
*    author: sunkuangzheng
*    created: 21.02.2024 17:50:14
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n; ll x,f[N][10];
void los(){
    cin >> x;
    ll p = sqrtl(x);
    for(int i = 1;1ll * i * i <= p;i ++) f[i][2] = p - 1ll * i * i + 1;
    for(int j = 3;j <= 7;j ++){
        p = sqrtl(p);
        for(int k = 1;k * k <= p;k ++){
            f[k][j] = 0;
            for(int l = k * k;l <= p;l ++) f[k][j] += f[l][j - 1];
        }
    }cout << f[1][7] << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(\color{blue}(119)\) ABC258G Triangle

难度 \(^*1500\)bitset

唯一的难点在于你需要知道 \(\mathcal O(\dfrac{n^3}{w})\) 能过。

朴素的暴力是枚举 \(i,j,k\),但是我们枚举完 \(i,j\) 后其实 \(k\) 一定在 \(i,j\) 能到的点集的交集里,bitset 实现即可,时间复杂度 \(\mathcal O(\dfrac{n^3}{w})\)

代码
/**
*    author: sunkuangzheng
*    created: 21.02.2024 19:14:06
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 3e3+5;
using namespace std;
int T,n;string s;
bitset<N> a[N];
void los(){
    cin >> n; ll ans = 0;
    for(int i = 1;i <= n;i ++){
        cin >> s,s = " " + s;
        for(int j = 1;j <= n;j ++) a[i][j] = s[j] - '0';
    }for(int i = 1;i <= n;i ++) for(int j = i + 1;j <= n;j ++)
    if(i != j && a[i][j]) ans += (a[i] & a[j]).count();
    cout << ans / 3;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(120)\) ABC234G Divide a Sequence

难度 \(^*2300\)动态规划,DP;单调栈

有朴素 \(\mathcal O(n^2)\) DP 式子:\(f_i = \sum \limits_{j=0}^{i-1} f_j\cdot \max\limits_{j+1}^i\{a_j\} \cdot \min\limits_{j+1}^i\{a_j\}\),注意到对于每个 \(i\) 其实 \(\max\)\(\min\) 可以拆开计算,维护 \(mx_i,mn_i,sm_i\) 表示 \(\max \cdot f\)\(\min \cdot f\) 和前缀和,单调栈维护即可。代码细节比较多。

代码
/**
*    author: sunkuangzheng
*    created: 21.02.2024 20:05:17
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
#include <atcoder/all>
using Z = atcoder::modint998244353;
int T,n,a[N],st1[N],st2[N],tp1,tp2; Z f[N],mx[N],mn[N],sm[N];
void los(){
    cin >> n; //f[i] = sum(f[j] * max(a[j+1..i])) - sum(f[j] * min(a[j+1...i]);
    auto sum = [&](int l,int r){return sm[r] - (l >= 1 ? sm[l - 1] : 0);};
    for(int i = 1;i <= n;i ++) cin >> a[i]; sm[0] = 1;
    for(int i = 1;i <= n;i ++){
        while(tp1 && a[st1[tp1]] > a[i]) tp1 --;
        while(tp2 && a[st2[tp2]] < a[i]) tp2 --;
        int l1 = (tp2 ? st2[tp2] : 0),l2 = (tp1 ? st1[tp1] : 0);
        mx[i] = a[i] * sum(l1,i-1) + mx[st2[tp2]],mn[i] = a[i] * sum(l2,i-1) + mn[st1[tp1]],
        f[i] = mx[i] - mn[i],sm[i] = sm[i - 1] + f[i],st1[++tp1] = i,st2[++tp2] = i;
    }cout << f[n].val();
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(121)\) CF620F Xors on Segments

难度 \(^*1600\)暴力

真的就是暴力。离线扫描线,扫右端点,维护 \(f_i\) 表示左端点 \(\ge i\) 的最大异或值,可以 \(\mathcal O(n^2 + V)\) 求得,然后即可 \(\mathcal O(m)\) 回答询问。

时间复杂度 \(\mathcal O(n^2+V+m)\)

啥?你说 \(\mathcal O(n^2)\) 咋过 \(5 \times 10^4\)
时间限制 \(10.00\) 秒。

代码
/**
*    author: sunkuangzheng
*    created: 22.02.2024 10:24:00
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,tmp[N*2],a[N],l,r,m,f[N],ans[N]; vector<pair<int,int>> g[N];
void los(){
    for(int i = 1;i <= 1000000;i ++) tmp[i] = (tmp[i - 1] ^ i);
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    for(int i = 1;i <= m;i ++) cin >> l >> r,g[r].emplace_back(l,i);
    for(int i = 1;i <= n;i ++){
        auto qry = [&](int x,int y){if(x > y) swap(x,y); return tmp[x - 1] ^ tmp[y];};
        for(int j = i;j >= 1;j --) f[j] = max({f[j],f[j + 1],qry(a[j],a[i])});
        for(auto [l,id] : g[i]) ans[id] = f[l];
    }for(int i = 1;i <= m;i ++) cout << ans[i] << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(122)\) ABC311G One More Grid Task

难度 \(^*2000\)单调栈

考虑到 \(a_i \le 300\),枚举最小值,我们可以选的数字必须大于等于最小值。也就是说,我们要选择一个所有数字都大于等于 \(x\) 的和最大的矩形。这是经典问题,单调栈求解。时间复杂度 \(\mathcal O(n^2V)\)

代码
/**
*    author: sunkuangzheng
*    created: 22.02.2024 11:46:28
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 3e3+5;
using namespace std;
int T,n,m,a[N][N],b[N][N],sm[N][N],f[N][N],st[N],tp,r[N];
void los(){
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) cin >> b[i][j];
    auto sol = [&](){
        int ans = 0;
        for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) 
            sm[i][j] = a[i][j] + sm[i-1][j] + sm[i][j-1] - sm[i-1][j-1];
        auto qry = [&](int l1,int r1,int l2,int r2){
            return sm[l2][r2] + sm[l1-1][r1-1] - sm[l2][r1-1] - sm[l1-1][r2];
        };for(int i = n;i >= 1;i --) for(int j = 1;j <= m;j ++) 
            f[i][j] = (a[i][j] == -1 ? 0 : f[i+1][j] + 1);
        for(int i = 1;i <= n;i ++){
            tp = 0;
            for(int j = m;j >= 1;j --){
                while(tp && f[i][st[tp]] >= f[i][j]) tp --;
                r[j] = (!f[i][j] ? -1 : (tp ? st[tp] : m + 1)),st[++tp] = j;
            }tp = 0;
            for(int j = 1;j <= m;j ++){
                while(tp && f[i][st[tp]] >= f[i][j]) tp --;
                if(f[i][j]) ans = max(ans,qry(i,st[tp]+1,i+f[i][j]-1,r[j] - 1));
                st[++tp] = j;
            }
        }return ans;
    };ll ans = 0;
    for(int k = 1;k <= 300;k ++){
        for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) a[i][j] = (b[i][j] >= k ? b[i][j] : -1);
        ans = max(ans,1ll * sol() * k);
    }cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(123)\) ABC274G Security Camera 3

难度 \(^*2500\)网络流;二分图

考虑我们不会选择一个极长连续段的中间点,那样一定不优。又注意到同一方向上的两种顺序本质等价,相当于对于每个位置可以选横的极长连续段或竖的极长连续段,是二分图最小点覆盖模型,网络流即可。时间复杂度 \(\mathcal O(nm \sqrt {nm})\)

代码
/**
*    author: sunkuangzheng
*    created: 20.11.2023 10:04:28
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
debug_helper deg;
#endif
using namespace std;
const int N = 4e2+5;
int n,m,a[N][N],tot;string ds;
namespace flow{
    const int maxn = 1e6+5;
    int s,t,cnt = 1,head[maxn],dep[maxn],cur[maxn],ans;
    struct edge{int to,nxt,w;}e[maxn];queue<int> q;
    void add(int u,int v,int w){e[++cnt].to = v,e[cnt].nxt = head[u],e[cnt].w = w,head[u] = cnt;}
    void adde(int u,int v,int w){add(u,v,w),add(v,u,0);}
    bool bfs(){
        memset(dep,-1,sizeof(dep)),dep[s] = 0,q.push(s);
        while(q.size()){
            int u = q.front();q.pop();
            for(int i = head[u],v;v = e[i].to,i;i = e[i].nxt) if(e[i].w && dep[v] == -1) dep[v] = dep[u] + 1,q.push(v);
        }return dep[t] != -1;
    }int dfs(int u,int f){
        if(u == t) return f;int w,a = 0;
        for(int &i = cur[u],v;v = e[i].to,i;i = e[i].nxt)
            if(e[i].w && dep[v] == dep[u] + 1) if(w = dfs(v,min(f-a,e[i].w)),a += w,e[i^1].w += w,e[i].w -= w,a == f) return f;
        if(!a) dep[u] = -1;return a;
    }int dinic(int a,int b){
        int ans = 0;
        while(bfs()){for(int i = a;i <= b;i ++) cur[i] = head[i];ans += dfs(s,1e9);}
        return ans;
    }
}using namespace flow;
int id(int x,int y){return (x - 1) * m + y;}
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> m,tot = 2 * n * m + 2,s = 0,t = 2 * n * m + 1;
    for(int i = 1;i <= n;i ++){
        cin >> ds,ds = " " + ds;
        for(int j = 1;j <= m;j ++) a[i][j] = (ds[j] == '#' ? 2 : (ds[j] == 'x'));
    }for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) if(cin >> a[i][j],!a[i][j]) adde(id(i,j),id(i,j) + n * m,1);
    for(int i = 1;i <= n;i ++)
        for(int j = 1;j <= m;j ++){
            int lj = j;
            while(j <= m && a[i][j] != 2) j ++;
            adde(s,++tot,1);
            for(int k = lj;k < j;k ++) adde(tot,id(i,k),1);
        }
    for(int i = 1;i <= m;i ++)
        for(int j = 1;j <= n;j ++){
            int lj = j;
            while(j <= n && a[j][i] != 2) j ++;
            adde(++tot,t,1);
            for(int k = lj;k < j;k ++) adde(id(k,i) + n * m,tot,1);
        }
    cout << dinic(s,tot);
}

\(\color{blue}(124)\) ABC286G Unique Walk

难度 \(^*2400\)欧拉路径

考虑非关键边可以随便走,那么断开关键边后仍然连通的部分可以缩为一个点。然后跑欧拉路径即可。时间复杂度 \(\mathcal O(n+m)\)

代码
/**
*    author: sunkuangzheng
*    created: 22.02.2024 16:17:40
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,fa[N],m,au[N],k,av[N],id[N],x,d[N],ct;
int fd(int x){return x == fa[x] ? x : fa[x] = fd(fa[x]);}
void mg(int x,int y){fa[fd(x)] = fd(y);}
void los(){
    cin >> n >> m;
    for(int i = 1;i <= m;i ++) cin >> au[i] >> av[i];
    cin >> k;
    for(int i = 1;i <= n;i ++) fa[i] = i;
    for(int i = 1;i <= k;i ++) cin >> x,id[x] = 1;
    for(int i = 1;i <= m;i ++) if(!id[i]) mg(au[i],av[i]); 
    for(int i = 1;i <= m;i ++) if(id[i]) d[fd(au[i])] ++,d[fd(av[i])] ++;
    for(int i = 1;i <= n;i ++) ct += (d[i] & 1);
    cout << (ct == 0 || ct == 2 ? "Yes\n" : "No\n");
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(125)\) CF1918E ace5 and Task Order

难度 \(^*2100\)分治;排序;交互;随机化

考虑如果我们已经有 \(x = a_i\),则可以用 \(\mathcal O(1)\) 次询问比较 \(a_i,a_j\) 的大小关系。只要我们能将序列排序我们就能知道每一项的值。问题在于怎么让 \(x = a_i\) 这个步骤花费的询问次数减少。考虑快速排序的过程,先左再右递归,\(x\) 的值一定会先到 \(1\) 再到 \(n\),消耗 \(\mathcal O(n)\) 次询问。总询问次数 \(\mathcal O(n \log n)\)

代码
/**
*    author: sunkuangzheng
*    created: 22.02.2024 17:41:45
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
mt19937 rd(time(0));
int T,n,a[N],b[N],c[N],val[N]; char ch;
char ask(int x){assert(x > 0 && x <= n);cout << "? " << x << endl,cin >> ch;return ch;}
void qsort(int l,int r){
    if(l >= r) return ;
    int mid = rd() % (r - l + 1) + l,t1 = 0,t2 = 0;
    for(;ask(a[mid]) != '=';);int tmp = a[mid];
    for(int i = l;i <= r;i ++){
        char ch = ask(a[i]);
        if(ch == '<') b[++t1] = a[i],ask(a[mid]);
        else if(ch == '>') c[++t2] = a[i],ask(a[mid]);
    }for(int i = l;i <= l + t1 - 1;i ++) a[i] = b[i - l + 1];
    a[l + t1] = tmp;
    for(int i = l + t1 + 1;i <= r;i ++) a[i] = c[i - l - t1];
    qsort(l,l+t1-1),qsort(l+t1+1,r);
}void los(){
    cin >> n;
    for(int i = 1;i <= n;i ++) a[i] = i;
    qsort(1,n);
    for(int i = 1;i <= n;i ++) val[a[i]] = i;
    cout << "! ";
    for(int i = 1;i <= n;i ++) cout << val[i] << " ";
    cout << endl;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(126 \sim 150\)

\(\color{blue}(126)\) ABC244G Construct Good Path

难度 \(^*2000\)构造

首先注意到题目说,可以证明问题有解,而数据中存在树的情况,也就是说把图去掉多余的边变成树也一定有解。考虑最简单的情况:树是一条链。此时只需要从左到右递推,如果实际的经过次数和要求不符,就走一次 \(i \to i + 1 \to i\),增加 \(i,i + 1\) 的经过次数,然后处理 \(i + 1\)。最后注意到结尾可以是 \(n-1\)\(n\),根据 \(n\) 的经过次数调整即可。

考虑树的情况,思路类似。从下向上确定,如果 \(i\) 不满足要求,就走 \(i \to fa_i \to i\)。最后我们可以选择结尾是不是 \(1\),根据 \(1\) 的经过次数调整即可。时间复杂度 \(\mathcal O(n)\),显然路径长度不会超过 \(4n\)

代码
/**
*    author: sunkuangzheng
*    created: 22.02.2024 19:57:00
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,m,fa[N],u,v,d[N],a[N]; vector<int> g[N],ans; string s;
int fd(int x){return x == fa[x] ? x : fa[x] = fd(fa[x]);}
void add(int u,int v){ans.push_back(v),d[v] ^= 1;}
void dfs(int u,int f){
    for(int v : g[u]){
        if(v != f){
            if(g[v].size() == 1 && a[v]) add(u,v),add(v,u);
            if(g[v].size() > 1) add(u,v),dfs(v,u);
        }
    }if(u != 1){
        if(d[u] != a[u]) add(u,f),add(f,u);
        add(u,f);
    }if(u == 1 && a[u] != d[u]) ans.pop_back();
}void los(){
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) fa[i] = i;
    for(int i = 1;i <= m;i ++) if(cin >> u >> v,fd(u) != fd(v)) 
        g[u].push_back(v),g[v].push_back(u),fa[fd(u)] = fd(v);
    cin >> s,s = " " + s;
    for(int i = 1;i <= n;i ++) a[i] = s[i] - '0'; d[1] = 1;
    ans.push_back(1),dfs(1,0),cout << ans.size() << "\n";
    for(int i : ans) cout << i << " ";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(127)\) CF1923E Count Paths

难度 \(^*2100\)虚树;动态规划,DP

对每种颜色建虚树,然后暴力 DP 即可。

代码
/**
*    author: sunkuangzheng
*    created: 13.02.2024 08:07:00
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
#define int long long
using namespace std;
int T,n,dfn[N],st[20][N],tot,u,v,a[N]; vector<int> g[N],p[N]; ll f[N];
void dfs(int u,int f){
    st[0][dfn[u] = ++tot] = f;
    for(int v : g[u]) if(v != f) dfs(v,u);
}void los(){
    cin >> n,tot = 0;
    for(int i = 1;i <= n;i ++) g[i].clear(),p[i].clear();
    for(int i = 1;i <= n;i ++) cin >> a[i],p[a[i]].push_back(i);
    for(int i = 1;i < n;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u);
    dfs(1,0);
    auto cmp = [&](int u,int v){return dfn[u] < dfn[v] ? u : v;};
    for(int j = 1;j <= __lg(n);j ++) for(int i = 1;i + (1 << j) - 1 <= n;i ++)
        st[j][i] = cmp(st[j-1][i],st[j-1][i+(1<<j-1)]);
    auto lca = [&](int u,int v){
        if(u == v) return u;
        if((u = dfn[u]) > (v = dfn[v])) swap(u,v);
        int k = __lg(v - u);
        return cmp(st[k][u+1],st[k][v-(1<<k)+1]);
    };for(int i = 1;i <= n;i ++) g[i].clear();
    auto dcmp = [&](int x,int y){return dfn[x] < dfn[y];};
    auto sol = [&](int k){
        vector<int> d; ll ans = 0;
        for(int i : p[k]) d.push_back(i); sort(d.begin(),d.end(),dcmp);
        for(int i = 1;i < p[k].size();i ++) d.push_back(lca(d[i-1],d[i]));
        sort(d.begin(),d.end(),dcmp),d.erase(unique(d.begin(),d.end()),d.end());
        for(int i = 1;i < d.size();i ++) g[lca(d[i-1],d[i])].push_back(d[i]);
        reverse(d.begin(),d.end());
        for(int u : d){
            f[u] = 0; ll tot = 0;
            for(int v : g[u]) f[u] += f[v],tot += (a[v] != k) * f[v] * (f[v] - 1) / 2;
            if(a[u] == k) ans += f[u],f[u] = 1; 
            else ans += 1ll * f[u] * (f[u] - 1) / 2 - tot;
            g[u].clear();
        }return ans;
    };ll ans = 0;
    for(int i = 1;i <= n;i ++) ans += sol(i);
    cout << ans << "\n";
}signed main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(\color{blue}(128)\) CF1923F Shrink-Reverse

难度 \(^*2800\)贪心;字符串;后缀数组,SA

要多练找性质的能力了。

  • 性质一:操作二最多做一次。

首先一定不会做操作二三次及以上,那样我们可以把两次操作合并为一次,节省操作次数。注意到前导 \(0\) 对权值没有影响,我们想做两次操作二是为了去除串 \(s\) 的后导 \(0\),但是这样并不划算:例如串 \(\texttt{00111001000}\),操作两次后串变为 \(\texttt{111001}\),但是我们可以选择操作一次(串变成 \(\texttt{000100111}\))后,将第一个 \(1\) 往后放得到 \(\texttt{1111}\)。也就是说,如果做两次二操作,第二次操作可以被一次一操作替代,且答案会变得不劣。

我们可以直接计算不翻转 \(s\) 的答案,即把所有 \(1\) 贪心的往后换。以下讨论翻转串 \(s\) 后的情况,注意下面的 \(s\) 是翻转过的串。

  • 性质二:答案串 \(s\) 去除前后导 \(0\) 后长度一定是可能达到的最小的。

因为进行了 \(1\) 操作,后导 \(0\) 已经被全部删除。比较两个二进制数字大小时第一关键字是长度。

\(c\) 表示串里 \(1\) 的数量,\(lt = c-k\) 表示有多少个 \(1\) 不能移动位置。答案的下界显然是 \(2^c-1\)。为了让长度最短,我们希望让不能移动位置的 \(1\) 连续,并把外面的 \(1\) 换到这个连续段里。记 \(\{pos_c\}\) 表示所有 \(1\) 的出现位置,如果存在 \(i\) 满足 \(pos_{i+lt-1}-pos_i+1 \le c\),就说明我们可以用剩余的 \(1\) 填满这些 \(1\) 中间的 \(0\),让答案达到下界。否则,最小的答案串长度就应该是 \(\min \limits_{i=1}^{c-lt+1}\{pos_{i+lt-1}-pos_i+1\}\),记这个串为 \(t\),此时我们可以把剩余的 \(1\) 全部插入到 \(t\) 的空隙里。但是长度最短的 \(t\) 可能不唯一。

  • 性质三:当存在多个长度最短的 \(t\) 时,我们选择字典序最小的。

考虑我们把剩余的 \(1\) 插入到串 \(t\) 中时会贪心的从后到前插,那么此时选最小的 \(t\) 一定不劣。

如果暴力比较所有最短的串 \(t\) 大小可能会被卡到 \(\mathcal O(n^2)\),使用后缀数组比较即可。可以用 SA-IS 做到线性。

放一份 \(\mathcal O(n \log^2 n)\) 的 SA 代码:

代码
/**
 *    author: sunkuangzheng
 *    created: 24.02.2024 16:28:17
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,k,sa[N],ok[N],rk[N]; string s;
void los(){
    cin >> n >> k >> s,s = " " + s;
    auto cmp = [&](string &s,string t){
        return s.size() == t.size() ? (s < t ? s : t) : (s.size() < t.size() ? s : t);
    };
    auto gd = [&](string s){
        int ct = 0;
        for(int i = 1;i <= n;i ++)
            if(ct < k && s[i] == '1') ct ++,s[i] = '0';
        for(int i = n;i >= 1;i --) if(s[i] == '0' && ct) s[i] = '1',ct --;
        return s.substr(s.find('1'));
    };string ans = gd(s);
    reverse(s.begin(),s.end()),k --,s = " " + s;
    vector<int> p;
    for(int i = 1;i <= n;i ++) if(s[i] == '1') p.push_back(i);
    if(k < p.size()){
        int lt = p.size() - k,len = 1e9;
        for(int i = 0;i + lt - 1 < p.size();i ++) len = min(len,p[i + lt - 1] - p[i] + 1);
        if(len <= p.size()) ans = string(p.size(),'1');
        else{
            for(int i = 1;i <= n;i ++) rk[i] = s[i],sa[i] = i;
            for(int j = 1;j <= n;j *= 2){
                for(int i = 1;i <= n;i ++) ok[i] = rk[i]; int p = 0;
                sort(sa+1,sa+n+1,[&](int x,int y){return rk[x] < rk[y] || rk[x] == rk[y] && rk[x + j] < rk[y + j];});
                auto cmp = [&](int x,int y){return ok[x] == ok[y] && ok[x + j] == ok[y + j];};
                for(int i = 1;i <= n;i ++) if(cmp(sa[i],sa[i-1])) rk[sa[i]] = p; else rk[sa[i]] = ++p; if(p == n) break;
            }for(int i = 1;i <= n;i ++){
                int pos = sa[i]; if(s[pos] == '0') continue;
                int d = lower_bound(p.begin(),p.end(),pos) - p.begin();
                if(d + lt - 1 < p.size() && p[d + lt - 1] - p[d] + 1 == len){
                    int l = pos,r = pos + len - 1;
                    for(int j = r;j >= l;j --) if(k && s[j] == '0') k --,s[j] = '1';
                    ans = cmp(ans,s.substr(l,len)); break;
                }
            }
        }
    }else ans = string(p.size(),'1');
    int sum = 0;
    for(char i : ans) sum = (2ll * sum + i - '0') % (int)(1e9+7);
    cout << sum;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(129)\) CF1619H Permutation and Queries

难度 \(^*2400\)分块

想了一个小时,看了题解才想起来 \(p\) 是个排列,唐完了。

注意到排列可以被表示为若干个环,每个点维护跳一步,跳 \(B\) 步,跳 \(-1\) 步能到的点,时间复杂度 \(\mathcal O((n+q)B+q\dfrac{n}{B})\),取 \(B = \sqrt n\) 得复杂度 \(\mathcal O(n+q \sqrt n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 25.02.2024 08:01:12
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5,B = 300;
using namespace std;
int T,n,q,kfa[N],pre[N],a[N],fa[N],vis[N],op,x,y;
void los(){
    cin >> n >> q;
    for(int i = 1;i <= n;i ++) cin >> fa[i],pre[fa[i]] = i;
    for(int i = 1;i <= n;i ++){
        int p = i;
        for(int j = 1;j <= B;j ++) p = fa[p];
        kfa[i] = p;
    }while(q --){
        if(cin >> op >> x >> y,op == 1){
            swap(fa[x],fa[y]),pre[fa[x]] = x,pre[fa[y]] = y; int p = x,q = y;
            for(int j = 1;j <= B;j ++) p = fa[p],q = fa[q]; kfa[x] = p,kfa[y] = q,x = pre[x],y = pre[y]; 
            for(int j = 1;j <= B;j ++,x = pre[x]) kfa[x] = pre[kfa[fa[x]]];
            for(int j = 1;j <= B;j ++,y = pre[y]) kfa[y] = pre[kfa[fa[y]]];
        }else{
            while(y > B) y -= B,x = kfa[x];
            while(y --) x = fa[x];
            cout << x << "\n";
        }
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(130)\) [ARC081F] Flip and Rectangles

难度 \(^*2600\)单调栈

人类智慧。寻找一个矩形可以填成全 \(1\) 的充要条件,这里直接给出结论:所有 \(2 \times 2\) 的子矩阵内都有偶数个 \(1\)

  • 必要性。

如果有一个子矩阵存在奇数个 \(1\),则不论怎么操作这个矩阵内始终会有奇数个 \(1\)

  • 充分性。

我们直接给出一种构造。首先,操作最左边和最上边的一行一列,把它们变成全 \(1\)。这是一定可以做到的,如果 \(a_{1,i} = 0\) 就操作第 \(i\) 列即可,行同理。由于操作不会改变 \(2 \times 2\) 的子矩阵内 \(1\) 的数量的奇偶性,由所有 \(2 \times 2\) 的矩阵内都有偶数个 \(1\) 可以推出此时矩阵已经全 \(1\)

然后跑最大全 \(1\) 子矩形即可。时间复杂度 \(\mathcal O(nm)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 25.02.2024 09:44:38
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 2e3+5;
using namespace std;
int T,n,a[N][N],m,f[N][N],r[N],st[N],tp; string s[N];
void los(){
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) cin >> s[i],s[i] = " " + s[i];
    int ans = max(n --,m --);
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++)
        a[i][j] = !((s[i][j] == '#') ^ (s[i+1][j] == '#') ^ (s[i+1][j+1] == '#') ^ (s[i][j+1] == '#'));
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) f[i][j] = (a[i][j] ? f[i-1][j] + 1 : 0);
    for(int i = 1;i <= n;i ++){
        st[++tp] = m + 1;
        for(int j = m;j >= 1;j --){
            while(tp && f[i][st[tp]] >= f[i][j]) tp --;
            if(!tp) r[j] = m + 1; else r[j] = st[tp]; st[++tp] = j;
        }tp = 0;
        for(int j = 1;j <= m;j ++){
            while(tp && f[i][st[tp]] >= f[i][j]) tp --;
            ans = max(ans,(r[j] - st[tp]) * (f[i][j] + 1)),st[++tp] = j;
        }tp = 0;
    }cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(131)\) [ABC219G] Propagation

难度 \(^*2400\)根号分治

考虑根号分治,对于 \(deg \le B\) 的点可以暴力改,对于 \(deg > B\) 的点打上修改 tag 并暴力改和它相连的 \(deg > B\) 的点。对于 \(deg \le B\) 的点,在所有出边里寻找打 tag 最晚的。时间复杂度 \(\mathcal O(n \sqrt n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 25.02.2024 10:43:19
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5,B = 305;
using namespace std;
int T,n,m,u,v,a[N],q,lst[N]; vector<int> g[N],t[N]; pair<int,int> tag[N];
void los(){
    cin >> n >> m >> q;
    for(int i = 1;i <= n;i ++) a[i] = i;
    for(int i = 1;i <= m;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u);
    for(int i = 1;i <= n;i ++) for(int j : g[i]) if(g[i].size() > B && g[j].size() > B) t[i].push_back(j);
    for(int i = 1;i <= q;i ++){
        cin >> u;
        if(g[u].size() <= B){
            pair<int,int> fk; 
            for(int v : g[u]) fk = max(fk,tag[v]);
            auto [ti,val] = fk; 
            if(ti > lst[u]) a[u] = val;
            for(int v : g[u]) a[v] = a[u],lst[v] = i;
        }else{
            for(int v : t[u]) a[v] = a[u],lst[v] = i;
            tag[u] = {i,a[u]};
        }
    }for(int u = 1;u <= n;u ++){
        if(g[u].size() <= B){
            pair<int,int> fk;
            for(int v : g[u]) fk = max(fk,tag[v]);
            auto [ti,val] = fk;
            if(ti > lst[u]) a[u] = val;
        }cout << a[u] << " ";
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(132)\) [ABC263G] Erasing Prime Pairs

难度 \(^*2500\)二分图;费用流

很容易发现除了 \(2\) 以外所有质数都是奇数,即一奇一偶匹配,是一张二分图,但是 \(a_i \ge 1\) 而不是 \(a_i \ge 2\)\(1\) 可以和自己匹配。最开始我的想法是把 \(1\) 都和自己匹配,剩余的跑二分图最大匹配,但是它假了。我们的策略应该是先用其他数字匹配并尽可能的多留下 \(1\),那么跑费用流即可。

代码
/**
 *    author: sunkuangzheng
 *    created: 25.02.2024 12:19:50
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],b[N],s,t;
#include <atcoder/mincostflow>
using namespace atcoder;
void los(){
    cin >> n;
    mcf_graph<ll,int> g(n + 2);
    s = 0,t = n + 1; int ans = 0;
    auto ckp = [&](int x){
        for(int i = 2;i * i <= x;i ++) if(x % i == 0) return 0;
        return 1;
    }; 
    for(int i = 1;i <= n;i ++) cin >> a[i] >> b[i];
    for(int i = 1;i <= n;i ++){
        if(a[i] == 1){
            ans = b[i];
            g.add_edge(s,i,b[i],1);
            for(int j = 1;j <= n;j ++)
                if(!(a[j] & 1) && ckp(a[i] + a[j])) g.add_edge(i,j,1e18,0);
        }else if(a[i] & 1){
            g.add_edge(s,i,b[i],0);
            for(int j = 1;j <= n;j ++)
                if(!(a[j] & 1) && ckp(a[i] + a[j])) g.add_edge(i,j,1e18,0);
        }else g.add_edge(i,t,b[i],0); 
    }auto [x,y] = g.flow(s,t);
    cout << x + (ans - y) / 2;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(133)\) [ABC265G] 012 Inversion

难度 \(^*2600\)线段树

比较恶心的题。

线段树节点记录 \(0,1,2,10,20,21\) 的数量,tag 维护 \(0,1,2\) 分别变成谁。修改时,\(0,1,2\) 的数量可以直接得到,原本的 \(10,20,21\) 直接贡献给 \(f_1f_0,f_2f_0,f_2f_1\),注意 \(01\) 也会贡献给 \(f_0f_1\)\(01\) 的数量显然是原本的 \(0 \times 1 - 01\)

合并两个 tag \(f,g\) 时,只需要返回 \(f_{g_i}\)。时间复杂度 \(\mathcal O(n \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 25.02.2024 15:59:48
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
#include <atcoder/lazysegtree>
#include <atcoder/convolution>
using namespace atcoder;
struct S{
    ll r[6]; // 0 1 2 10 20 21
    ll operator [](int x){return r[x];};
};
struct F{
    int f[3];
    int operator [](int x){return f[x];};
};
S op(S l,S r){
    return S{{l[0] + r[0],l[1] + r[1],l[2] + r[2],
        l[3] + r[3] + l[1] * r[0],l[4] + r[4] + l[2] * r[0],l[5] + r[5] + l[2] * r[1]}};
}S e(){return S{0,0,0,0,0,0};}
S mapping(F f,S x){
    vector<vector<ll>> fk(3,vector<ll>(3)); vector<ll> sb(3);
    fk[f[0]][f[0]] += x[0],fk[f[1]][f[1]] += x[1],fk[f[2]][f[2]] += x[2],
    fk[f[1]][f[0]] += x[3],fk[f[2]][f[0]] += x[4],fk[f[2]][f[1]] += x[5],
    fk[f[0]][f[1]] += x[0] * x[1] - x[3],fk[f[0]][f[2]] += x[0] * x[2] - x[4],
    fk[f[1]][f[2]] += x[1] * x[2] - x[5],sb[f[0]] += x[0],sb[f[1]] += x[1],sb[f[2]] += x[2];
    return S{sb[0],sb[1],sb[2],fk[1][0],fk[2][0],fk[2][1]};
}F composition(F f,F g){
    return F{f[g[0]],f[g[1]],f[g[2]]};
}F id(){return {0,1,2};}
int T,n,q,x,po,l,r,s1,s2,s3;
vector<S> a;
void los(){
    cin >> n >> q;
    for(int i = 1;i <= n;i ++) cin >> x,a.push_back({(x == 0),(x == 1),(x == 2),0,0,0});
    lazy_segtree<S, op, e, F, mapping, composition, id> seg(a);
    while(q --){
        if(cin >> po >> l >> r,po == 1){
            auto s = seg.prod(l - 1,r);
            cout << s.r[3] + s.r[4] + s.r[5] << "\n";
        }else cin >> s1 >> s2 >> s3,seg.apply(l - 1,r,F{s1,s2,s3});
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(134)\) [ARC069F] Flags

难度 \(^*2800\)二分;线段树;2-SAT

很无聊的题。二分后转化为 2-SAT 问题,发现 \(\mathcal O(n^2 \log V)\) 过不了,但是连边都是点向区间连边,可以线段树优化。时间复杂度 \(\mathcal O(n \log n \log V)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 25.02.2024 19:37:34
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
#include <atcoder/twosat>
using namespace atcoder;
using namespace std;
int T,n,ax[N],ay[N]; pair<int,int> pos[N];
void los(){
    cin >> n; vector<pair<int,int>> a;
    for(int i = 1;i <= n;i ++) cin >> ax[i] >> ay[i],a.emplace_back(ax[i],i),a.emplace_back(ay[i],i + n);
    sort(a.begin(),a.end());
    for(int i = 1;i <= n;i ++) ax[i] = lower_bound(a.begin(),a.end(),make_pair(ax[i],i)) - a.begin() + 1,
                               ay[i] = lower_bound(a.begin(),a.end(),make_pair(ay[i],i + n)) - a.begin() + 1,
                               pos[ax[i]] = {i,0},pos[ay[i]] = {i,1};
    int l = 0,r = 1e9;
    while(l <= r){
        int mid = (l + r) / 2;
        auto id = [&](pair<int,int> x){return 8 * n + x.second * n + x.first;};
        auto ck = [&](int x){
            two_sat g(n * 10 + 1);
            auto add = [&](int x,int y){g.add_clause(x,1,y,0);};
            auto build = [&](auto self,int s,int l,int r) -> void {
                if(l == r) return add(s,id(pos[l])),void();
                int mid = (l + r) / 2;
                self(self,s*2,l,mid),self(self,s*2+1,mid+1,r);
                add(s,s*2),add(s,s*2+1);
            };auto upd = [&](auto self,int s,int l,int r,int ql,int qr,int idd) -> void {
                if(ql > qr) return ;
                if(ql <= l && r <= qr) return g.add_clause(idd,0,s,0),void();
                int mid = (l + r) / 2; 
                if(ql <= mid) self(self,s*2,l,mid,ql,qr,idd); 
                if(qr > mid) self(self,s*2+1,mid+1,r,ql,qr,idd);
            };build(build,1,1,2*n);
            for(int i = 1;i <= n;i ++) g.add_clause(id({i,0}),1,id({i,1}),1);
            for(int i = 1;i <= 2 * n;i ++){
                int l1 = a[i-1].first - x + 1,r1 = a[i-1].first + x - 1;
                int l = lower_bound(a.begin(),a.end(),make_pair(l1,(int)-1e9)) - a.begin() + 1,
                    r = upper_bound(a.begin(),a.end(),make_pair(r1,(int)1e9)) - a.begin();
                upd(upd,1,1,2*n,max(1,l),i-1,id(pos[i])),upd(upd,1,1,2*n,i+1,min(r,2*n),id(pos[i]));
            }return g.satisfiable(); ;
        };
        if(ck(mid)) l = mid + 1; else r = mid - 1;
    }cout << l - 1;
    // ck(2);
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(135)\) P4374 [USACO18OPEN] Disruption P

难度 \(^*2500\)树链剖分;线段树

一条边的贡献是树链取 \(\min\)。树剖 + 线段树。时间复杂度 \(\mathcal O(n \log^2 n)\)

代码
using namespace atcoder;
using S = int;
using F = int;
S op(S l,S r){return min(l,r);}
S e(){return 2e9;}
S mapping(F f,S x){return min(f,x);}
F composition(F f,F g){return min(f,g);}
F id(){return 2e9;}
int m,dfn[N],top[N],fa[N],au[N],av[N],son[N],dep[N],u,v,w,siz[N],tot; vector<int> g[N];
void los(){
    cin >> n >> m;
    for(int i = 1;i < n;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u),au[i] = u,av[i] = v;
    auto dfs1 = [&](auto self,int u,int f) -> void {
        fa[u] = f,siz[u] = 1,dep[u] = dep[f] + 1;
        for(int v : g[u]) if(v != f) if(self(self,v,u),siz[u] += siz[v],siz[v] > siz[son[u]]) son[u] = v;
    };auto dfs2 = [&](auto self,int u,int tp) -> void {
        dfn[u] = ++tot,top[u] = tp; if(son[u]) self(self,son[u],tp);
        for(int v : g[u]) if(v != fa[u] && v != son[u]) self(self,v,v);
    }; dfs1(dfs1,1,0),dfs2(dfs2,1,1);
    lazy_segtree<S, op, e, F, mapping, composition, id> seg(n);
    auto upd = [&](int u,int v){
        while(top[u] != top[v]){
            if(dep[top[u]] < dep[top[v]]) swap(u,v);
            seg.apply(dfn[top[u]] - 1,dfn[u],w),u = fa[top[u]];
        }if(u == v) return ;
        seg.apply(min(dfn[u],dfn[v]),max(dfn[u],dfn[v]),w);
    };
    while(m --)
        cin >> u >> v >> w,upd(u,v);
    for(int i = 1;i < n;i ++){
        int d = (dfn[au[i]] > dfn[av[i]] ? dfn[au[i]] : dfn[av[i]]),ans = seg.prod(d - 1,d);
        cout << (ans > 1e9 ? -1 : ans) << "\n";
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(136)\) AT_joisc2021_j ビーバーの会合 2 (Meetings 2)

难度 \(^*2800\)点分治;树状数组

首先有结论:如果 \(|S| \bmod 2 = 1\),那么 \(x\) 唯一,证明可见 CF1824B2 的题解区。否则,\(x\) 一定在 \(|S|\) 最中心的两个点的路径上,这些点满足以它为根时,关键点最多的子树里不超过 \(\dfrac{|S|}{2}\) 个关键点。

对于一条路径 \(u,v\),可以对所有 \(|S| \le \min(siz_u,siz_v)\) 产生贡献。考虑点分治,若当前枚举的点是 \(u\),则在前面的子树中寻找深度最大的 \(v\) 满足 \(siz_v \ge siz_u\)。注意要反着再跑一遍以防漏掉 \(siz_v \le siz_u\)\(u \to v\) 贡献的情况。找深度最大的 \(v\) 用树状数组维护,时间复杂度 \(\mathcal O(n \log^2 n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 26.02.2024 08:43:55
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,u,v,mx[N],siz[N],vis[N],re,rt,dep[N]; vector<int> g[N]; vector<pair<int,int>> fk;
void los(){
    cin >> n,mx[0] = 1e9;
    for(int i = 1;i < n;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u);
    vector<int> t(n + 1,-1e9),ans(n + 1,1);
    auto upd = [&](int x,int p){for(;x;x -= x & -x) t[x] = max(t[x],p);};
    auto qry = [&](int x){for(re = -1e9;x <= n;x += x & -x) re = max(re,t[x]); return re;};
    auto clr = [&](int x){for(;x;x -= x & -x) t[x] = -1e9;};
    auto dfs1 = [&](auto self,int u,int f,int tot) -> void {
        siz[u] = 1,mx[u] = 0;
        for(int v : g[u]) if(v != f && !vis[v]) self(self,v,u,tot),siz[u] += siz[v],mx[u] = max(mx[u],siz[v]);
        if(mx[u] = max(mx[u],tot - siz[u]),mx[u] < mx[rt]) rt = u;
    };auto get = [&](auto self,int u,int f) -> int {
        int sm = 1; dep[u] = dep[f] + 1;
        for(int v : g[u]) if(v != f && !vis[v]) sm += self(self,v,u);
        return fk.emplace_back(dep[u],sm),sm;
    };
    auto dfs2 = [&](auto self,int u) -> void {
        vector<int> pos; dep[u] = 0; int p = -1,q = -1;  dfs1(dfs1,u,0,n);
        vector<pair<int,int>> sb;
        for(int v : g[u]) if(!vis[v]){
            fk.clear(),p = get(get,v,u);
            for(auto [d,sz] : fk) ans[sz] = max(ans[sz],d + qry(sz) + 1),q = min(sz,siz[u] - p),ans[q] = max(ans[q],d + 1);
            for(auto [d,sz] : fk) upd(sz,d),pos.push_back(sz),sb.emplace_back(d,sz);
            sb.emplace_back(-1,-1);
        }
         for(int i : pos) clr(i); vis[u] = 1; reverse(sb.begin(),sb.end()),fk.clear();
        for(auto [d,sz] : sb){
            if(sz == -1){for(auto [d,sz] : fk) upd(sz,d);fk.clear();}
            else ans[sz] = max(ans[sz],d + qry(sz) + 1),fk.emplace_back(d,sz);
        }for(int i : pos) clr(i);
        for(int v : g[u]) if(!vis[v]) rt = 0,dfs1(dfs1,v,u,siz[v]),self(self,rt);
    };dfs1(dfs1,1,0,n),dfs2(dfs2,rt);
    for(int i = n - 1;i >= 1;i --) ans[i] = max(ans[i],ans[i + 1]);
    for(int i = 1;i <= n;i ++) cout << (i & 1 ? 1 : ans[i / 2]) << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(137)\) [AGC004C] AND Grid

难度 \(^*2000\)构造

####.   ....#
#....   #####
####.   ....#
#....   #####
####.   ....#
代码
#include<bits/stdc++.h>
const int N = 5e3+5;
using namespace std;
int T,n,m; string s[N];
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) cin >> s[i],s[i] = " " + s[i];
    for(int i = 1;i <= n;i ++,cout << "\n") for(int j = 1;j <= m;j ++)
        cout << ".#"[s[i][j] == '#' || j == 1 || (i & 1) && j != m];
    for(int i = 1;i <= n;i ++,cout << "\n") for(int j = 1;j <= m;j ++)
        cout << ".#"[s[i][j] == '#' || j == m || !(i & 1) && j != 1];
}

\(\color{blue}(138)\) CF1667F Yin Yang

难度 \(^*3500\)构造;模拟

我的题解

\(\color{blue}(139)\) CF226E Noble Knight's Path

难度 \(^*2500\)树链剖分;可持久化线段树

这能黑?这能黑?这能黑?这能黑?这能黑?这能黑?

可持久化线段树维护每个版本时黑点的数量即可,树剖跳链找到答案点在的链,然后二分主席树。

时间复杂度 \(\mathcal O(m \log^2 n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 27.02.2024 11:11:20
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,u,v,cnt,tot,m,op,x,y,tr,d,a[N]; 
struct node{int l,r,w;};
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n;
    vector<vector<int>> g(n + 1,vector<int>(0));
    for(int i = 1;i <= n;i ++) if(cin >> u,u) g[u].push_back(i); else tr = i;
    cin >> m;
    vector<int> fa(n + 1),siz(n + 1),son(n + 1),top(n + 1),nfd(n + 1),dep(n + 1),dfn(n + 1),rt(m + 1);
    auto dfs1 = [&](auto self,int u,int f) -> void {
        siz[u] = 1,dep[u] = dep[f] + 1,fa[u] = f;
        for(int v : g[u]) if(v != f) if(self(self,v,u),siz[u] += siz[v],siz[v] > siz[son[u]]) son[u] = v;
    };auto dfs2 = [&](auto self,int u,int tp) -> void {
        dfn[u] = ++cnt,top[u] = tp,nfd[cnt] = u; if(son[u]) self(self,son[u],tp);
        for(int v : g[u]) if(v != fa[u] && v != son[u]) self(self,v,v);
    }; vector<node> t((n + m) * 20 + 5); 
    auto upd = [&](auto self,int s,int l,int r,int x,int k) -> int {
        int p = ++tot,mid = (l + r) / 2; t[p] = t[s],t[p].w += k;
        if(l == r) return p;
        if(x <= mid) t[p].l = self(self,t[p].l,l,mid,x,k); else t[p].r = self(self,t[p].r,mid+1,r,x,k);
        return p;
    };auto qry = [&](auto self,int u,int v,int l,int r,int ql,int qr) -> int {
        if(ql <= l && r <= qr) return t[v].w - t[u].w;
        int mid = (l + r) / 2,ans = 0;
        if(ql <= mid) ans += self(self,t[u].l,t[v].l,l,mid,ql,qr);
        if(qr > mid) ans += self(self,t[u].r,t[v].r,mid+1,r,ql,qr);
        return ans;
    };dfs1(dfs1,tr,0),dfs2(dfs2,tr,tr); 
    auto gpth = [&](int u,int v){
        vector<tuple<int,int,int>> acc,cjr;
        while(top[u] != top[v]){
            if(dep[top[u]] > dep[top[v]]) acc.emplace_back(dfn[top[u]],dfn[u],0),u = fa[top[u]];
            else cjr.emplace_back(dfn[top[v]],dfn[v],1),v = fa[top[v]];
        }acc.emplace_back(min(dfn[u],dfn[v]),max(dfn[u],dfn[v]),dep[u] < dep[v]),reverse(cjr.begin(),cjr.end());
        for(auto x : cjr) acc.push_back(x); return acc;
    }; 
    for(int i = 1;i <= m;i ++){
        rt[i] = rt[i - 1];
        if(cin >> op >> u,op == 1) rt[i] = upd(upd,rt[i],1,n,dfn[u],1),a[u] = 1;
        else{
            cin >> v >> x >> y; auto fk = gpth(u,v); int fg = 0;
            x += 1 - qry(qry,rt[y],rt[i],1,n,dfn[u],dfn[u]);
            for(auto [l,r,op] : fk){
                int k = r - l + 1 - qry(qry,rt[y],rt[i],1,n,l,r);
                if(x > k) x -= k; 
                else{
                    if(!op) x = k - x + 1;
                    int ql = l,qr = r;
                    while(ql <= qr){
                        int mid = (ql + qr) / 2; 
                        if(mid - l + 1 - qry(qry,rt[y],rt[i],1,n,l,mid) >= x) qr = mid - 1; else ql = mid + 1;  
                    }cout << (nfd[qr + 1] == v ? -1 : nfd[qr + 1]) << "\n",fg = 1;break;
                }
            }if(!fg) cout << "-1\n";
        }
    }
}   

\(\color{blue}(140)\) CF1677E Tokitsukaze and Beautiful Subsegments

难度 \(^*2600\)扫描线;单调栈;线段树

枚举 \(\max\),设 \(a_i\)\(\max\) 时区间为 \([l,r]\),枚举 \(\max\) 的约数 \(j,k(j \cdot k = a_i)\),令 \(j,k\) 的位置为 \(p_j,p_k\),则 \(L \in (l,\min(p_j,i)],R \in [\max(p_k,i),r)\) 的区间 \([L,R]\) 都合法,相当于一个矩形覆盖矩形求和问题。想到了扫描线维护历史和,但是不会。

考虑一个经典套路:我们对于每个 \(i\) 枚举 \((l_i,i],[i,r_i)\) 较短的一段,得到形如 \(L = i,R \in [ql,qr]\) 或者 \(R = i,L \in [ql,qr]\) 的区间合法,这样的总区间数量是 \(\mathcal O(n \log n)\) 级别的。跑两遍扫描线,线段树维护历史和即可。时间复杂度 \(\mathcal O(n \log^2 n + q \log n)\)

感觉不是很难调啊,为啥我调了 2h qwq。

代码
/**
 *    author: sunkuangzheng
 *    created: 27.02.2024 15:14:05
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 2e6+5;
using namespace std;
int T,n,q,a[N],l[N],r[N],st[N],tp,ql,qr,pos[N],tg[N],mt[N]; ll t[N],ans[N]; vector<int> d[N];
vector<pair<int,int>> g1[N],g2[N];
void cg(int s,int l,int r,int k){t[s] += 1ll * (r - l + 1) * k,tg[s] += k;}
void pd(int s,int l,int r){int mid = (l + r) / 2; cg(s*2,l,mid,tg[s]),cg(s*2+1,mid+1,r,tg[s]),tg[s] = 0;}
void upd(int s,int l,int r,int ql,int qr){
    if(ql <= l && r <= qr) return cg(s,l,r,1),void();
    int mid = (l + r) / 2; pd(s,l,r);
    if(ql <= mid) upd(s*2,l,mid,ql,qr); if(qr > mid) upd(s*2+1,mid+1,r,ql,qr);
    t[s] = t[s*2] + t[s*2+1];
}ll qry(int s,int l,int r,int ql,int qr){
    if(ql <= l && r <= qr) return t[s];
    int mid = (l + r) / 2; ll ans = 0; pd(s,l,r);
    if(ql <= mid) ans += qry(s*2,l,mid,ql,qr);
    if(qr > mid) ans += qry(s*2+1,mid+1,r,ql,qr);
    return ans;
}void los(){
    cin >> n >> q;
    for(int i = 1;i <= n;i ++) cin >> a[i],pos[a[i]] = i;
    for(int i = 1;i <= n;i ++) for(int j = i;j <= n;j += i) d[j].push_back(i);
    for(int i = 1;i <= n;i ++){
        while(tp && a[st[tp]] < a[i]) tp --;
        l[i] = st[tp],st[++tp] = i;
    }tp = 0,st[++tp] = n + 1,a[n + 1] = n + 1; 
    for(int i = n;i >= 1;i --){
        while(tp && a[st[tp]] < a[i]) tp --;
        r[i] = st[tp],st[++tp] = i;
    }for(int i = 1;i <= n;i ++){
        if(i - l[i] <= r[i] - i){
            for(int j = l[i] + 1;j <= i;j ++) mt[j] = 1e9; 
            for(int j : d[a[i]]) if(pos[j] < r[i] && pos[a[i] / j] > pos[j]) 
                mt[min(i,pos[j])] = min(mt[min(i,pos[j])],pos[a[i] / j]);
            for(int j = i - 1;j > l[i];j --) mt[j] = min(mt[j],mt[j + 1]);
            for(int j = l[i] + 1;j <= i;j ++) g1[j].emplace_back(max(mt[j],i+(i==j)),r[i]-1);
            
        }else{
            for(int j = i;j <= r[i] - 1;j ++) mt[j] = 0;
            for(int j : d[a[i]]) if(pos[j] > l[i] && pos[a[i] / j] < pos[j]) 
                mt[max(i,pos[j])] = max(mt[max(i,pos[j])],pos[a[i] / j]);
            for(int j = i + 1;j < r[i];j ++) mt[j] = max(mt[j],mt[j - 1]);
            for(int j = i;j < r[i];j ++) g2[j].emplace_back(l[i]+1,min(i-(i==j),mt[j]));  
        }
    }for(int i = 1;i <= q;i ++) cin >> ql >> qr,g1[ql].emplace_back(qr,-i),g2[qr].emplace_back(ql,-i);
    for(int i = 1;i <= n;i ++)
        for(auto [l,r] : g2[i]){
            if(r < 0) ans[abs(r)] += qry(1,1,n,l,i);
            else if(l <= r) upd(1,1,n,l,r);
        }
    for(int i = 1;i <= 4 * n;i ++) t[i] = tg[i] = 0;
    for(int i = n;i >= 1;i --)
        for(auto [l,r] : g1[i]){
            if(r < 0) ans[abs(r)] += qry(1,1,n,i,l);
            else if(l <= r) upd(1,1,n,l,r);
        }
    for(int i = 1;i <= q;i ++) cout << ans[i] << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(141)\) ARC020C A mod B Problem

难度 \(^*1900\)矩阵快速幂;动态规划,DP

\(f_{i,j}\) 表示到了第 \(i\) 个数字,已经重复了 \(j\) 遍的结果,有 \(f_{i,0} = f_{i-1,l_{i-1}},f_{i,j}=d_if_{i,j-1}+a_i\),其中 \(d_i\) 表示数字 \(a_i\) 的位数。这样暴力转移是 \(\mathcal O(nl)\) 的。注意到 \(f_{i,j-1} \to f_{i,j}\) 可以用矩阵快速幂优化:

\[\begin{bmatrix}f_{i,j-1} & a_i\end{bmatrix} \times \begin{bmatrix}d_i & 0 \\ 1 & 1\end{bmatrix} = \begin{bmatrix}f_{i,j} & a_i\end{bmatrix} \]

总时间复杂度 \(\mathcal O(n \log l)\)

注意 \(d_i\) 要开 long long,否则会 WA #40。

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 07:53:01
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,mod,a[N],l[N],ans;
struct mat{
    int a[2][2];
    mat(){memset(a,0,sizeof(a));}
    int* operator [](int x){return a[x];}
    mat operator *(mat b){
        mat c;
        for(int i = 0;i <= 1;i ++) for(int k = 0;k <= 1;k ++) for(int j = 0;j <= 1;j ++) 
            c[i][j] = (c[i][j] + 1ll * a[i][k] * b[k][j]) % mod;
        return c;
    }
}f,p,k;mat qp(mat a,int b){
    mat r = a; b --;
    for(;b;b >>= 1,a = a * a) if(b & 1) r = r * a;
    return r;
}void los(){
    cin >> n;
    p[0][0] = p[1][1] = p[1][0] = 1;
    for(int i = 1;i <= n;i ++) cin >> a[i] >> l[i];
    cin >> mod;
    for(int i = 1;i <= n;i ++){
        int d = (ll)(pow(10,(int)log10(a[i]) + 1)) % mod;
        f[0][0] = ans,f[0][1] = a[i] % mod,p[0][0] = d;
        k = f * qp(p,l[i]),ans = k[0][0];
    }cout << ans << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(142)\) AGC041C Domino Quality

难度 \(^*2100\)构造

首先手玩出 \(n=4,5\) 的答案,注意到这两个答案的 quality 相同,于是所有 \(n \ge 12\) 或者 \(n=4,5,9,10\) 都可以用它们拼起来。\(n=1,2\) 显然无解,我们需要考虑 \(n=3,6,7,11\) 的情况。\(6\) 的情况样例已经给出,手玩 \(n=3,7\) 后发现 \(7,4\)quality 也相同,那么 \(11\)\(4,7\) 构造即可。代码乱写的,比较长。

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 08:59:48
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 2e3+5;
using namespace std;
int T,n;
string ans3[3] = {"a..","a..",".aa"};
string ans4[4] = {"abcc","abdd","ccab","ddab"};
string ans5[5] = {"aabbc","b.ddc","bd..a","ad..a","abbcc"};
string ans7[7] = {".aabbcc","a.dd.dd","ad..d..","bd..d..","b.dd.dd","cd..d..","cd..d.."};
void los(){
    cin >> n;
    vector<string> ans(n,string(n,'.'));
    if(n <= 2) return cout << -1,void();
    if(n >= 12 || n % 4 == 0 || n % 5 == 0){
        vector<int> con; 
        int cur = 0; int rn = n;
        while(n >= 4) n -= 4,con.push_back(4);
        for(int i = 0;i < n;i ++) con[i] = 5;
        for(int d : con){
            for(int i = 0;i < d;i ++) for(int j = 0;j < d;j ++)
                ans[i + cur][j + cur] = (d == 5 ? ans5[i][j] : ans4[i][j]);
            cur += d;
        }for(int i = 0;i < rn;i ++,cout << "\n") for(int j = 0;j < rn;j ++) cout << ans[i][j];
    }else if(n % 3 == 0){
        for(int k = 0;k < n;k += 3)
            for(int i = 0;i < 3;i ++) for(int j = 0;j < 3;j ++)
                ans[k + i][k + j] = ans3[i][j];
        for(int i = 0;i < n;i ++,cout << "\n") for(int j = 0;j < n;j ++) cout << ans[i][j];
    }else{
        for(int i = 0;i < 7;i ++) for(int j = 0;j < 7;j ++) ans[i][j] = ans7[i][j];
        if(n == 11) for(int i = 0;i < 4;i ++) for(int j = 0;j < 4;j ++)
                        ans[i+7][j+7] = ans4[i][j];
        for(int i = 0;i < n;i ++,cout << "\n") for(int j = 0;j < n;j ++) cout << ans[i][j];
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(143)\) AGC030C Coloring Torus

难度 \(^*2600\)构造

很厉害的构造题啊!为啥我一点思路都没有呢 /kel

首先考虑 \(k \le 500\) 的做法:直接第 \(i\) 行全填 \(i\),正确性显然。但是不太好拓展。

考虑把横着填变成对角线填,例如:

\(1\) \(2\) \(3\) \(4\)
\(2\) \(3\) \(4\) \(1\)
\(3\) \(4\) \(1\) \(2\)
\(4\) \(1\) \(2\) \(3\)

这样正确性不受影响,但还是只能处理 \(k \le 500\)

这时候很妙的思路是,注意到交替替换对角线上的元素也不影响正确性,例如:

\(1\) \(2\) \(\textbf{5}\) \(4\)
\(2\) \(3\) \(4\) \(1\)
\(\textbf{5}\) \(4\) \(1\) \(2\)
\(4\) \(1\) \(2\) \(\textbf{5}\)

然后我们一对对角线就可以填两个数字了,可以通过 \(k \le 1000\)

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 09:57:24
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 3e3+5;
using namespace std;
int T,n,k,ans[N][N];
void los(){
    cin >> k;
    if(k <= 500){
        cout << k << "\n";
        for(int i = 1;i <= k;i ++) for(int j = 1;j <= k;j ++) cout << i << " \n"[j == k];
    }else{
        cout << (n = 500) << "\n",k -= n;
        for(int i = 1;i <= n;i ++) for(int j = 1;j <= n;j ++)
            ans[i][j] = (j + i - 2) % n + 1;
        for(int i = 1;i <= n;i ++) for(int j = 1;j <= n;j ++)
            if(ans[i][j] <= k && (i & 1)) ans[i][j] += n;
        for(int i = 1;i <= n;i ++) for(int j = 1;j <= n;j ++) 
            cout << ans[i][j] << " \n"[j == n];
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(144)\) AGC031C Differ by 1 Bit

难度 \(^*2700\)分治;构造;位运算

首先考虑有解的必要条件:\(\operatorname{popcount(a \oplus b)} \bmod 2 = 1\),原因是 \(a\)\(b\)\(\operatorname{popcount}\) 奇偶性必然不同。

我们将通过构造证明该条件是充要的。不难发现 \(a \to b\) 的排列等价于 \(0 \to a \oplus b\) 的排列,下面采用类似数学归纳的方法证明:

  • 如果 \(n=1\),则 \(p = [0,1]\)
  • 假设我们已经能在 \(n-1\) 时对于任意 \(k \in [1,2^{n-1}-1]\) 构造出答案,考虑构造 \(0 \to t\) 的答案。
    • 如果 \(t < 2^{n-1}\),只需要在 \(n-1\) 时构造 \(0 \to t\) 的路径,并在任意一个位置插入后半段路径。考虑这样插入:\(0 \to 2^d \to \ldots \to (2^d \oplus x) \to x \to \ldots \to t\)
    • 否则 \(t \ge 2^{n-1}\),任选一个 \(x\),先构造 \(0 \to x\) 的路径,然后再选择 \(0 \to ((2^{n-1}+x) \oplus t)\) 的后半段路径拼起来,形如 \(0 \to \ldots \to x \to (2^{n-1}+x) \to \ldots \to ((2^{n-1}+x) \oplus t)\) 即可。

照此模拟,递归处理即可。时间复杂度 \(\mathcal O(n2^n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 11:13:04
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,x,y,b[N],ans[N];
void los(){
    cin >> n >> x >> y;
    if(__builtin_popcount(x) % 2 == __builtin_popcount(y) % 2) return cout << "NO\n",void();
    cout << "YES\n";
    auto sol = [&](auto self,int n,int t,int *ans) -> void {
        if(n == 1) return ans[0] = 0,ans[1] = 1,void();
        int d = (1 << n - 1);
        if(d & t){
            self(self,n-1,1,ans),self(self,n-1,t^(d+1),ans+d);
            for(int i = d;i < (1 << n);i ++) ans[i] ^= (d + 1);
        }else{
            self(self,n-1,t,ans),self(self,n-1,ans[1],ans+d);
            for(int i = 0;i < d;i ++) b[i] = ans[i];
            for(int i = 1;i <= d;i ++) ans[i] = ans[i + d - 1] ^ d;
            for(int i = d + 1;i < (1 << n);i ++) ans[i] = b[i - d];
        }
    }; sol(sol,n,x^y,ans);
    for(int i = 0;i < (1 << n);i ++) cout << (ans[i] ^ x) << " ";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(145)\) CF1286C2 Madhouse (Hard version)

难度 \(^*2800\)构造;交互

考虑简单版本,我们直接询问 \([1,n],[2,n]\),将 \([1,n]\) 的所有子集删去 \([2,n]\) 的所有子集,得到的就是 \(s\) 的所有前缀,按照长度排序输出即可。询问长度 \(n^2\)

考虑困难版本,我们询问 \([1,\dfrac{n}{2}-1],[1,\dfrac{n}{2}],[1,n]\) 这三段,可以确定下来 \([1,\dfrac{n}{2}]\) 的所有前缀。然后考虑所有长度为 \(2\) 的子串会覆盖除了 \(s_1,s_n\) 外的串恰好两次,而我们知道 \(s_1\),因此可以确定 \(s_n\)。考虑所有长度为 \(3\) 的子串,可以确定 \(s_{n-1}\),据此输出答案即可。总数量约为 \(0.75n^2\),可以通过。

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 15:14:27
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e2+5;
using namespace std;
int T,n; string len[N]; vector<string> g[N];
void los(){
    cin >> n;
    auto ask = [&](int l,int r){
        multiset<string> ans; string tmp;
        cout << "? " << l << " " << r << endl;
        for(int i = 1;i <= (r - l + 1) * (r - l + 2) / 2;i ++)
            cin >> tmp,sort(tmp.begin(),tmp.end()),ans.insert(tmp);
        return ans;
    }; 
    if(n == 1){
        cout << "? 1 1" << endl,cin >> len[0],cout << "! " << len[0] << endl;
    } string ans(n + 5,'a'); int m = (n <= 4 ? n : (n+1) / 2); 
    multiset<string> a1 = ask(1,m),a2 = ask(2,m); map<char,int> mp;
    for(auto i : a2) a1.erase(a1.find(i));
    for(auto i : a1) len[i.size()] = i; 
    for(int i = 1;i <= m;i ++){
        map<char,int> mp2;
        for(auto c : len[i]) mp2[c] ++;
        for(auto [x,y] : mp2) if(y > mp[x]) {ans[i] = x; break;}
        mp = mp2;
    }if(m == n){
        cout << "! ";
        for(int i = 1;i <= n;i ++) cout << ans[i];
    }else{
        multiset<string> a3 = ask(1,n);
        for(auto i : a3) g[i.size()].push_back(i);
        for(int i = 2;i <= n / 2 + 1;i ++){
            map<char,int> mp;
            for(auto j : g[i]) for(char c : j) mp[c] ++;
            for(int j = 1;j < i;j ++) mp[ans[j]] -= j;
            for(int j = 2;j < i;j ++) mp[ans[n - j + 2]] -= j - 1;
            for(auto [x,y] : mp) if(y % i == i - 1) {ans[n - i + 2] = x; break;}
        }cout << "! ";
        for(int i = 1;i <= n;i ++) cout << ans[i];
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(146)\) CF690A3 Collective Mindsets (hard)

难度 \(^*2600\)构造

人类智慧!智慧!智慧!智慧!智慧!智慧!智慧!智慧!

考虑第 \(i\) 个人猜 \(\sum a_i \bmod n = i-1\),这样一定有一个人猜的中总和,那么根据总和推出他要猜什么即可。

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 19:23:15
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,x,k;
void los(){
    cin >> n >> k;
    for(int i = 1;i < n;i ++) cin >> x,k = (k - x + n) % n;
    cout << (k ? k : n) << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(\color{blue}(147)\) AGC063C Add Mod Operations

难度 \(^*2700\)构造;数学

观察性质。观察性质。观察性质。观察性质。观察性质。观察性质。观察性质。

我咋啥都观察不出来 /ll 我咋啥都观察不出来 /ll 我咋啥都观察不出来 /ll

  • 无解情况

有解的必要条件是不存在 \(i,j\) 使得 \(a_i = a_j,b_i \ne b_j\)

事实上它充要,我们直接给出方案。

  • \(n = 1\)

\(x = \inf - a_1,y = \inf - b_1\) 即可。

  • 重要操作:\(y = x + \max\)

先将 \(a\) 升序排序。

我们可以通过一次 \((x,x + a_n)\) 来把序列变成 \([a_1+x_1,a_2+x_1,\ldots,a_{n-1}+x_1,0]\),重复 \(n\) 次后序列变为 \([0,x_n,x_n+x_{n-1},\ldots,\sum \limits_{i=2}^n x_i]\)

  • 简单情况:\(b_1 = 0\)\(b\) 单调不降。

此时注意到我们可以合理设置 \(x_i\) 使得操作后恰好 \(a=b\)

  • \(b_i \gets b_i + i \cdot \inf\)

把每一项加 \(\inf\) 就单调不降了!但是最后需要一次额外的 \(\bmod \inf\) 操作,正好需要 \(n+1\) 次操作。

  • 利用 \(x_1\)

发现上面我们没有用 \(x_1\),只是用它占一次操作。考虑只进行 \(n-1\)\(y = x + \max\),序列变为 \([\sum \limits_{i=1}^{n-1} x_i,0,x_n,x_n+x_{n-1},\ldots,\sum \limits_{i=2}^{n-1} x_i]\),我们合理设置 \(x_i\) 的值,最后再令 \(x = b_2,y = \inf\) 即可。当然注意一下要修改 \(b_i\) 加的 \(\inf\) 的系数,因为 \(a_1\) 是最大的。

时间复杂度 \(\mathcal O(n^2)\)。有一些小细节。

代码
/**
 *    author: sunkuangzheng
 *    created: 28.02.2024 21:48:24
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
const ll inf = 1e10;
using namespace std;
int T,n,fa[N],fb[N],fn,p[N];
ll a[N],b[N],c[N];
struct num{ll a,b;}t[N];
void los(){
    cin >> fn;
    for(int i = 1;i <= fn;i ++) cin >> fa[i];
    for(int i = 1;i <= fn;i ++) cin >> fb[i];
    for(int i = 1;i <= fn;i ++){
        bool ok = 1;
        for(int j = 1;j <= n;j ++){
            if(fa[i] == a[j] && fb[i] != b[j]) return cout << "No\n",void();
            if(fa[i] == a[j] && fb[i] == b[j]) ok = 0;
        }if(ok) a[++n] = fa[i],b[n] = fb[i],t[n] = {fa[i],fb[i]};
    }cout << "Yes\n";
    if(n == 1)
        return cout << "1\n" << (inf - a[1]) % (inf - b[1]) << " " << inf - b[1] << "\n",void();
    cout << n << "\n";
    sort(t+1,t+n+1,[&](num a,num b){return a.a < b.a;});
    c[1] = t[1].b + inf * n;
    for(int i = 2;i <= n;i ++) c[i] = t[i].b + inf * (i - 1); 
    ll x = c[1] - c[n] - t[1].a,y = x + t[n].a; 
    cout << x << " " << y << "\n";
    for(int j = 1;j <= n;j ++) t[j].a = (t[j].a + x) % y;
    for(int i = 2;i < n;i ++){
        ll x = c[n - i + 2] - c[n - i + 1],y = x + t[n - i + 1].a;
        cout << x << " " << y << "\n";
        for(int j = 1;j <= n;j ++) t[j].a = (t[j].a + x) % y;
    }cout << t[2].b << " " << inf << "\n";
    for(int j = 1;j <= n;j ++) t[j].a = (t[j].a + t[2].b) % inf;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(148)\) P4070 [SDOI2016] 生成魔咒

难度 \(^*2600\)字符串;后缀数组,SA

注意到往后面加一个字符会打乱后缀数组,但是往前面加则只会新增一个后缀。倒置整个串,先跑出后缀数组,然后维护现有的后缀的 \(rk\) 集合,每次查前驱后继,只有它们的 \(ht\) 会改变,维护即可。时间复杂度 \(\mathcal O(n \log n)\)

代码
/**
*    author: sunkuangzheng
*    created: 29.02.2024 09:38:40
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,s[N],sa[N],rk[N],ok[N],h[N],st[20][N];
int rk_lcp(int i,int j){
    if(i == j) return n - sa[i] + 1; 
    if(i > j) swap(i,j);
    int k = __lg(j - i);
    return min(st[k][i+1],st[k][j-(1<<k)+1]);
}void los(){
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> s[i];
    reverse(s+1,s+n+1);
    for(int i = 1;i <= n;i ++) sa[i] = i,rk[i] = s[i];
    for(int j = 1;j < n;j *= 2){
        for(int i = 1;i <= n;i ++) ok[i] = rk[i]; int p = 0;
        sort(sa+1,sa+n+1,[&](int x,int y){return rk[x] < rk[y] || rk[x] == rk[y] && rk[x + j] < rk[y + j];});
        auto cmp = [&](int x,int y){return ok[x] == ok[y] && ok[x + j] == ok[y + j];};
        for(int i = 1;i <= n;i ++) if(cmp(sa[i],sa[i-1])) rk[sa[i]] = p; else rk[sa[i]] = ++p; if(p == n) break;
    }for(int i = 1,k = 0;i <= n;h[rk[i ++]] = k) 
    for(k --,k = max(k,0);s[i + k] == s[sa[rk[i] - 1] + k];k ++);
    for(int i = 1;i <= n;i ++) st[0][i] = h[i];
    for(int j = 1;j <= __lg(n);j ++) for(int i = 1;i + (1 << j) - 1 <= n;i ++)
        st[j][i] = min(st[j-1][i],st[j-1][i+(1<<j-1)]);
    set<int> fk; ll sm = 0; 
    for(int i = n;i >= 1;i --){
        auto it = fk.lower_bound(rk[i]);
        if(it != fk.end()) sm -= h[*it],h[*it] = rk_lcp(*it,rk[i]),sm += h[*it];
        if(it != fk.begin()) h[rk[i]] = rk_lcp(rk[i],*(--it)),sm += h[rk[i]]; else h[rk[i]] = 0;
        fk.insert(rk[i]),cout << 1ll * (n - i + 1) * (n - i + 2) / 2 - sm << "\n";
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(149)\) P8631 [蓝桥杯 2015 国 AC] 切开字符串

难度 \(^*2800\)字符串;后缀数组,SA

我的题解

\(\color{blue}(150)\) [AGC058C] Planar Tree

难度 \(^*3000\)构造

寻找性质。

  • 性质一:可以缩掉相邻的相同数字,或者删除 \(2\) 旁边的 \(1\)\(3\) 旁边的 \(4\)

注意到这样连接不会破坏别的本来可以连的边,因此连了它没有影响。

  • 性质二:操作完的环上,如果 \(2\) 的数量大于 \(4\) 的数量,且 \(3\) 的数量大于 \(1\) 的数量,则有解,否则无解。

注意到 \(2\) 的旁边一定只能有 \(3,4\),我们可以把 \(2,4\) 一换一后,把 \(4\) 当作叶子。最终如果只剩下 \(2,3\) 则有解。

照此模拟即可。时间复杂度 \(\mathcal O(n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 29.02.2024 16:19:02
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],f[N],cnt,st[N],tp,ct[5];
void los(){
    cin >> n,cnt = 0; memset(ct,0,sizeof(ct));
    auto ck = [&](int x,int y){return x == y || 
        min(x,y) == 1 && max(x,y) == 2 || min(x,y) == 3 && max(x,y) == 4;};
    for(int i = 1;i <= n;i ++) if(cin >> a[i],!ck(f[cnt],a[i])) f[++cnt] = a[i]; 
                               else f[cnt] = (a[i] == 2 || a[i] == 3 ? a[i] : f[cnt]); 
    int l = 1,r = cnt;
    while(l < r && ck(f[l],f[r])) (f[l] == 2 || f[l] == 3 ? r -- : l ++);
    for(int i = l;i <= r;i ++) ct[f[i]] ++;
    if(ct[2] > ct[4] && ct[3] > ct[1]) cout << "Yes\n";
    else cout << "No\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(151 \sim 175\)

\(\color{blue}(151)\) [AGC045B] 01 Unbalanced

难度 \(^*2700\)贪心

\(0\) 看作 \(-1\),考虑前缀和后,问题等价于让极差最小。我们枚举 \(\max\) 的值,贪心的让 \(\min\) 最大,简单处理后可以 \(\mathcal O(n^2)\) 实现。

注意到 \(\max\) 增加 \(2\) 最多能让一个 \(-1 \to 1\),也就是 \(\min\) 最多增加 \(2\)。因此我们只需要考虑能到达的 \(\max\) 下界和下界 \(+1\),时间复杂度 \(\mathcal O(n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 29.02.2024 18:35:05
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e6+5;
using namespace std;
int T,n,sm[N],mx[N]; string s;
void los(){
    cin >> s,s = " " + s,n = s.size() - 1;
    for(int i = n;i >= 1;i --) sm[i] = max(0,sm[i + 1] + (s[i] == '1' ? 1 : -1));
    auto ck = [&](int li){
        int now = 0,mn = 0;
        for(int i = 1;i <= n;i ++){
            if(s[i] == '1') now ++;
            else if(s[i] == '0') now --;
            else
                if((now) + 1 + sm[i+1] <= li) now ++;
                else now --;
            mn = min(mn,now);
        }
        return li - mn;
    };cout << min(ck(sm[1]),ck(sm[1]+1));

}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(152)\) CF1936C Pokémon Arena

难度 \(^*2400\)图论建模;最短路

首先问题形如若干条描述,一条描述是 \(x\) 可以花费 \(y\) 代价打败 \(y\),这是最短路问题,但是边数为 \(\mathcal O(n^2)\) 级别。

对于每个属性建 \(2n\) 个虚点,一条左链一条右链,按照人的这个属性从小到大排序,左链边权为 \(0\) 表示属性强于它则可以直接击败,右链边权为差值。一个点连接左链和右链,从点到链的边权是 \(c_i\)

时间复杂度 \(\mathcal O(nm \log nm)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 01.03.2024 09:59:43
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 2e6+5;
using namespace std;
int T,n,m,c[N],p[N]; vector<int> a[N]; vector<pair<int,int>> g[N]; ll dis[N];
void los(){
    cin >> n >> m;
    auto id = [&](int x,int y,int k){return (x - 1) * m + y + k * n * m;};
    for(int i = 1;i <= n;i ++) cin >> c[i],a[i].resize(m + 1),p[i] = i;
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) cin >> a[i][j];
    auto add = [&](int x,int y,int w){g[x].emplace_back(y,w);};
    for(int i = 1;i <= n;i ++)
        for(int j = 1;j <= m;j ++) add(i+2*n*m,id(i,j,0),c[i]),add(id(i,j,1),i+2*n*m,0),add(id(i,j,0),id(i,j,1),0);
    for(int j = 1;j <= m;j ++){
        sort(p+1,p+n+1,[&](int x,int y){return a[x][j] < a[y][j];});
        for(int i = 1;i < n;i ++) add(id(p[i],j,0),id(p[i+1],j,0),a[p[i+1]][j] - a[p[i]][j]);
        for(int i = n - 1;i >= 1;i --) add(id(p[i+1],j,1),id(p[i],j,1),0);
    }for(int i = 1;i <= 2 * n * m + n;i ++) dis[i] = 1e18;
    dis[n + 2* n * m] = 0; priority_queue<pair<int,int>> q;
    q.emplace(0,n + 2 * n * m);
    while(q.size()){
        auto [w,u] = q.top(); q.pop();
        for(auto [v,w] : g[u]) if(dis[v] > dis[u] + w) dis[v] = dis[u] + w,q.emplace(-dis[v],v);
    }cout << dis[1 + 2 * n * m] << "\n";
    for(int i = 1;i <= 2 * n * m + n;i ++) g[i].clear();
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(\color{blue}(153)\) CF1217F Forced Online Queries Problem

难度 \(^*2600\)线段树分治;并查集

这个题目名字提示我们正解可能是非在线做法。

考虑 \(lst\) 只能是 \(0,1\),即是每次操作只有两种可能性。设一条边 \((u,v)\)\(t_1,t_2,\dots,t_k\) 涉及到,则 \((t_1,t_2),(t_2,t_3),\ldots,(t_{k-1},t_k)\) 每一个段内一定存在性相同。注意到线段树分治实际上是中序遍历递归树,那么我们在 \(t_i\) 时已经知道 \((t_i,t_{i+1})\) 时刻的存在性,据此加边即可。时间复杂度 \(\mathcal O(n \log^2 n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 01.03.2024 11:19:05
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,m,fa[N],siz[N],stx[N],sty[N],tp,ql[N],qr[N],ans[N],lst,id[N],x,y,op;
vector<pair<int,int>> t[N*4]; map<pair<int,int>,int> mp,fk; 
int fd(int x){return x == fa[x] ? x : fd(fa[x]);}
void mg(int x,int y){
    if(x = fd(x),y = fd(y),x == y) return ;
    if(siz[x] < siz[y]) swap(x,y); siz[x] += siz[y],fa[y] = x,
    stx[++tp] = x,sty[tp] = y;
}void fkk(){fa[sty[tp]] = sty[tp],siz[stx[tp]] -= siz[sty[tp]],tp --;}
void upd(int s,int l,int r,int ql,int qr,int x,int y){
    if(ql > qr) return ; 
    if(ql <= l && r <= qr) return t[s].emplace_back(x,y),void();
    int mid = (l + r) / 2;
    if(ql <= mid) upd(s*2,l,mid,ql,qr,x,y);
    if(qr > mid) upd(s*2+1,mid+1,r,ql,qr,x,y);
}void dfs(int s,int l,int r){
    int mid = (l + r) / 2,lp = tp;
    for(auto [x,y] : t[s]) if(mp[{x,y}]) mg(x,y);
    if(l == r){
        if(id[l]) cout << (lst = fd((ql[l] + lst - 1) % n + 1) == fd((qr[l] + lst - 1) % n + 1));
        else{
            int u = (ql[l] + lst - 1) % n + 1,v = (qr[l] + lst - 1) % n + 1;
            if(u > v) swap(u,v); mp[{u,v}] ^= 1;
        }
    }else dfs(s*2,l,mid),dfs(s*2+1,mid+1,r);
    while(tp > lp) fkk();
}void los(){
    cin >> n >> m;
    auto cg = [&](int &x,int &y){if(x > y) swap(x,y);};
    for(int i = 1;i <= n;i ++) fa[i] = i,siz[i] = 1;
    for(int i = 1;i <= m;i ++) if(cin >> op >> x >> y,ql[i] = x,qr[i] = y,op == 1){
        int tx = x,ty = y;
        if(x = x % n + 1,y = y % n + 1,cg(x,y),fk[{x,y}]) upd(1,1,m,fk[{x,y}] + 1,i - 1,x,y); 
        fk[{x,y}] = i;
        if(x = (tx-1) % n + 1,y = (ty-1) % n + 1,cg(x,y),fk[{x,y}]) upd(1,1,m,fk[{x,y}] + 1,i - 1,x,y); 
        fk[{x,y}] = i;
    }else id[i] = 1;
    for(auto [x,f] : fk) upd(1,1,m,f + 1,m,x.first,x.second);
    dfs(1,1,m);
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(154)\) CF1932G Moving Platforms

难度 \(^*2200\)最短路;数论;exgcd

注意到尽早到达一个点一定不劣,问题在于如何求解 \(u \to v\) 的边权。这是一个同余方程的形式,可以使用 exgcd 求解。

代码
/**
 *    author: sunkuangzheng
 *    created: 01.03.2024 15:06:14
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
#define int long long
using namespace std;
int T,n,m,h,l[N],s[N],u,v,vis[N]; vector<int> g[N]; ll dis[N];
void exgcd(int &x,int &y,int a,int b){
    if(!b) return x = 1,y = 0,void();
    exgcd(y,x,b,a % b),y -= (a / b) * x;
}ll inv(ll a,ll b){int x,y; exgcd(x,y,a,b),x = (x % b + b) % b;return x;}
ll sol(int x,int y,int h){
    x = (x % h + h) % h,y = (y % h + h) % h;
    if(!y) return (!x ? 0 : 1e18);
    ll k = gcd(x,y);
    x /= k,y /= k,h /= gcd(h,k);
    if(gcd(h,y) != 1) return 1e18;
    return inv(y,h) * (x % h) % h;
}void los(){
    cin >> n >> m >> h;
    for(int i = 1;i <= n;i ++) g[i].clear();
    for(int i = 1;i <= n;i ++) cin >> l[i];
    for(int i = 1;i <= n;i ++) cin >> s[i];
    for(int i = 1;i <= m;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u);
    for(int i = 2;i <= n;i ++) dis[i] = 1e18,vis[i] = 0; vis[1] = 0;
    priority_queue<pair<ll,int>> q; q.emplace(0,1);
    while(q.size()){
        auto [w,u] = q.top(); q.pop();
        if(vis[u]) continue; vis[u] = 1;
        for(int v : g[u]){
            if(dis[u] == 1e18) break; 
            int w = sol(l[v] + dis[u] % h * s[v] % h - (l[u] + dis[u] % h * s[u] % h),s[u] - s[v],h) + 1;
            if(dis[v] > dis[u] + w) dis[v] = dis[u] + w,q.emplace(-dis[v],v);
        }
    }cout << (dis[n] == 1e18 ? -1 : dis[n]) << "\n";
}signed main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(\color{blue}(155)\) AGC029C Lexicographic constraints

难度 \(^*2200\)二分;贪心

二分答案 \(mid\),贪心的确定每一个串。

  • 如果 \(a_i > a_{i-1}\),则直接在后面拼全 \(1\)
  • 否则,找到最后一个不为 \(mid\) 的位置 \(+1\),并把后面清空为 \(1\)

上面的操作等价于 \(mid\) 进制下的加法,用 map 维护,均摊复杂度 \(\mathcal O(n \log n)\),总复杂度 \(\mathcal O(n \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 01.03.2024 19:21:42
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],t;
void los(){
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    int l = 1,r = n;
    while(l <= r){
        int mid = (l + r) / 2;
        auto ck = [&](int x){
            map<int,int> mp;
            for(int i = 1;i <= n;i ++)
                if(a[i] <= a[i - 1]){
                    if(x == 1) return 0;
                    mp.erase(mp.upper_bound(a[i]),mp.end());
                    for(t = a[i];t > 0;t --)
                        if(++mp[t] == x) mp[t] = 0;
                        else break;
                    if(t <= 0) return 0;
                }
            return 1;
        };if(ck(mid)) r = mid - 1; else l = mid + 1;
    }cout << r + 1;
    // cout << ck(1);
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(156)\) CF1934E Weird LCM Operations

难度 \(^*3000\)数论;构造

我的题解

\(\color{blue}(157)\) CF1934D2 XOR Break — Game Version

难度 \(^*2400\)博弈论

我的题解

\(\color{blue}(158)\) P10217 [省选联考 2024] 季风

难度 \(^*2400\)数学;

我的题解

\(\color{blue}(159)\) ABC343G Compress Strings

难度 \(^*2600\)字符串;KMP;动态规划,DP

简单的考虑 DP:设 \(f_{s,i}\) 表示覆盖状态为 \(s\),结尾为 \(i\) 串的最小长度,我们转移时会遇到跨串匹配问题。注意到性质:如果 \(t\)\(s\) 的子串那么 \(t\) 是无用的,这样就不会出现跨串匹配问题。用 KMP 求出前后缀匹配的最长长度后状压 DP 即可。时间复杂度 \(\mathcal O(n2^n + n^2 \sum |s_i|)\)

\(\color{blue}(160)\) ABC271G Access Counter

难度 \(^*2600\)动态规划,DP;概率论;数学;矩阵

注意到 \(n\) 很大而 \(|s|\) 只有 \(24\),考虑矩阵优化 DP。这启示我们设 \(f_{i,j}\) 表示第 \(i\) 次访问是在 \(j\) 时刻的概率。考虑转移,枚举 \(i-1\) 的时刻 \(k\),我们应乘上概率 \(p(k,j)\) 表示从 \(k \to j\) 的方案数量。

\(c\) 表示 \(s\)\(\texttt T\) 的数量,\(p,q\) 表示 \(k+1 \to j-1\)\(\texttt{T,A}\) 的数量,概率是:

\[now \cdot \sum \limits_{i=0}^{\infty} (1-x)^{ci+p} \cdot (1-y)^{(24-c)i + q} \]

其中如果 \(s_j = \texttt T\)\(now = x\),否则 \(now = y\)

这是一个等比数列求和的形式:\(a \cdot \sum \limits_{i=0}^{\infty} d^i\),其中 \(a = (1-x)^p \cdot (1-y) ^q,d = (1-x)^{ci} \cdot (1-y) ^ {(24-c)i}\),可以快速计算。

然后套上矩阵即可。时间复杂度 \(\mathcal O(|s|^3 \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 08:34:44
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5,mod = 998244353;
using namespace std;
int T,x,y; ll n; string s;
struct mat{
    int a[24][24];
    mat(){memset(a,0,sizeof(a));};
    int* operator [](int x){return a[x];}
    mat operator *(mat b){
        mat c;
        for(int i = 0;i < 24;i ++) for(int j = 0;j < 24;j ++) for(int k = 0;k < 24;k ++)
            c[i][j] = (c[i][j] + 1ll * a[i][k] * b[k][j] % mod) % mod;
        return c;
    }void print(){
        for(int i = 0;i < 24;i ++) for(int j = 0;j < 24;j ++)
            cout << a[i][j] << " \n"[j == 23];
    }
}tr,in;
mat qp(mat a,ll b){
    mat r = a; b --;
    for(;b;b >>= 1,a = a * a) if(b & 1) r = r * a;
    return r;
}int pq(int a,ll b){
    int r = 1; 
    for(;b;b >>= 1,a = 1ll * a * a % mod) if(b & 1) r = 1ll * r * a % mod;
    return r;
}int inv(int x){return pq(x,mod - 2);}
void los(){
    cin >> n >> x >> y >> s,x = 1ll * x * inv(100) % mod,y = 1ll * y * inv(100) % mod;
    int p = (1 - x + mod) % mod,q = (1 - y + mod) % mod,c = 0,ans = 0;
    for(char ch : s) c += ch == 'T';
    int fk = inv((1 - 1ll * pq(p,c) * pq(q,24-c) % mod + mod) % mod);
    for(int i = 0;i < 24;i ++) for(int j = 0;j < 24;j ++){
        int a = 0,b = 0;
        if(j < i) for(int k = j+1;k < i;k ++) a += s[k] == 'T',b += s[k] == 'A';
        else{
            for(int k = j+1;k <= 23;k ++) a += s[k] == 'T',b += s[k] == 'A';
            for(int k = 0;k < i;k ++) a += s[k] == 'T',b += s[k] == 'A';
        }tr[j][i] = (1ll * (s[i] == 'T' ? x : y) % mod * pq(p,a) % mod * pq(q,b) % mod * fk % mod + mod) % mod;
    }in[0][23] = 1,in = in * qp(tr,n);
    // tr.print();
    for(int i = 0;i < 24;i ++) if(s[i] == 'A') ans = (ans + in[0][i]) % mod;
    cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(160)\) ABC295G Minimum Reachable City

难度 \(^*1900\)并查集

题目给出的原始有向图就是一棵叶向有根树,一次操作是把一条树上从后代到祖先的路径合并为一个强连通分量,并查集维护即可,时间复杂度均摊 \(\mathcal O(n \log n)\)

代码
#include<bits/stdc++.h>
const int N = 5e5+5;
using namespace std;
int T,n,fa[N],f[N],q,op,u,v;
int fd(int x){return x == f[x] ? x : f[x] = fd(f[x]);}
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n,f[1] = 1;
    for(int i = 2;i <= n;i ++) cin >> fa[i],f[i] = i;
    for(cin >> q;q --;)
        if(cin >> op >> u,op == 1) for(cin >> v,v = fd(v);u = fd(u),v != u;) f[u] = v,u = fa[u];
        else cout << fd(u) << "\n";
}

\(\color{blue}(161)\) ABC238G Cubic?

难度 \(^*2100\)数学;哈希

和 SNOI2024 平方数的部分分思路几乎一样。每个质因子我们只关心其次数模 \(3\) 的值,用三进制哈希维护。

代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
using ll = int;
const int N = 5e5+5,mod = 1e12+13,M = 2e6+5;
map<int,vector<int>> pm; map<ll,int> mp;
int n,pw[M],cnt,hsh,q,rsh[N],runti,l,r; ll x,ans,a[N]; vector<ll> p[N]; int ip[N];
int qp(int a,int b){
    int r = 1;
    for(;b;b >>= 1,a = 1ll * a * a % mod) if(b & 1) r = 1ll * r * a % mod;
    return r;
}signed main(){
    //freopen("square.in","r",stdin),freopen("square.out","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> q,pw[0] = 1;
    for(int i = 1;i < M;i ++) pw[i] = pw[i - 1] * 3ll % mod;
    for(int i = 1;i <= n;i ++){
        cin >> a[i]; ll x = a[i];
        for(ll j = 2;j * j <= x;j ++){
            if(x % j == 0) mp[j] = 1;
            while(x % j == 0) x /= j,p[i].push_back(j),++runti;
        }if(x > 1) mp[x] = 1,p[i].push_back(x);
    }for(auto [x,y] : mp) mp[x] = ++cnt;
    rsh[0] = 0;
    for(int i = 1;i <= n;i ++){
        for(int j : p[i]){
            j = mp[j]; 
            if(ip[j] != 2) hsh = (hsh + pw[j]) % mod,ip[j] ++;
            else hsh = (hsh - 2ll * pw[j] + mod * 2ll) % mod,ip[j] = 0;
        }rsh[i] = hsh;
    }
    while(q --) cin >> l >> r,cout << (rsh[l-1] == rsh[r] ? "Yes\n" : "No\n");
}

\(\color{blue}(162)\) ABC289G Shopping in AtCoder store

难度 \(^*2500\)分治

贪心的考虑,我们的定价 \(p\) 一定是 \(a_i + b_j\) 中的某个,且从大到小排序后收益是 \((a_i+b_j) \cdot i\)

感觉这道题不太好注意到决策单调性。把所有询问的 \(b\) 放在一起考虑,较小的 \(b\) 选择的 \(i\) 一定比较大的 \(b\) 选择的 \(i\) 靠后。然后分治即可,时间复杂度 \(\mathcal O(n \log n)\)

代码
#include<bits/stdc++.h>
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],m,x,b[N],p[N]; ll ans[N];
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    sort(a+1,a+n+1,[&](int x,int y){return x > y;});
    for(int i = 1;i <= m;i ++) cin >> b[i],p[i] = i;
    sort(p+1,p+m+1,[&](int x,int y){return b[x] < b[y];});
    auto sol = [&](auto self,int l,int r,int ql,int qr) -> void {
        if(l > r) return ;
        int mid = (l + r) / 2; ll res = -1,q = -1;
        for(int i = ql;i <= qr;i ++) if(ll d = 1ll * i * (a[i] + b[p[mid]]);d > res) res = d,q = i;
        ans[p[mid]] = res,self(self,l,mid-1,ql,q),self(self,mid+1,r,q,qr);
    };sol(sol,1,m,1,n);
    for(int i = 1;i <= m;i ++) cout << ans[i] << " ";
}

\(\color{blue}(163)\) ABC229G Longest Y

难度 \(^*2300\)二分;贪心

显然答案有单调性,二分答案 \(len\),如果存在一个交换方案使得代价 \(\le k\) 则合法。

考虑怎么计算答案,贪心的让两边的 \(\texttt Y\) 往中间移动,由初中数学知识得,一种最优解为移动到中点,那么维护 \(\texttt Y\) 位置的前缀和即可,时间复杂度 \(\mathcal O(n \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 13:13:30
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,num[N],tot,id[N]; ll k,sum[N]; string s;
void los(){
    cin >> s >> k,n = s.size(),s = " " + s;
    int l = 0,r = n; 
    for(int i = 1;i <= n;i ++) if(s[i] == 'Y') id[i] = ++tot,sum[tot] = sum[tot - 1] + i;
                               else id[i] = tot+1;
    while(l <= r){
        int mid = (l + r) / 2;
        auto ck = [&](int x){
            auto cost = [&](int i){
                int di = id[i],l = x / 2,r = x - x / 2;
                if(di <= l || di + r - 1 > tot) return k + 1;
                auto sm = [&](int l,int r){return 1ll * (l + r) * (r - l + 1) / 2;};
                return sm(i-l,i-1)-(sum[di-1]-sum[di-l-1])+sum[di+r-1]-sum[di-1]-sm(i,i+r-1);
            };
            for(int i = 1;i <= n;i ++) if(cost(i) <= k) return 1;      
            return 0;
        };
        if(ck(mid)) l = mid + 1; else r = mid - 1;
    }cout << l - 1;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(163)\) ABC223G Vertex Deletion

难度 \(^*2400\)动态规划,DP

学到了贪心法求树上二分图最大匹配,从叶子开始,每个点贪心的匹配父亲。

换根部分考虑删除一个和儿子匹配的点一定会使匹配数量 \(-1\),因为儿子已经尽可能的和其子树点匹配。如果删除一个和父亲匹配的点,匹配不变的条件是父亲不是强制匹配自己。

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 14:26:34
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],u,v,ans; vector<int> g[N];
void dfs1(int u,int f){for(int v : g[u]) if(v != f) dfs1(v,u),a[u] += !a[v];}
void dfs2(int u,int f,bool ok){
    ans += ok && !a[u];
    for(int v : g[u]) if(v != f) dfs2(v,u,!ok || (a[u] - !a[v]));
}void los(){
    cin >> n;
    for(int i = 1;i < n;i ++) cin >> u >> v,g[u].push_back(v),g[v].push_back(u);
    dfs1(1,0),dfs2(1,0,1),cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(164)\) ABC245G Foreign Friends

难度 \(^*2200\)最短路;进制

刚开始以为这是一道分块题,想着想着分块就想到了二进制分组,然后就会了。

按颜色二进制分组即可。

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 15:26:37
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,m,k,l,a[N],b[N],u,v,w,vis[N]; ll ans[N],dis[N]; vector<pair<int,int>> g[N];
void los(){
    cin >> n >> m >> k >> l;
    for(int i = 1;i <= n;i ++) cin >> a[i],ans[i] = 1e18;
    for(int i = 1;i <= l;i ++) cin >> b[i];
    for(int i = 1;i <= m;i ++) cin >> u >> v >> w,g[u].emplace_back(v,w),g[v].emplace_back(u,w);
    auto sol = [&](int x,int y){
        for(int i = 1;i <= n;i ++) vis[i] = 0,dis[i] = 1e18; priority_queue<pair<ll,int>> q;
        for(int i = 1;i <= l;i ++) if(((a[b[i]] >> x) & 1) == y) dis[b[i]] = 0,q.emplace(0,b[i]);
        while(q.size()){
            auto [w,u] = q.top(); q.pop();
            if(vis[u]) continue; vis[u] = 1;
            for(auto [v,w] : g[u]) if(dis[u] + w < dis[v]) dis[v] = dis[u] + w,q.emplace(-dis[v],v);
        }for(int i = 1;i <= n;i ++) if(((a[i] >> x) & 1) ^ y) ans[i] = min(ans[i],dis[i]);
    };
    for(int i = 0;i <= __lg(k);i ++) sol(i,0),sol(i,1);
    for(int i = 1;i <= n;i ++) cout << (ans[i] == 1e18 ? -1 : ans[i]) << " ";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(165)\) ABC227G Divisors of Binomial Coefficient

难度 \(^*2200\)数论;筛法

注意到 \(\dbinom{n}{k} = \dfrac{n \times (n-1) \times \ldots \times (n-k+1)}{k!}\),我们希望分解出分子分母的质因数然后计算约数,但这样的复杂度是 \(\mathcal O(k\sqrt n)\),显然不能通过。

考虑用 \(1 \sim \sqrt n\) 的质数去筛 \([n-k+1,n]\) 的数,复杂度为调和级数的 \(\mathcal O(k \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 15:53:20
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e6+5;
using namespace std;
int T,k; ll n; vector<int> a[N],fk[N]; unordered_map<ll,int> mp;
void los(){
    cin >> n >> k;
    ll l = n - k + 1,r = n;
    for(ll i = 2;i * i <= r;i ++){
        if(fk[i].size()) continue; 
        for(ll j = i;j * j <= r || j <= k;j += i) fk[j].push_back(i);
        for(ll j = (l + i - 1) / i * i;j <= r;j += i)
            a[j - l + 1].push_back(i);
    }
    auto fkf = [&](ll tmp,vector<int> a,int p){
        for(auto x : a) while(tmp % x == 0) mp[x] += p,tmp /= x;
        if(tmp != 1) mp[tmp] += p; 
    };
    for(ll i = l;i <= r;i ++) fkf(i,a[i-l+1],1);
    for(int i = 1;i <= k;i ++) fkf(i,fk[i],-1);
    int res = 1;
    for(auto [x,y] : mp) res = 1ll * res * (y + 1) % 998244353;
    cout << res;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(166)\) ABC247G Dream Team

难度 \(^*2300\)网络流;二分图;费用流

对于一个点 \((x,y)\),如果选了它则第 \(x\) 行第 \(y\) 列都不能再选其它点,如果把第 \(x\) 行向第 \(y\) 列连边,这是一个二分图最大匹配模型。跑一遍最大流即可求出 \(k\) 的值。

考虑第二问其实就是二分图最大权匹配,直接暴力跑 \(k\) 遍费用流。令 \(N = 300\),时间复杂度 \(\mathcal O(N^3n)\),不知道能不能过。

使用 acl 的费用流,单次时间复杂度为 \(\mathcal O(Nn \log n)\),总复杂度 \(\mathcal O(N^2n\log n)\),可以通过。

其实就是懒得写费用流捏

代码
#include<bits/stdc++.h>
#include <atcoder/all>
using ll = long long;
const int N = 5e5+5;
const ll inf = 1e10;
using namespace std;
using namespace atcoder;
int T,n,a[N],b[N],c[N],s,ss,tt,x;
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> a[i] >> b[i] >> c[i];
    auto build = [&](int lim){
        mcf_graph<int,ll> g(302);
        ss = 0,tt = 301;
        for(int i = 1;i <= 150;i ++) g.add_edge(ss,i,1,0),g.add_edge(i+150,tt,1,0);
        for(int i = 1;i <= n;i ++) g.add_edge(a[i],b[i]+150,1,inf - c[i]);
        return g.flow(ss,tt,lim);
    };
    cout << (x = build(1e9).first) << "\n";
    for(int i = 1;i <= x;i ++) cout << - build(i).second + inf * i << "\n";
}

\(\color{blue}(167)\) ABC339G Smaller Sum

难度 \(^*1900\)可持久化线段树

谔谔。

\(\color{blue}(168)\) ABC225G X

难度 \(^*2600\)网络流;最小割

考虑最小割模型:先获得每个点 \(a_{i,j}\) 的收益,然后如果选则 \(-2c\),否则 \(-a_{i,j}\)。如果一个点和左上同时选有 \(c\) 的收益,和右上同时选有 \(c\) 的收益,这是经典模型,最小割解决。

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 19:44:48
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
#include <atcoder/all>
using ll = long long;
const int N = 1e2+5;
using namespace std;
using namespace atcoder;
int T,n,m,c,a[N][N];
void los(){
    cin >> n >> m >> c; ll ans = 0;
    mf_graph<ll> g(3 * n * m + 2); int s = 0,t = 3 * n * m + 1;
    auto id = [&](int x,int y){return (x - 1) * m + y;};
    for(int i = 1;i <= n;i ++) for(int j = 1;j <= m;j ++) 
        cin >> a[i][j],ans += a[i][j],g.add_edge(s,id(i,j),2*c),g.add_edge(id(i,j),t,a[i][j]);
    for(int i = 2;i <= n;i ++) for(int j = 1;j <= m;j ++){
        int d1 = id(i,j) + n * m; g.add_edge(d1,t,c);
        if(j != 1) g.add_edge(id(i,j),d1,1e9),g.add_edge(id(i-1,j-1),d1,1e9),ans += c;
        d1 = id(i,j) + 2 * n * m; g.add_edge(d1,t,c);
        if(j != m) g.add_edge(id(i,j),d1,1e9),g.add_edge(id(i-1,j+1),d1,1e9),ans += c;
    }cout << ans - g.flow(s,t);
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(169)\) ABC254F Rectangle GCD

难度 \(^*1900\)最大公约数,GCD;差分

考虑按行差分,式子变为 \(\gcd\{a_1+b_1,a_1+b_2,\ldots,a_1+b_n,a_2-a_1,a_3-a_2,\ldots,a_n-a_{n-1}\}\)。再按列差分,不难发现第一段也变成了 \(b_i - b_{i-1}\) 的形式,ST 表维护差分数组的区间 \(\gcd\) 即可。

代码
/**
 *    author: sunkuangzheng
 *    created: 04.03.2024 21:37:15
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],b[N],fa[N],fb[N],q,l1,r1,l2,r2;
struct st{
    int st[20][N];
    void build(int *a){
        for(int i = 1;i <= n;i ++) st[0][i] = a[i];
        for(int j = 1;j <= __lg(n);j ++) for(int i = 1;i + (1 << j) - 1 <= n;i ++)
            st[j][i] = gcd(st[j-1][i],st[j-1][i+(1<<j-1)]); 
    }int qry(int l,int r){
        if(l > r) return 0;
        int k = __lg(r - l + 1);
        return gcd(st[k][l],st[k][r-(1<<k)+1]);
    }
}A,B;
void los(){
    cin >> n >> q;
    for(int i = 1;i <= n;i ++) cin >> a[i],fa[i] = a[i] - a[i-1]; A.build(fa);
    for(int i = 1;i <= n;i ++) cin >> b[i],fb[i] = b[i] - b[i-1]; B.build(fb);
    while(q --)
        cin >> l1 >> r1 >> l2 >> r2,
        cout << gcd(a[l1] + b[l2],gcd(A.qry(l1+1,r1),B.qry(l2+1,r2))) << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(170)\) CF587F Duff is Mad

难度 \(^*3000\)字符串;AC 自动机;根号分治

考虑差分询问,然后根号分治。

  • 对于 \(|s_k| \le B\),把 \(s_{1 \ldots k}\) 的子树加 \(1\),然后暴力把 \(s_k\) 放在自动机上跑做单点求和。
  • 对于 \(|s_k| > B\),考虑串只有 \(\mathcal O(\sqrt n)\) 种,直接对于每一种把 \(s_k\) 放在自动机上单点加,然后处理出每个前缀的答案和。

时间复杂度 \(\mathcal O(n \sqrt n \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 08.03.2024 14:14:52
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
#define int long long
const int N = 1e5+5,B = 1000;
using namespace std;
int T,n,q,ch[N][26],tot,fa[N],ed[N],re,dcnt,id[N],ans[N],t[N],l,r,k,sm[N],cnt,qid[N],dfn[N],siz[N]; 
string s[N]; vector<int> g[N];
int res[102][N]; vector<pair<int,int>> qu[N];
void upd(int x,int p){for(;x <= dcnt + 1;x += x & -x) t[x] += p;}
int qry(int x){for(re = 0;x;x -= x & -x) re += t[x]; return re;}
void rupd(int l,int r,int k){upd(l,k),upd(r+1,-k);}
void los(){
    cin >> n >> q;
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        ed[x] = s;
    };auto build = [&](){
        queue<int> q;  
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop();
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
    }; for(int i = 1;i <= n;i ++) cin >> s[i],ins(i,s[i]); build();
    auto dfs1 = [&](auto self,int u) -> void {
        for(int v : g[u]) self(self,v),sm[u] += sm[v];
    };auto dfs2 = [&](auto self,int u) -> void {
        dfn[u] = ++dcnt,siz[u] = 1; for(int v : g[u]) self(self,v),siz[u] += siz[v];
    };for(int i = 1;i <= n;i ++)
        if(s[i].size() >= B){
            id[++cnt] = i,qid[i] = cnt; int p = 0;
            for(char c : s[i]) p = ch[p][c - 'a'],sm[p] ++;
            dfs1(dfs1,0);
            for(int j = 1;j <= n;j ++) res[cnt][j] = res[cnt][j-1] + sm[ed[j]];
            memset(sm,0,sizeof(sm));
        }
    dfs2(dfs2,0);
    for(int i = 1;i <= q;i ++){
        cin >> l >> r >> k;
        if(s[k].size() <= B) qu[l-1].emplace_back(k,-i),qu[r].emplace_back(k,i);
        else ans[i] = res[qid[k]][r] - res[qid[k]][l-1]; 
    }for(int i = 1;i <= n;i ++){
        rupd(dfn[ed[i]],dfn[ed[i]] + siz[ed[i]] - 1,1);
        for(auto [j,id] : qu[i]){
            int p = 0,anss = 0;
            for(char c : s[j]) p = ch[p][c - 'a'],anss += qry(dfn[p]);
            ans[abs(id)] += (id > 0 ? 1 : -1) * anss;
        }
    }for(int i = 1;i <= q;i ++) cout << ans[i] << "\n";
}signed main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(171)\) P7582 「RdOI R2」风雨

难度 \(^*3100\)字符串;AC 自动机;分块

怎么都没想到对每个块建 AC 自动机。

考虑没有修改的情况。对于每个整块,把 \(S\) 放在块的 AC 自动机上跑,对块内串的每个结尾点子树加后我们把 \(S\) 经过的点做单点求和。对于散块暴力做即可。

对于有修改的情况,每个块维护 \(tg_1,tg_2\) 表示加法和推平 tag。考虑如果对半个块修改就对 tag 重构,整块修改处理 tag 间的关系是简单的。查询时额外维护根到点的点数量即可处理 tag 对答案的影响。

时间复杂度 \(\mathcal O(n \sqrt n \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 08.03.2024 12:01:44
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
#define int long long
const int N = 2e5+5,B = 330;
using namespace std;
int T,n,ed[N],ch[N][3],tot,fa[N],m,a[N],id[N],al[N],ar[N],op,l,r,k,ct[N],cnt,dfn[N],siz[N],re; 
string s,t; vector<int> g[N];
struct fen{
    int t[N];
    void upd(int x,int p){for(;x <= tot + 1;x += x & -x) t[x] += p;}
    int qry(int x){for(re = 0;x;x -= x & -x) re += t[x]; return re;}
    void rupd(int l,int r,int k){upd(l,k),upd(r+1,-k);}
}t1,t2;
struct sol{
    int rt,tg1,tg2,pl,pr;
    void ins(int x,string t){
        int s = rt; 
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        ed[x] = s,ct[s] ++;
    }void build(){
        queue<int> q;  fa[rt] = rt;
        for(int i = 0;i < 3;i ++) if(ch[rt][i]) q.push(ch[rt][i]),fa[ch[rt][i]] = rt; else ch[rt][i] = rt;
        while(q.size()){
            int u = q.front(); q.pop(); g[fa[u]].push_back(u);
            for(int i = 0;i < 3;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }
    }void dfs(int u,int f){
        ct[u] += ct[f],dfn[u] = ++cnt,siz[u] = 1;
        for(int v : g[u]) dfs(v,u),siz[u] += siz[v];
    }int getall(int u){
        if(tg2) return ct[u] * tg2;
        return t1.qry(dfn[u]) + tg1 * ct[u];
    }int qryall(string &t){
        int ans = 0,p = rt;
        for(char c : t) p = ch[p][c - 'a'],ans += getall(p);
        return ans;
    }int bf(int l,int r,string &t){
        int ans = 0,p = rt; vector<int> nd;
        for(char c : t) p = ch[p][c - 'a'],t2.upd(dfn[p],1),nd.push_back(dfn[p]);
        for(int i = l;i <= r;i ++) ans += (tg2 ? tg2 : a[i] + tg1) * (t2.qry(dfn[ed[i]] + siz[ed[i]] - 1) - t2.qry(dfn[ed[i]] - 1));
        for(int x : nd) t2.upd(x,-1); return ans;
    }void md(int op,int k){ // 0 推平 1 修改
        if(!op) tg1 = 0,tg2 = k;
        else if(tg2) tg2 += k; else tg1 += k; 
    }void remake(int op,int l,int r,int k){
        for(int i = pl;i <= pr;i ++) t1.rupd(dfn[ed[i]],dfn[ed[i]] + siz[ed[i]] - 1,-a[i]),
                                     a[i] = (tg2 ? tg2 : tg1 + a[i]);
        tg2 = tg1 = 0;
        for(int i = l;i <= r;i ++) a[i] = (op ? a[i] + k : k);
        for(int i = pl;i <= pr;i ++) t1.rupd(dfn[ed[i]],dfn[ed[i]] + siz[ed[i]] - 1,a[i]);
    }void init(int id){
        tg1 = tg2 = 0,build(),dfs(rt,0),pl = al[id],pr = ar[id];
        for(int i = pl;i <= pr;i ++) t1.rupd(dfn[ed[i]],dfn[ed[i]] + siz[ed[i]] - 1,a[i]);
    }
}bl[B];
void los(){
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) id[i] = (i - 1) / B + 1,al[id[i]] = (al[id[i]] ? al[id[i]] : i),ar[id[i]] = i;
    int ott = id[n]; 
    for(int i = 1;i <= ott;i ++) bl[i].rt = ++tot;
    for(int i = 1;i <= n;i ++) cin >> s >> a[i],bl[id[i]].ins(i,s);
    for(int i = 1;i <= ott;i ++) bl[i].init(i);
    while(m --){
        cin >> op >> l >> r;
        int p = id[l],q = id[r];
        if(op == 1 || op == 2){
            op = 2 - op,cin >> k;
            if(p == q) bl[p].remake(op,l,r,k);
            else{
                bl[p].remake(op,l,ar[p],k),bl[q].remake(op,al[q],r,k);
                for(int i = p + 1;i <= q - 1;i ++) bl[i].md(op,k);
            }
        }else{
            cin >> t;
            if(p == q) cout << bl[p].bf(l,r,t) << "\n";
            else{
                int ans = bl[p].bf(l,ar[p],t) + bl[q].bf(al[q],r,t);
                for(int i = p + 1;i <= q - 1;i ++) ans += bl[i].qryall(t);
                cout << ans << "\n";
            }
        }
    }
}signed main(){    
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(172)\) P8147 [JRKSJ R4] Salieri

难度 \(^*3100\)字符串;AC 自动机;虚树;可持久化线段树

考虑有大量的链上 \(cnt\) 相等,我们把虚树建出来就能得到这些链。二分答案 \(x\),对于一条 \(cnt = y\) 的链相当于问有多少 \(j\) 满足 \(j \cdot y > x\),可持久化线段树维护。

\(p = \sum |S_i|\),时间复杂度 \(\mathcal O(p \log p \log V)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 07.03.2024 20:29:29
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,m,ch[N][4],tot,fa[N],x,cnt,dfn[N],siz[N],k,st[20][N],md[N],ndcnt,rt[N]; vector<int> ed[N],g[N]; string s;
struct tr{int l,r,w;}t[N*24];
void los(){
    cin >> n >> m,tot = 1;
    auto ins = [&](int x,string t){
        int s = 1; 
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        ed[s].push_back(x);
    };auto build = [&](){
        queue<int> q;  fa[1] = 1;
        for(int i = 0;i < 4;i ++) if(ch[1][i]) q.push(ch[1][i]); else ch[1][i] = 1;
        while(q.size()){
            int u = q.front(); q.pop(); fa[u] = max(1,fa[u]);
            for(int i = 0;i < 4;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 2;i <= tot;i ++) g[fa[i]].push_back(i);
    }; for(int i = 1;i <= n;i ++) cin >> s >> x,ins(x,s); build();
    auto upd = [&](auto self,int s,int l,int r,int x) -> int {
        int p = ++ndcnt,mid = (l + r) / 2; t[p] = t[s],t[p].w ++;
        if(l == r) return p;
        if(x <= mid) t[p].l = self(self,t[p].l,l,mid,x);
        else t[p].r = self(self,t[p].r,mid+1,r,x);
        return p;
    };auto qry = [&](auto self,int u,int v,int l,int r,int x){
        if(x > r) return 0;
        if(l == r) return t[v].w - t[u].w;
        int mid = (l + r) / 2;
        if(x <= mid) return t[t[v].r].w - t[t[u].r].w + self(self,t[u].l,t[v].l,l,mid,x);
        else return self(self,t[u].r,t[v].r,mid+1,r,x);
    };
    auto dfs = [&](auto self,int u,int f) -> void { 
        st[0][dfn[u] = ++cnt] = f,rt[u] = rt[f];
        for(int i : ed[u]) rt[u] = upd(upd,rt[u],0,1000,i); 
        for(int v : g[u]) self(self,v,u); 
    };dfs(dfs,1,0);  
    auto cmp = [&](int u,int v){return dfn[u] < dfn[v] ? u : v;};
    for(int j = 1;j <= __lg(cnt);j ++) for(int i = 1;i + (1 << j) - 1 <= cnt;i ++)
        st[j][i] = cmp(st[j-1][i],st[j-1][i+(1<<j-1)]);
    auto lca = [&](int u,int v){
        if(u == v) return u;
        if((u = dfn[u]) > (v = dfn[v])) swap(u,v);
        int k = __lg(v - u);
        return cmp(st[k][u+1],st[k][v-(1<<k)+1]);
    };for(int i = 0;i <= tot;i ++) g[i].clear();
    while(m --){
        int p = 1;
        cin >> s >> k; vector<int> a; a.push_back(1);
        for(char c : s) p = max(1,ch[p][c - 'a']),a.push_back(p),md[p] ++;
        sort(a.begin(),a.end(),[&](int x,int y){return dfn[x] < dfn[y];});
        int d = a.size();
        for(int i = 1;i < d;i ++) a.emplace_back(lca(a[i-1],a[i]));
        sort(a.begin(),a.end(),[&](int x,int y){return dfn[x] < dfn[y];});
        a.erase(unique(a.begin(),a.end()),a.end());
        for(int i = 1;i < a.size();i ++) g[lca(a[i-1],a[i])].emplace_back(a[i]);
        reverse(a.begin(),a.end());
        for(int u : a){
            siz[u] = md[u];
            for(int v : g[u]) siz[u] += siz[v];
        }int l = 0,r = 1e9;
        while(l <= r){
            int mid = (l + r) / 2;
            auto ck = [&](int x){
                int sum = 0;
                for(int u : a) for(int v : g[u]) sum += qry(qry,rt[u],rt[v],0,1000,x / siz[v] + 1);
                return sum < k; 
            };if(ck(mid)) r = mid - 1; else l = mid + 1;
        }cout << r + 1 << "\n";
        for(int i : a) g[i].clear(),md[i] = 0;
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(173)\) CF86C Genetic engineering

难度 \(^*2500\)字符串;AC 自动机;动态规划,DP

注意到 \(|s_i|\) 很小,设 \(f_{i,j,k}\) 表示到位置 \(i\),走到 ACAM 上节点 \(j\),最后 \(k\) 个字符未覆盖的方案书,转移讨论即可。

代码
/**
 *    author: sunkuangzheng
 *    created: 07.03.2024 10:33:12
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e3+5,mod = 1e9+9;
using namespace std;
int T,n,ed[N],ch[N][26],fa[N],tot,f[N][105][12],m,le[N]; string s;
void los(){
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - 'A'] ? ch[s][c - 'A'] : (ch[s][c - 'A'] = ++tot));
        ed[s] = t.size();
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] = max(ed[u],ed[fa[u]]);
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }
    };cin >> n >> m; int ans = 0;
    for(int i = 1;i <= m;i ++) cin >> s,ins(i,s),le[i] = s.size(); build();
    f[0][0][0] = 1; auto add = [&](int &x,int y){if(x += y,x >= mod) x -= mod;};
    for(int i = 0;i < n;i ++) for(int j = 0;j <= tot;j ++) for(int l = 0;l <= 10;l ++) 
        for(int k = 0;k < 26;k ++) 
            add(f[i+1][ch[j][k]][(ed[ch[j][k]] >= l + 1 ? 0 : l + 1)],f[i][j][l]);
    for(int i = 0;i <= tot;i ++) add(ans,f[n][i][0]);
    cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(174)\) CF86D Powerful array

难度 \(^*2100\)莫队

莫队板子题。

代码
/**
 *    author: sunkuangzheng
 *    created: 07.03.2024 10:01:35
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5,B = 305;
using namespace std;
struct tii{int l,r,id;};
int T,n,ct[N*4],q,a[N],l,r; vector<tii> g; ll ans,res[N]; 
void los(){
    cin >> n >> q;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    for(int i = 1;i <= q;i ++) cin >> l >> r,g.emplace_back(l,r,i);
    sort(g.begin(),g.end(),[&](tii a,tii b){return a.l / B == b.l / B ? a.r < b.r : a.l < b.l;});
    int R = 0,L = 1;
    auto ins = [&](int x,int p){ans -= 1ll * ct[x] * ct[x] * x,ct[x] += p,ans += 1ll * ct[x] * ct[x] * x;};
    for(auto [l,r,id] : g){
        while(R < r) ins(a[++R],1);
        while(L > l) ins(a[--L],1);
        while(R > r) ins(a[R--],-1);
        while(L < l) ins(a[L++],-1);
        res[id] = ans;
    }for(int i = 1;i <= q;i ++) cout << res[i] << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(175)\) CF1207G Indie Album

难度 \(^*2600\)字符串;AC 自动机

按照字典树的 dfs 序遍历加删自动机节点即可保证复杂度。

代码
/**
 *    author: sunkuangzheng
 *    created: 07.03.2024 09:07:10
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 8e5+5;
using namespace std;
int T,n,ch[N][26],rp[N][26],fa[N],tot,op,j,t[N],cnt,re,dfn[N],siz[N],m,ans[N],rt[N],id[N],qj[N],fk[N];  char c;
vector<int> g[N]; vector<pair<int,int>> q[N]; string tt; vector<int> ed[N];
void los(){
    auto ins = [&](int x,int s,char c){
        s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = rp[s][c - 'a'] = ++tot));
        return ed[s].push_back(x),s;
    };auto sin = [&](int x,string t){
        int s = 0;
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = rp[s][c - 'a'] = ++tot));
        id[x] = s;
    };
    auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop();
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
    };auto upd = [&](int x,int p){for(;x <= cnt;x += x & -x) t[x] += p;};
    auto qry = [&](int x){for(re = 0;x;x -= x & -x) re += t[x];return re;};
    cin >> n;
    for(int i = 1;i <= n;i ++){
        if(cin >> op,op == 1) cin >> c,rt[i] = ins(i,rt[0],c);
        else cin >> j >> c,rt[i] = ins(i,rt[j],c);
    }cin >> m;
    for(int i = 1;i <= m;i ++) cin >> qj[i] >> tt,sin(i,tt),q[rt[qj[i]]].emplace_back(id[i],i); build();
    auto dfs = [&](auto self,int u) -> void {
        dfn[u] = ++cnt,siz[u] = 1; for(int v : g[u]) self(self,v),siz[u] += siz[v];
    };dfs(dfs,0);
    auto dfs2 = [&](auto self,int u) -> void {
        upd(dfn[u],1);
        for(auto [x,id] : q[u]) ans[id] = qry(dfn[x] + siz[x] - 1) - qry(dfn[x] - 1);
        for(int i = 0;i < 26;i ++) if(rp[u][i]) self(self,rp[u][i]); upd(dfn[u],-1);
    };dfs2(dfs2,0);
    for(int i = 1;i <= m;i ++) cout << ans[i] << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(176 \sim 200\)

最近在学 ACAM,太板子的题就不写了。

\(\color{blue}(176)\) P2444 [POI2000] 病毒

难度 \(^*2600\)字符串;AC 自动机;拓扑排序

不走单词节点仍有环则有解,拓扑判环。

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 20:48:15
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,ch[N][2],ed[N],fa[N],tot,d[N]; string s;
void los(){
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - '0'] ? ch[s][c - '0'] : (ch[s][c - '0'] = ++tot));
        ed[s] = 1;
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 2;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] |= ed[fa[u]];
            for(int i = 0;i < 2;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 0;i <= tot;i ++) if(!ed[i]) 
            for(int j = 0;j < 2;j ++) if(!ed[ch[i][j]]) d[ch[i][j]] ++;
    };cin >> n;
    for(int i = 1;i <= n;i ++) cin >> s,ins(i,s); build();
    queue<int> q;
    for(int i = 0;i <= tot;i ++) if(!d[i] && !ed[i]) q.push(i);
    while(q.size()){
        int u = q.front(); q.pop();
        for(int i = 0;i < 2;i ++) if(!ed[ch[u][i]] && !(--d[ch[u][i]])) q.push(ch[u][i]);
    }cout << (*max_element(d,d+tot+1) ? "TAK" : "NIE");
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(177)\) CF1257F Make Them Similar

难度 \(^*2300\)meet-in-the-middle;搜索

\(x\) 的位数双向搜索,差分数组快速寻找状态。

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 19:51:58
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,a[N],b[N],c[N];  map<vector<int>,int> mp1,mp2;
void los(){
    cin >> n;
    for(int i = 1;i <= n;i ++) cin >> a[i];
    auto dfs = [&](int x,map<vector<int>,int> &mp){
        int d = 0;
        for(int i = 0;i < (1 << 15);i ++){
            vector<int> f(n - 1);
            for(int j = 1;j <= n;j ++) b[j] = __builtin_popcount(c[j] ^ (i << x));
            for(int j = 2;j <= n;j ++) f[j - 2] = b[j] - b[j - 1];
            mp[f] = (i << x);
        }
    }; for(int i = 1;i <= n;i ++) c[i] = (a[i] & ((1 << 15) - 1));
    dfs(0,mp1);
    for(int i = 1;i <= n;i ++) c[i] = (a[i] & ((1ll << 31) - (1 << 15)));
    dfs(15,mp2);
    for(auto [x,y] : mp1){
        vector<int> p = x;
        for(int &j : p) j = -j; 
        if(mp2.find(p) != mp2.end())
            return cout << (y ^ (mp2[p])) << "\n",void();
    }cout << -1;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(178)\) CF1437G Death DBMS

难度 \(^*2700\)字符串;AC 自动机;后缀数组,SA;线段树;树链剖分

建 ACAM 后容易转换为单点修链 \(\max\),树剖维护。用 SA 可以省去剖,但是在练 ACAM 就写剖了。

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 17:40:18
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,m,ch[N][26],ed[N],tot,op,fa[N],x,y,dfn[N],sb[N],cnt,siz[N]; vector<int> g[N]; string t;
using S = int;
multiset<int> ac[N];
int gp(multiset<int> &s){return s.size() ? *(--s.end()) : -1e9;}
S OP(S a,S b){return max(a,b);}
S e(){return -1e9;}
namespace atcoder {

template <class S, S (*op)(S, S), S (*e)()> struct segtree {
public:
    segtree() : segtree(0) {}
    segtree(int n) : segtree(std::vector<S>(n, e())) {}
    segtree(const std::vector<S>& v) : _n(int(v.size())) {
        log = ceil(log2(_n));
        size = 1 << log;
        d = std::vector<S>(2 * size, e());
        for (int i = 0; i < _n; i++) d[size + i] = v[i];
        for (int i = size - 1; i >= 1; i--) {
            update(i);
        }
    }

    void set(int p, S x) {
        assert(0 <= p && p < _n);
        p += size;
        d[p] = x;
        for (int i = 1; i <= log; i++) update(p >> i);
    }

    S get(int p) {
        assert(0 <= p && p < _n);
        return d[p + size];
    }

    S prod(int l, int r) {
        assert(0 <= l && l <= r && r <= _n);
        S sml = e(), smr = e();
        l += size;
        r += size;

        while (l < r) {
            if (l & 1) sml = op(sml, d[l++]);
            if (r & 1) smr = op(d[--r], smr);
            l >>= 1;
            r >>= 1;
        }
        return op(sml, smr);
    }

    S all_prod() { return d[1]; }

    template <bool (*f)(S)> int max_right(int l) {
        return max_right(l, [](S x) { return f(x); });
    }
    template <class F> int max_right(int l, F f) {
        assert(0 <= l && l <= _n);
        assert(f(e()));
        if (l == _n) return _n;
        l += size;
        S sm = e();
        do {
            while (l % 2 == 0) l >>= 1;
            if (!f(op(sm, d[l]))) {
                while (l < size) {
                    l = (2 * l);
                    if (f(op(sm, d[l]))) {
                        sm = op(sm, d[l]);
                        l++;
                    }
                }
                return l - size;
            }
            sm = op(sm, d[l]);
            l++;
        } while ((l & -l) != l);
        return _n;
    }

    template <bool (*f)(S)> int min_left(int r) {
        return min_left(r, [](S x) { return f(x); });
    }
    template <class F> int min_left(int r, F f) {
        assert(0 <= r && r <= _n);
        assert(f(e()));
        if (r == 0) return 0;
        r += size;
        S sm = e();
        do {
            r--;
            while (r > 1 && (r % 2)) r >>= 1;
            if (!f(op(d[r], sm))) {
                while (r < size) {
                    r = (2 * r + 1);
                    if (f(op(d[r], sm))) {
                        sm = op(d[r], sm);
                        r--;
                    }
                }
                return r + 1 - size;
            }
            sm = op(d[r], sm);
        } while ((r & -r) != r);
        return 0;
    }

private:
    int _n, size, log;
    std::vector<S> d;

    void update(int k) { d[k] = op(d[2 * k], d[2 * k + 1]); }
};

} 
int top[N],dep[N],son[N];
void los(){
    cin >> n >> m,tot = 0;
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        ed[x] = s;
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop();
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
    };auto dfs1 = [&](auto self,int u,int f) -> void {
        siz[u] = 1,dep[u] = dep[max(0,f)] + 1,son[u] = -1,fa[u] = f; for(int v : g[u]) 
            if(self(self,v,u),siz[u] += siz[v],son[u] == -1 || siz[v] > siz[son[u]]) son[u] = v;
    };auto dfs2 = [&](auto self,int u,int tp) -> void {
        top[u] = tp,dfn[u] = ++cnt; if(son[u] != -1) self(self,son[u],tp);
        for(int v : g[u]) if(v != son[u]) self(self,v,v);
    };
    for(int i = 1;i <= n;i ++) cin >> t,ins(i,t); build();
    dfs1(dfs1,0,-1),dfs2(dfs2,0,0);
    atcoder::segtree<S,OP,e> seg(cnt); 
    for(int i = 1;i <= n;i ++) ac[ed[i]].insert(0);
    for(int i = 0;i <= tot;i ++) seg.set(dfn[i]-1,S{gp(ac[i])});
    while(m --){
        cin >> op;
        auto qry = [&](int u){
            int ans = -1e9;
            while(u != -1) ans = max(ans,seg.prod(dfn[top[u]]-1,dfn[u])),u = fa[top[u]];
            return ans;
        };
        if(op == 2){
            cin >> t; int ans = -1e9,p = 0;
            for(char c : t) p = ch[p][c - 'a'],ans = max(ans,qry(p));
            cout << (ans == -1e9 ? -1 : ans) << "\n";
        }else cin >> x >> y,ac[ed[x]].erase(ac[ed[x]].find(sb[x])),sb[x] = y,
            ac[ed[x]].insert(y),seg.set(dfn[ed[x]]-1,gp(ac[ed[x]]));
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(179)\) CF585F Digits of Number Pi

难度 \(^*2600\)字符串;AC 自动机;数位 DP

ACAM 上数位 DP 的套路是加一维表示走到 ACAM 上哪个点。

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 16:01:57
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 3e4+5,mod = 1e9+7;
using namespace std;
int T,ch[N*2][26],ed[N],fa[N],tot,f[55][N*2][2][2][2]; string x,y,n;
int dfs(int p,int m,int l,int q,bool ok){
    if(f[p][m][l][q][ok] != -1) return f[p][m][l][q][ok];
    ok |= ed[m];
    if(p == n.size()) return ok;
    int ans = 0,up = (l ? n[p] - '0' : 9);
    for(int i = 0;i <= up;i ++) ans = (ans + dfs(p+1,(q && !i ? m : ch[m][i]),l && (i == up),q && !i,ok)) % mod;
    return f[p][m][l][q][ok] = ans;
}void los(){
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - '0'] ? ch[s][c - '0'] : (ch[s][c - '0'] = ++tot));
        ed[s] = 1;
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 10;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] |= ed[fa[u]];
            for(int i = 0;i < 10;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }
    };cin >> n >> x >> y; int m = x.size() / 2;
    for(int i = 0;i < n.size();i ++) if(i + m <= n.size()) ins(i,n.substr(i,m)); else break;
    build(); int ans = 0,p = 0;
    for(char c : x) p = ch[p][c - '0'],ans |= ed[p];
    memset(f,-1,sizeof(f)),n = x;
    int fk1 = dfs(0,0,1,1,0);
    memset(f,-1,sizeof(f)),n = y;
    int fk2 = dfs(0,0,1,1,0);
    cout << (fk2 - fk1 + mod + ans) % mod;
    // cout << fk2;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(180)\) P3311 [SDOI2014] 数数

难度 \(^*2600\)字符串;AC 自动机;数位 DP

数位 DP + ACAM。

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 15:38:51
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,ch[N][12],tot,ed[N],fa[N],m,L,f[1205][1505][2][2]; string n,s;
int dfs(int p,int m,int l,int q){
    if(p == L) return !ed[m];
    if(f[p][m][l][q] != -1) return f[p][m][l][q];
    if(ed[m]) return f[p][m][l][q] = 0;
    int ans = 0,up = (l ? n[p] - '0' : 9);
    for(int i = 0;i <= up;i ++)
        ans = (ans + dfs(p+1,(q && !i ? m : ch[m][i]),l && (i == up),q && !i)) % (int)(1e9+7);
    return f[p][m][l][q] = ans;
}void los(){
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - '0'] ? ch[s][c - '0'] : (ch[s][c - '0'] = ++tot));
        ed[s] = x;
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 10;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] |= ed[fa[u]];
            for(int i = 0;i < 10;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }
    };cin >> n >> m,L = n.size(); int fg = 0;
    for(int i = 1;i <= m;i ++) cin >> s,ins(i,s),fg |= (s == "0"); build();
    memset(f,-1,sizeof(f)),cout << dfs(0,0,1,1) - (!fg) << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(181)\) P3121 [USACO15FEB] Censoring G

难度 \(^*2500\)字符串;AC 自动机;栈

栈维护匹配的点,方便删除。

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 14:59:03
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 5e5+5;
using namespace std;
int T,n,ed[N],ch[N][26],fa[N],tot,len[N]; string s,t;
void los(){
    auto ins = [&](int x,string t){
        int s = 0; 
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        ed[s] = x;
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] |= ed[fa[u]];
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }
    };cin >> s >> n; int now = 0,p = 0; vector<int> st; st.push_back(p); string ans = "";
    for(int i = 1;i <= n;i ++) cin >> t,ins(i,t),len[i] = t.size(); build(); 
    for(;now < s.size();now ++){
        p = ch[p][s[now] - 'a'],st.push_back(p),ans += s[now]; int le = 0;
        if(le = len[ed[p]],le) while(le --) st.pop_back(),ans.pop_back(); 
        p = st.back();
    }cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(182)\) CF1935F Andrey's Tree

难度 \(^*3000\)构造

我的题解

\(\color{blue}(183)\) CF1935E Distance Learning Courses in MAC

难度 \(^*3000\)构造

我的题解

\(\color{blue}(184)\) P6349 [PA2011] Kangaroos

难度 \(^*2800\)分块

考虑答案即为最远的两个不相交的相邻区间的距离 \(-1\),直接暴力可以做到 \(\mathcal O(mn)\)

考虑分块,注意到不交区间满足 \(l_i > r\)\(r_i < l\),按照 \(l,r\) 分别排序后求出前缀 \(\max,\min\),回答询问时,二分得到不交区间的前后缀,记录之前块的最后一个不交区间,用 \(\min\) 更新答案,并把最后一个不交区间改为 \(\max\)

注意到我们没有统计块内答案,块内直接暴力枚举前缀 \(i,j\),用 set 维护答案。

时间复杂度 \(\mathcal O((n+m) \sqrt n \log n)\),不能通过。

发现瓶颈在询问时的二分,那么离线询问排序后单调移动指针即可 \(\mathcal O(m \sqrt n)\) 求出对应前缀,可以卡着通过。

代码
#include <bits/stdc++.h>
using namespace std;
#define debug(x) cerr << (#x) << " = " << x << "\n"
const int N = 1e5+5,B = 250,C = 230,M = 2e5+5;
using ll = long long;
void printarr(int s,int t,int *a){
    cerr << "{";
    for(int i = s;i <= t;i ++) cerr << a[i] << ",}"[i == t];
    cerr << "\n";
}
int l[N],r[N],n,ans[N],id[N],al[N],ar[N],b[B],pl,pr,c[B],m,afl[M][B],afr[M][B],ql[M],qr[M],idd[M],L[B],R[B];
int prel1[B][B],prel2[B][B],prer1[B][B],prer2[B][B],preans[B][B][B],fl[N],fr[N],d[B][B],e[B][B]; //1 max 2 min
int main(){
    // freopen("section.in","r",stdin),freopen("section.out","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0);
    cin >> n >> m;
    for(int i = 1;i <= n;i ++) cin >> l[i] >> r[i],l[i] --,r[i] ++,id[i] = (i - 1) / C + 1,al[id[i]] = (al[id[i]] ? al[id[i]] : i),ar[id[i]] = i;
    int tot = id[n];
    auto init = [&](int id,int *l,int *r){
        int n = ar[id] - al[id] + 1;
        for(int i = 1;i <= n;i ++) b[i] = c[i] = i,d[id][i] = l[i],e[id][i] = r[i];
        sort(b+1,b+n+1,[&](int x,int y){return l[x] > l[y];});
        sort(c+1,c+n+1,[&](int x,int y){return r[x] < r[y];});
        sort(d[id]+1,d[id]+n+1,[&](int x,int y){return x > y;}),sort(e[id]+1,e[id]+n+1);
        prel2[id][0] = prer2[id][0] = 1e9;
        for(int i = 1;i <= n;i ++) prel1[id][i] = max(prel1[id][i-1],b[i]),prel2[id][i] = min(prel2[id][i-1],b[i]),
                                prer1[id][i] = max(prer1[id][i-1],c[i]),prer2[id][i] = min(prer2[id][i-1],c[i]);
        set<int> s; multiset<int> sm; int ans = 0;
        for(int i = 0;i <= n;i ++){
            s.clear(),sm.clear(),sm.insert(0),ans = 0;
            auto upd = [&](int x){
                auto it = s.lower_bound(x),ti = it;
                if(s.count(x)) return ;
                if(it != s.end()) sm.insert(*it - x - 1);
                if(it != s.begin()) sm.insert(x - *(--it) - 1);
                if(ti != s.end() && ti != s.begin()) sm.erase(sm.find(*ti - *it - 1));
                s.insert(x);
            }; for(int j = 1;j <= i;j ++) upd(b[j]);
            for(int j = 0;j <= n;j ++){
                if(j) upd(c[j]);
                preans[id][i][j] = *(--sm.end());
            }
        }
    };
    for(int i = 1;i <= tot;i ++) init(i,l+al[i]-1,r+al[i]-1);
    for(int i = 1;i <= m;i ++) cin >> ql[i] >> qr[i],idd[i] = i;
    sort(idd+1,idd+m+1,[&](int x,int y){return qr[x] > qr[y];});
    for(int j = 1;j <= tot;j ++) sort(l+al[j],l+ar[j]+1,[&](int x,int y){return x > y;}),
                                sort(r+al[j],r+ar[j]+1);
    for(int i = 1;i <= m;i ++){
        for(int j = 1;j <= tot;j ++){
            int n = ar[j] - al[j] + 1;
            while(L[j] < n && l[L[j] + al[j]] >= qr[idd[i]]) L[j] ++;
            afl[idd[i]][j] = L[j];
        }
    }sort(idd+1,idd+m+1,[&](int x,int y){return ql[x] < ql[y];});
    for(int i = 1;i <= m;i ++){
        for(int j = 1;j <= tot;j ++){
            int n = ar[j] - al[j] + 1;
            while(R[j] < n && r[R[j] + al[j]] <= ql[idd[i]]) R[j] ++;
            afr[idd[i]][j] = R[j];
        }
    }
    for(int j = 1;j <= m;j ++){
        int fk = 0,ans = 0; 
        auto sol = [&](int id){
            int n = ar[id] - al[id] + 1;
            int l = afl[j][id],r = afr[j][id]; 
            if(!l && !r) return ;
            int mx = max(prel1[id][l],prer1[id][r]),mn = min(prel2[id][l],prer2[id][r]);
            ans = max(ans,preans[id][l][r]); 
            ans = max(ans,mn + al[id] - 1 - fk - 1),fk = al[id] + mx - 1;
        };
        for(int i = 1;i <= tot;i ++) sol(i); 
        ans = max(ans,n - fk),cout << ans << "\n";
    }
}

\(\color{blue}(185)\) UVA11019 Matrix Matcher

难度 \(^*2700\)字符串;AC 自动机;KMP

很妙的题。先把小矩阵的行拿出来去重后建 ACAM,找到每个位置 \((i,j)\) 往后延伸的串 \(s_{i}[j \ldots j + y - 1]\) 和小矩阵哪一行匹配,记作 \(a_{i,j}\)。注意到一个矩阵出现当且仅当 \(a\) 矩阵中竖着出现小矩阵的行的编号,对每一列跑 KMP 即可。时间复杂度 \(\mathcal O(nm)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 06.03.2024 13:38:08
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e3+5,M = 2e4+5;
using namespace std;
int T,n,m,ch[M][26],fa[M],tot,b[N],ed[M],id,x,y,a[N][N],nxt[N]; string s[N],t[N];
void los(){
    cin >> n >> m; memset(ch,0,sizeof(ch)),memset(ed,0,sizeof(ed)),memset(fa,0,sizeof(fa)),
    tot = id = 0;
    auto ins = [&](int x,string t){
        int s = 0;
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        if(!ed[s]) ed[s] = ++id; b[x] = ed[s];
    };auto build = [&](){
        queue<int> q; 
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] |= ed[fa[u]];
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }
    };
    for(int i = 1;i <= n;i ++) cin >> s[i]; cin >> x >> y;
    for(int i = 1;i <= x;i ++) cin >> t[i],ins(i,t[i]); build();
    if(x > n || y > m) return cout << "0\n",void();
    for(int i = 1;i <= n;i ++){
        int p = 0;
        for(int j = 1;j <= m;j ++)
            p = ch[p][s[i][j-1] - 'a'],a[i][j] = ed[p];
    }int j = 0;
    for(int i = 2;i <= x;i ++){
        while(j && b[j + 1] != b[i]) j = nxt[j];
        if(b[j + 1] == b[i]) j ++; nxt[i] = j;
    }auto mc = [&](int fk){
        int j = 0,ans = 0;
        for(int i = 1;i <= n;i ++){
            while(j && b[j + 1] != a[i][fk]) j = nxt[j];
            if(b[j + 1] == a[i][fk]) j ++; 
            if(j == x) ans ++,j = nxt[j];
        }return ans;
    };int sum = 0;
    for(int i = 1;i <= m;i ++) sum += mc(i);
    cout << sum << "\n";
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(cin >> T;T --;) los();
}

\(\color{blue}(186)\) P8203 [传智杯 #4 决赛] DDOSvoid 的馈赠

难度 \(^*2900\)字符串;AC 自动机;虚树;根号分治

设阈值为 \(B\),令 \(|s_x| \ge |s_y|\),则当 \(|s_x| \le B\) 时可以暴力建虚树求解。当 \(|s_x| > B\) 时,我们希望能用 \(\mathcal O(|s_y|)\) 的时间复杂度回答询问。注意到 \(|s_i| > B\)\(i\) 只有 \(\mathcal O(\dfrac{n}{B})\) 个,那么对于 \(|s_i| > B\) 的串暴力把合法点找出来然后对询问建虚树。复杂度 \(\mathcal O(nB \log n + \dfrac{n^2}{B} \log n)\),取 \(B = \sqrt n\) 复杂度为 \(\mathcal O(n \sqrt n \log n)\),基本不需要卡常就能过。

代码
/**
 *    author: sunkuangzheng
 *    created: 10.03.2024 16:11:48
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e5+5,B = 270;
using namespace std;
int T,n,m,ed[N],ch[N][26],fa[N],tot,per[N],q,x,y,st[20][N],dfn[N],cnt,a[N],re,sm[N],val[N],ans[N]; 
string s[N],t; map<int,vector<int>> qu[N]; vector<int> g[N],gr[N];
int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    auto ins = [&](int x,string t){
        int s = 0;
        for(char c : t) 
            s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot)); 
        ed[x] = s;
    };auto build = [&](){
        queue<int> q;
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); 
            for(int i = 0;i < 26;i ++) 
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
    };auto dfs1 = [&](auto self,int u,int f) -> void {
        st[0][dfn[u] = ++cnt] = f; for(int v : g[u]) self(self,v,u); 
    };auto dfs2 = [&](auto self,int u) -> void {
        for(int v : g[u]) a[v] += a[u],self(self,v); 
    };auto dfs3 = [&](auto self,int u) -> void {
        for(int v : g[u]) self(self,v),sm[u] += sm[v];
    };cin >> n >> m >> q;
    auto cmp = [&](int u,int v){return dfn[u] < dfn[v] ? u : v;};
    for(int i = 1;i <= n;i ++) cin >> t,ins(i,t); build(); dfs1(dfs1,0,0);
    for(int j = 1;j <= __lg(cnt);j ++) for(int i = 0;i + (1 << j) - 1 <= cnt;i ++)
        st[j][i] = cmp(st[j-1][i],st[j-1][i+(1<<j-1)]);
    auto lca = [&](int u,int v){
        if(u == v) return u;
        if((u = dfn[u]) > (v = dfn[v])) swap(u,v);
        int k = __lg(v - u);
        return cmp(st[k][u+1],st[k][v-(1<<k)+1]);
    };
    for(int i = 1;i <= m;i ++) cin >> s[i],per[i] = i;
    for(int i = 1;i <= q;qu[x][y].push_back(i ++))
        if(cin >> x >> y,s[y].size() > s[x].size()) swap(x,y);
    auto sol = [&](vector<int> &p){
        auto dcmp = [&](int x,int y){return dfn[x] < dfn[y];};
        sort(p.begin(),p.end(),dcmp); int d = p.size(),ans = 0;
        for(int i = 1;i < d;i ++) p.emplace_back(lca(p[i],p[i-1]));
        sort(p.begin(),p.end(),dcmp),p.erase(unique(p.begin(),p.end()),p.end());
        for(int i = 1;i < p.size();i ++) gr[lca(p[i],p[i-1])].push_back(p[i]);
        reverse(p.begin(),p.end());
        for(int u : p){
            for(int v : gr[u]){
                val[u] |= val[v];
                if(val[v] == 3) ans += a[v] - a[u];
            }
        }if(val[p.back()] == 3) ans += a[p.back()];
        for(int i : p) gr[i].clear(),val[i] = 0;
        return ans;
    };  sort(per+1,per+m+1,[&](int x,int y){return s[x].size() > s[y].size();});
    int fg = 0;
    for(int i = 1;i <= m;i ++){
        vector<int> acc;
        if(s[per[i]].size() >= B){
            int p = 0;
            for(char c : s[per[i]]) p = ch[p][c - 'a'],sm[p] = 1;
            dfs3(dfs3,0);
            for(int j = 1;j <= n;j ++) if(sm[ed[j]]) a[ed[j]] ++;
            dfs2(dfs2,0);
            for(auto [j,di] : qu[per[i]]){
                p = 0; 
                for(char c : s[j]) p = ch[p][c - 'a'],val[p] = 3,acc.push_back(p);
                int tmp = sol(acc);
                for(int id : di) ans[id] = tmp;
                acc.clear();
            }for(int j = 0;j <= tot;j ++) sm[j] = a[j] = 0;
        }else{
            if(!fg) {fg = 1;for(int j = 1;j <= n;j ++) a[ed[j]] ++; dfs2(dfs2,0);}
            for(auto [j,di] : qu[per[i]]){
                int p = 0;
                for(char c : s[per[i]]) p = ch[p][c - 'a'],acc.push_back(p),val[p] = 1; p = 0;
                for(char c : s[j]) p = ch[p][c - 'a'],val[p] |= 2,acc.push_back(p);
                int tmp = sol(acc);
                for(int id : di) ans[id] = tmp;
                acc.clear();
            }
        }
    }for(int i = 1;i <= q;i ++) cout << ans[i] << "\n";
}

\(\color{blue}(187)\) P4022 [CTSC2012] 熟悉的文章

难度 \(^*2800\)字符串;后缀数组,SA;动态规划,DP;单调队列

注意到答案显然有单调性,二分答案 \(mid\)。设 \(f_i\) 为到 \(i\) 的最大覆盖长度,对于位置 \(i\),找到最小的 \(j\) 满足 \([j,i]\) 是给定串的子串,那么我们有转移 \(f_i = \max\limits_{k=j-1}^{i-1} \{f_k + i - k\}\)。注意到 \(j\) 是单调不降的,可以单调队列优化。寻找 \(j\) 可以使用后缀数组,时间复杂度 \(\mathcal O(n \log n)\)

感觉黑题虚高了。

代码
/**
 *    author: sunkuangzheng
 *    created: 11.03.2024 07:50:23
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 2e6+5;
using namespace std;
namespace atcoder {

namespace internal {

std::vector<int> sa_naive(const std::vector<int>& s) {
    int n = int(s.size());
    std::vector<int> sa(n);
    std::iota(sa.begin(), sa.end(), 0);
    std::sort(sa.begin(), sa.end(), [&](int l, int r) {
        if (l == r) return false;
        while (l < n && r < n) {
            if (s[l] != s[r]) return s[l] < s[r];
            l++;
            r++;
        }
        return l == n;
    });
    return sa;
}

std::vector<int> sa_doubling(const std::vector<int>& s) {
    int n = int(s.size());
    std::vector<int> sa(n), rnk = s, tmp(n);
    std::iota(sa.begin(), sa.end(), 0);
    for (int k = 1; k < n; k *= 2) {
        auto cmp = [&](int x, int y) {
            if (rnk[x] != rnk[y]) return rnk[x] < rnk[y];
            int rx = x + k < n ? rnk[x + k] : -1;
            int ry = y + k < n ? rnk[y + k] : -1;
            return rx < ry;
        };
        std::sort(sa.begin(), sa.end(), cmp);
        tmp[sa[0]] = 0;
        for (int i = 1; i < n; i++) {
            tmp[sa[i]] = tmp[sa[i - 1]] + (cmp(sa[i - 1], sa[i]) ? 1 : 0);
        }
        std::swap(tmp, rnk);
    }
    return sa;
}

// SA-IS, linear-time suffix array construction
// Reference:
// G. Nong, S. Zhang, and W. H. Chan,
// Two Efficient Algorithms for Linear Time Suffix Array Construction
template <int THRESHOLD_NAIVE = 10, int THRESHOLD_DOUBLING = 40>
std::vector<int> sa_is(const std::vector<int>& s, int upper) {
    int n = int(s.size());
    if (n == 0) return {};
    if (n == 1) return {0};
    if (n == 2) {
        if (s[0] < s[1]) {
            return {0, 1};
        } else {
            return {1, 0};
        }
    }
    if (n < THRESHOLD_NAIVE) {
        return sa_naive(s);
    }
    if (n < THRESHOLD_DOUBLING) {
        return sa_doubling(s);
    }

    std::vector<int> sa(n);
    std::vector<bool> ls(n);
    for (int i = n - 2; i >= 0; i--) {
        ls[i] = (s[i] == s[i + 1]) ? ls[i + 1] : (s[i] < s[i + 1]);
    }
    std::vector<int> sum_l(upper + 1), sum_s(upper + 1);
    for (int i = 0; i < n; i++) {
        if (!ls[i]) {
            sum_s[s[i]]++;
        } else {
            sum_l[s[i] + 1]++;
        }
    }
    for (int i = 0; i <= upper; i++) {
        sum_s[i] += sum_l[i];
        if (i < upper) sum_l[i + 1] += sum_s[i];
    }

    auto induce = [&](const std::vector<int>& lms) {
        std::fill(sa.begin(), sa.end(), -1);
        std::vector<int> buf(upper + 1);
        std::copy(sum_s.begin(), sum_s.end(), buf.begin());
        for (auto d : lms) {
            if (d == n) continue;
            sa[buf[s[d]]++] = d;
        }
        std::copy(sum_l.begin(), sum_l.end(), buf.begin());
        sa[buf[s[n - 1]]++] = n - 1;
        for (int i = 0; i < n; i++) {
            int v = sa[i];
            if (v >= 1 && !ls[v - 1]) {
                sa[buf[s[v - 1]]++] = v - 1;
            }
        }
        std::copy(sum_l.begin(), sum_l.end(), buf.begin());
        for (int i = n - 1; i >= 0; i--) {
            int v = sa[i];
            if (v >= 1 && ls[v - 1]) {
                sa[--buf[s[v - 1] + 1]] = v - 1;
            }
        }
    };

    std::vector<int> lms_map(n + 1, -1);
    int m = 0;
    for (int i = 1; i < n; i++) {
        if (!ls[i - 1] && ls[i]) {
            lms_map[i] = m++;
        }
    }
    std::vector<int> lms;
    lms.reserve(m);
    for (int i = 1; i < n; i++) {
        if (!ls[i - 1] && ls[i]) {
            lms.push_back(i);
        }
    }

    induce(lms);

    if (m) {
        std::vector<int> sorted_lms;
        sorted_lms.reserve(m);
        for (int v : sa) {
            if (lms_map[v] != -1) sorted_lms.push_back(v);
        }
        std::vector<int> rec_s(m);
        int rec_upper = 0;
        rec_s[lms_map[sorted_lms[0]]] = 0;
        for (int i = 1; i < m; i++) {
            int l = sorted_lms[i - 1], r = sorted_lms[i];
            int end_l = (lms_map[l] + 1 < m) ? lms[lms_map[l] + 1] : n;
            int end_r = (lms_map[r] + 1 < m) ? lms[lms_map[r] + 1] : n;
            bool same = true;
            if (end_l - l != end_r - r) {
                same = false;
            } else {
                while (l < end_l) {
                    if (s[l] != s[r]) {
                        break;
                    }
                    l++;
                    r++;
                }
                if (l == n || s[l] != s[r]) same = false;
            }
            if (!same) rec_upper++;
            rec_s[lms_map[sorted_lms[i]]] = rec_upper;
        }

        auto rec_sa =
            sa_is<THRESHOLD_NAIVE, THRESHOLD_DOUBLING>(rec_s, rec_upper);

        for (int i = 0; i < m; i++) {
            sorted_lms[i] = lms[rec_sa[i]];
        }
        induce(sorted_lms);
    }
    return sa;
}

}  // namespace internal

std::vector<int> suffix_array(const std::vector<int>& s, int upper) {
    assert(0 <= upper);
    for (int d : s) {
        assert(0 <= d && d <= upper);
    }
    auto sa = internal::sa_is(s, upper);
    return sa;
}

template <class T> std::vector<int> suffix_array(const std::vector<T>& s) {
    int n = int(s.size());
    std::vector<int> idx(n);
    iota(idx.begin(), idx.end(), 0);
    sort(idx.begin(), idx.end(), [&](int l, int r) { return s[l] < s[r]; });
    std::vector<int> s2(n);
    int now = 0;
    for (int i = 0; i < n; i++) {
        if (i && s[idx[i - 1]] != s[idx[i]]) now++;
        s2[idx[i]] = now;
    }
    return internal::sa_is(s2, now);
}

std::vector<int> suffix_array(const std::string& s) {
    int n = int(s.size());
    std::vector<int> s2(n);
    for (int i = 0; i < n; i++) {
        s2[i] = s[i];
    }
    return internal::sa_is(s2, 255);
}

// Reference:
// T. Kasai, G. Lee, H. Arimura, S. Arikawa, and K. Park,
// Linear-Time Longest-Common-Prefix Computation in Suffix Arrays and Its
// Applications
template <class T>
std::vector<int> lcp_array(const std::vector<T>& s,
                           const std::vector<int>& sa) {
    int n = int(s.size());
    assert(n >= 1);
    std::vector<int> rnk(n);
    for (int i = 0; i < n; i++) {
        rnk[sa[i]] = i;
    }
    std::vector<int> lcp(n - 1);
    int h = 0;
    for (int i = 0; i < n; i++) {
        if (h > 0) h--;
        if (rnk[i] == 0) continue;
        int j = sa[rnk[i] - 1];
        for (; j + h < n && i + h < n; h++) {
            if (s[j + h] != s[i + h]) break;
        }
        lcp[rnk[i] - 1] = h;
    }
    return lcp;
}

std::vector<int> lcp_array(const std::string& s, const std::vector<int>& sa) {
    int n = int(s.size());
    std::vector<int> s2(n);
    for (int i = 0; i < n; i++) {
        s2[i] = s[i];
    }
    return lcp_array(s2, sa);
}

// Reference:
// D. Gusfield,
// Algorithms on Strings, Trees, and Sequences: Computer Science and
// Computational Biology
template <class T> std::vector<int> z_algorithm(const std::vector<T>& s) {
    int n = int(s.size());
    if (n == 0) return {};
    std::vector<int> z(n);
    z[0] = 0;
    for (int i = 1, j = 0; i < n; i++) {
        int& k = z[i];
        k = (j + z[j] <= i) ? 0 : std::min(j + z[j] - i, z[i - j]);
        while (i + k < n && s[k] == s[i + k]) k++;
        if (j + z[j] < i + z[i]) j = i;
    }
    z[0] = n;
    return z;
}

std::vector<int> z_algorithm(const std::string& s) {
    int n = int(s.size());
    std::vector<int> s2(n);
    for (int i = 0; i < n; i++) {
        s2[i] = s[i];
    }
    return z_algorithm(s2);
}

}
int T,n,m,len,sa[N],rk[N],h[N],ans[N],f[N],al[N],q[N]; string s,t,tr[N];
void los(){
    cin >> n >> m;
    for(int i = 1;i <= m;i ++) cin >> t,s += string(t.rbegin(),t.rend()) + '#';
    int le = s.size();
    for(int i = 1;i <= n;i ++) cin >> tr[i],al[i] = s.size() + 1,s += string(tr[i].rbegin(),tr[i].rend()) + '#';
    auto SA = [&](){
        vector<int> _sa = atcoder::suffix_array(s); len = s.size(),s = " " + s;
        for(int i = 1;i <= len;i ++) sa[i] = _sa[i-1] + 1,rk[sa[i]] = i;
        for(int i = 1,k = 0;i <= len;h[rk[i ++]] = k)
            for(k --,k = max(k,0);s[i + k] == s[sa[rk[i] - 1] + k] && s[i + k] != '#';k ++);
        int lcp = -1;
        for(int i = 1;i <= len;i ++)
            if(sa[i] <= le) lcp = h[i + 1];
            else ans[i] = lcp,lcp = min(lcp,h[i + 1]); 
        lcp = 0;
        for(int i = len;i >= 1;i --)
            if(sa[i] <= le) lcp = h[i];
            else ans[i] = max(ans[i],lcp),lcp = min(lcp,h[i]);
    };SA();
    for(int i = 1;i <= n;i ++){
        int l = 1,le = tr[i].size(),r = le; tr[i] = " " + tr[i];
        while(l <= r){
            int mid = (l + r) / 2,ql = 1,qr = 0;
            auto ck = [&](int x){
                for(int j = 1;j <= le;j ++){
                    f[j] = f[j - 1] - 1;
                    int fk = j - ans[rk[al[i] + le - j]];
                    if(j >= x){
                        while(ql <= qr && f[q[qr]] < f[j - x]) qr --;
                        q[++qr] = j - x;
                    }while(ql <= qr && q[ql] < fk) ql ++; 
                    if(ql <= qr) f[j] = max(f[j],f[q[ql]]);
                }return f[le] + le;
            };if(ck(mid) * 10 >= le * 9) l = mid + 1; else r = mid - 1;
        }cout << l - 1 << "\n";
    }
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(188)\) CF1801G A task for substrings

难度 \(^*3400\)字符串;AC 自动机

\(f_r\) 表示 \(s_{1 \ldots n}\)\(t[1 \ldots r]\) 中出现多少次,这个可以 ACAM 简单递推。

考虑把每组询问的答案表示为 \(f_r - f_{l-1}\) 有什么问题:我们会把左端点 \(L \in [1,l-1]\),右端点 \(R \in [l,r]\) 的串统计入答案,我们需要把这部分减去。注意到 \([L,l-1],[l,R]\) 分别是这个串的前后缀,且正好拼起来这个串。对正反串建 ACAM,设当前正反串的节点走到位置 \(p_1,p_2\),这个问题问的是有多少个串 \(s_k\) 的断点 \(x\) 满足 \(s_{k,x}\) 的终止节点在 \(1 \sim p_1\) 的链,\(s^R_{k,|s_k|-x}\) 的终止节点在 \(1 \sim p_2\) 的链。离线 BIT 扫一遍即可。时间复杂度 \(\mathcal O(|t| + |s| \log |s|)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 11.03.2024 11:11:14
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e6+5;
using namespace std;
int T,n,tr[N],re,le[N],m,q,l,r,fl[N],fr[N],rl[N],f[N][20]; vector<pair<int,int>> qr[N]; ll ans[N],res;
string s,t; vector<int> S[N*5],SR[N*5],rr[N*5];
struct acam{
    int ch[N][26],fa[N],tot,ed[N],dfn[N],cnt,siz[N],dep[N]; 
    vector<int> g[N],fk[N]; vector<pair<int,int>> nd[N];
    void ins(int x,string t){
        int s = 0,k = 0;
        fk[x].resize(t.size());
        for(char c : t)
            s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot)),
            fk[x][k] = s,nd[s].emplace_back(x,k ++),dep[s] = k;
        ed[s] ++;
    }void build(){
        queue<int> q;
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop();
            for(int i = 0;i < 26;i ++)
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
    }void dfs(int u){
        dfn[u] = ++cnt,siz[u] = 1,ed[u] += ed[fa[u]];
        for(int i = 1;i <= 19;i ++) f[u][i] = f[f[u][i-1]][i-1];
        for(int v : g[u]) f[v][0] = u,dfs(v),siz[u] += siz[v];
    }int fd(int u,int k){
        for(int i = 19;i >= 0;i --) if(dep[f[u][i]] > k) u = f[u][i];
        return (dep[u] <= k ? u : f[u][0]);
    }
}A,AR;
void upd(int x,int p){for(;x <= AR.cnt;x += x & -x) tr[x] += p;}
int qry(int x){for(re = 0;x;x -= x & -x) re += tr[x]; return re;}
void rupd(int l,int r,int k){upd(l,k),upd(r+1,-k);}
void dfs(int u){
    for(auto [x,y] : A.nd[u]){
        if(y == le[x] - 1) continue; 
        int v = AR.fk[x][le[x] - y - 2];
        rupd(AR.dfn[v],AR.dfn[v] + AR.siz[v] - 1,1);
    }for(auto [v,id] : qr[u]) ans[id] -= qry(AR.dfn[v]);
    for(int v : A.g[u]) dfs(v);
    for(auto [x,y] : A.nd[u]){
        if(y == le[x] - 1) continue;
        int v = AR.fk[x][le[x] - y - 2]; 
        rupd(AR.dfn[v],AR.dfn[v] + AR.siz[v] - 1,-1);
    }
}void los(){
    cin >> n >> q >> t,m = t.size(),t = " " + t; int p = 0;
    for(int i = 1;i <= n;i ++) cin >> s,le[i] = s.size(),A.ins(i,s),AR.ins(i,string(s.rbegin(),s.rend()));
    for(int i = 1;i <= q;i ++) cin >> l >> r,rr[r].emplace_back(i),rr[l-1].emplace_back(-i),
                               S[l-1].emplace_back(i),SR[l].emplace_back(i),rl[i] = r - l + 1;
    A.build(),AR.build(),A.dfs(0),AR.dfs(0);
    for(int i = 1;i <= m;i ++){
        p = A.ch[p][t[i] - 'a'],res += A.ed[p];
        for(int x : rr[i]) ans[abs(x)] += (x > 0 ? 1 : -1) * res;
        for(int x : S[i]) fl[x] = p;
    }p = 0;
    for(int i = m;i >= 1;i --){
        p = AR.ch[p][t[i] - 'a'];
        for(int x : SR[i]) fr[x] = p;
    }for(int i = 1;i <= q;i ++) qr[fl[i]].emplace_back(AR.fd(fr[i],rl[i]),i);
    dfs(0);
    for(int i = 1;i <= q;i ++) cout << ans[i] << " ";

}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(189)\) CF1483F Exam

难度 \(^*3400\)字符串;AC 自动机

很巧妙的题,需要一些观察。

  • Observation \(1\):答案不超过 \(\sum \limits_{i=1}^n |s_i|\)

注意到对于长串 \(s_i\),对于每个点 \(p\),以 \(p\) 为结尾的合法 \(s_j\) 只有一个,否则会相互包含。

  • Observation \(2\):一对 \((j,i)\) 合法的条件是 \(s_j\)\(s_i\) 中的每一次出现都没有被别的串包含。

我们对于每个 \(p\) 找到最长的串 \(s_j\),然后把这些线段排序扫描线即可找到包含的串。一个方便的办法是统计未被包含的次数。

然后即可 ACAM 直接做。时间复杂度 \(\mathcal O(n \log n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 11.03.2024 16:57:00
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e6+5;
using namespace std;
int T,n,ch[N][26],ed[N],fa[N],dfn[N],cnt,siz[N],tot,de[N],t[N],re,ans; 
string s[N]; vector<int> g[N];
void los(){
    cin >> n;
    auto ins = [&](int x,string t){
        int s = 0;
        for(char c : t) 
            s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot)); 
        ed[s] = x,de[x] = s;
    };auto build = [&](){
        queue<int> q;
        for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); if(!ed[u]) ed[u] = ed[fa[u]];
            for(int i = 0;i < 26;i ++) 
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
        }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
    }; auto dfs = [&](auto self,int u) -> void {
        dfn[u] = ++cnt,siz[u] = 1; for(int v : g[u]) self(self,v),siz[u] += siz[v];
    };auto upd = [&](int x,int p){for(;x <= cnt;x += x & -x) t[x] += p;};
    auto qry = [&](int x){for(re = 0;x;x -= x & -x) re += t[x]; return re;};
    auto qq = [&](int u){return qry(dfn[u] + siz[u] - 1) - qry(dfn[u] - 1);};
    for(int i = 1;i <= n;i ++) cin >> s[i],ins(i,s[i]); build(),dfs(dfs,0);
    for(int i = 1;i <= n;i ++){
        int p = 0,d = 0; vector<tuple<int,int,int>> f; map<int,int> mp;
        for(int j = 0;j < s[i].size();j ++) 
            p = ch[p][s[i][j] - 'a'],d = ed[(j == s[i].size()-1 ? fa[p] : p)],
            f.emplace_back(j+1 - s[d].size() + 1,-(j+1),d),upd(dfn[p],1);
        sort(f.begin(),f.end()); int mxr = 0;
        for(auto [l,r,id] : f) if(r = -r,l <= r && r > mxr) mxr = r,mp[id] ++;
        for(auto [x,y] : mp) if(x && qq(de[x]) == y) ans ++;
        p = 0;
        for(int j = 0;j < s[i].size();j ++) 
            p = ch[p][s[i][j] - 'a'],upd(dfn[p],-1);
    }cout << ans;
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(190)\) P10176 「OICon-02」Native Faith

难度 \(^*3500\)字符串;AC 自动机;根号分治

考虑乘法原理,枚举 \(s_k\) 的断点 \(i\),令 \(f_i\)\(s_k[1\ldots i]\)\(s_{l \ldots r}\) 中的出现次数,\(g_i\)\(s_k[i\ldots |s_k|]\)\(s_{l \ldots r}\) 中的出现次数,答案为 \(\sum f_i g_{i+1}\)

统计出现次数这部分是经典的,对正反串建 ACAM 即是 CF547E,离线差分后 ACAM。

这样我们的复杂度是 \(\mathcal O(n^2 \log n)\),可以用分块代替 BIT 平衡复杂度做到 \(\mathcal O(n^2)\)

考虑阈值分值,设置阈值 \(B\),对于 \(|s_k| \le B\) 的部分暴力做。枚举断点 \(i\),对于 \(\min(s_k[1\ldots i],s_k[i+1 \ldots |s_k|]) \le B\) 暴力做,这部分时空复杂度均为 \(\mathcal O(nB)\)

注意到 \(|s_k| > B\) 的串只有 \(\dfrac{n}{B}\) 个,而一个长度 \(>B\) 的串不会在长度小于等于 \(B\) 的串里出现,所以本质不同的 \(l,r\) 都是 \(\mathcal O(\dfrac{n}{B})\) 级别,一共只有 \(\mathcal O(\dfrac{n^2}{B^2})\)\(l,r\)。暴力计算这些答案即可,这部分时间复杂度为 \(\mathcal O(\dfrac{n^3}{B^2})\)

\(B = n^{2/3}\) 可以做到 \(\mathcal O(n^{5/3})\),注意减少空间消耗,注意别把复杂度写假。

代码
/**
 *    author: sunkuangzheng
 *    created: 11.03.2024 18:35:58
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e5+5,B = 600,T = N / B + 10,BL = 350;
using namespace std;map<tuple<int,int,int>,int> mp;
int n,q,l,r,k,ql[N],qr[N],id[N],qk[N]; string s[N]; vector<int> pos; ll pre[N];
struct ACAM{
    string s[N];
    int ch[N][26],tot,fa[N],ed[N],tr[N],siz[N],dfn[N],re,cnt,tg[N];
    vector<tuple<int,int,int>> qu[N];  vector<int> f[N],g[N],pa[N],run[N];
    vector<tuple<int,int,int>> rm[N];
    inline void ins(int x,string t){
        int ss = 0; s[x] = t;
        for(char c : t) 
            ss = (ch[ss][c - 'a'] ? ch[ss][c - 'a'] : (ch[ss][c - 'a'] = ++tot)); 
        ed[x] = ss;
    }inline void work(){
        auto build = [&](){
            queue<int> q;
            for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
            while(q.size()){
                int u = q.front(); q.pop(); 
                for(int i = 0;i < 26;i ++) 
                    if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                    else ch[u][i] = ch[fa[u]][i];
            }for(int i = 1;i <= tot;i ++) g[fa[i]].push_back(i);
        };auto dfs = [&](auto self,int u) -> void {
            dfn[u] = ++cnt,siz[u] = 1; for(int v : g[u]) self(self,v),siz[u] += siz[v];
        };build(),dfs(dfs,0);
        for(int i = 1;i <= n;i ++){
            int p = 0,rk = 0; run[i].resize(s[i].size());
            for(char c : s[i]) p = ch[p][c - 'a'],run[i][rk ++] = p;
        }
    }inline void add1(int i,int l,int r,int k,int len,int pi){f[i].resize(len);
        qu[l-1].emplace_back(-i,k,pi),qu[r].emplace_back(i,k,pi);}
    inline void add2(int x,int y,int z,int ct){pa[ct].resize(s[pos[x]].size() - 2 * B);
        rm[pos[y]-1].emplace_back(-x-1,y,z),rm[pos[z]].emplace_back(x+1,y,z);}
    inline void sol(){
        for(int i = 1;i <= cnt;i ++) id[i] = (i - 1) / BL + 1;
        auto upd = [&](int x,int p){
            for(int i = id[x] + 1;i <= id[cnt];i ++) tg[i] += p;
            for(int i = x;i <= id[x] * BL;i ++) tr[i] += p;
        };
        auto qry = [&](int x){return tg[id[x]] + tr[x];};
        auto add = [&](int i,int k,int li){
            int p = 0,pk = 0; 
            auto ad = [&](int x,int y,int ff){f[abs(x)][y] += (x > 0 ? 1 : -1) * (qry(dfn[ff] + siz[ff] - 1) - qry(dfn[ff] - 1));};
            for(char c : s[k]) p = ch[p][c - 'a'],ad(i,pk ++,p); 
        };auto addB = [&](int i,int k){
            int p = 0,pk = 0,rk = 0,len = s[k].size(); 
            auto ad = [&](int x,int y,int ff){f[abs(x)][y] += (x > 0 ? 1 : -1) * (qry(dfn[ff] + siz[ff] - 1) - qry(dfn[ff] - 1));};
            for(int j = 0;j <= B;j ++) ad(i,rk ++,run[k][j]);
            for(int j = len - B - 1;j < len;j ++) ad(i,rk ++,run[k][j]);
        };auto addall = [&](int i,int j,int k){
            int len = s[pos[abs(i)-1]].size(),p = 0,pk = 0;
            for(int l = 0;l < B;l ++) p = ch[p][s[pos[abs(i)-1]][l] - 'a'];
            for(int l = B;l < len - B;l ++) 
                p = ch[p][s[pos[abs(i)-1]][l] - 'a'],
                pa[mp[{abs(i),j+1,k+1}]][l-B] += (i > 0 ? 1 : -1) * (qry(dfn[p] + siz[p] - 1) - qry(dfn[p] - 1));
        };
        for(int i = 1;i <= n;i ++){
            int p = 0;   
            for(char c : s[i]) p = ch[p][c - 'a'],upd(dfn[p],1);
            for(auto [id,k,kd] : qu[i])
                if(kd) add(id,k,s[k].size());
                else addB(id,k);
            for(auto [x,y,z] : rm[i]) addall(x,y,z);
        }
    }
}A,AR;
int main(){
    // freopen("P10176.in","r",stdin),freopen("P10176.out","w",stdout);
    ios::sync_with_stdio(0),cin.tie(0); int qcnt = 0;
    cin >> n >> q;  
    for(int i = 1;i <= n;i ++) 
        if(cin >> s[i],A.ins(i,s[i]),AR.ins(i,string(s[i].rbegin(),s[i].rend())),s[i].size() > B)
            pos.push_back(i); 
    A.work(),AR.work();
    for(int i = 1;i <= q;i ++)
        if(cin >> l >> r >> k,s[k].size() <= 2*B+2) A.add1(i,l,r,k,s[k].size(),1),AR.add1(i,l,r,k,s[k].size(),1);
        else{
            qk[i] = k,ql[i] = l,qr[i] = r,A.add1(i,l,r,k,2*B+2,0),AR.add1(i,l,r,k,2*B+2,0);
            auto lb = [&](int x){return lower_bound(pos.begin(),pos.end(),x) - pos.begin();};
            auto rb = [&](int x){return upper_bound(pos.begin(),pos.end(),x) - pos.begin();};
            int d = lb(qk[i]) + 1,l = lb(ql[i]) + 1,r = rb(qr[i]);
            if(l <= r && !mp[{d,l,r}]) A.add2(d-1,l-1,r-1,++qcnt),AR.add2(d-1,l-1,r-1,qcnt),mp[{d,l,r}] = qcnt;
        }
    A.sol(),AR.sol();
    for(int i = 1;i <= qcnt;i ++){
        int d = A.pa[i].size(); 
        for(int l = 0;l < d - 1;l ++) pre[i] += 1ll * A.pa[i][l] * AR.pa[i][d - l - 2];
    }for(int i = 1;i <= q;i ++){
        int d = A.f[i].size(); ll ans = 0; 
        if(!ql[i])
            for(int j = 0;j < d - 1;j ++) ans += 1ll * A.f[i][j] * AR.f[i][d - j - 2];
        else{
            for(int j = 0;j < B;j ++) ans += 1ll * A.f[i][j] * AR.f[i][d - j - 2];
            for(int j = 0;j < B;j ++) ans += 1ll * AR.f[i][j] * A.f[i][d - j - 2];
            auto lb = [&](int x){return lower_bound(pos.begin(),pos.end(),x) - pos.begin();};
            auto rb = [&](int x){return upper_bound(pos.begin(),pos.end(),x) - pos.begin();};
            int d = lb(qk[i]) + 1,l = lb(ql[i]) + 1,r = rb(qr[i]);
            if(l <= r) ans += pre[mp[{d,l,r}]];
        }cout << ans << "\n";
    }
}

\(\color{blue}(191)\) CF914F Substrings in a String

难度 \(^*2900\)字符串;

第一次见 bitset 做串匹配。

考虑如果 \(t_i = c\),则所有合法的串末尾的前 \(|t| - i\) 个字符一定是 \(c\),可以用 bitsetand 运算实现这一过程。时间复杂度 \(\mathcal O(\dfrac{n^2}{w})\)

代码
/**
 *    author: sunkuangzheng
 *    created: 12.03.2024 10:32:55
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 1e5+5;
using namespace std;
int T,n,q,op,l,r; string s,t; bitset<N> g[26],ans; char c;
void los(){
    cin >> s >> q;
    for(int i = 0;i < 26;i ++)
        for(int j = 0;j < s.size();j ++) if(s[j] - 'a' == i) g[i].set(j);
    while(q --){
        cin >> op >> l,l --;
        if(op == 1)
            cin >> c,g[s[l] - 'a'].reset(l),g[c - 'a'].set(l),s[l] = c;
        else{
            cin >> r >> t,r --;
            if(t.size() > r - l + 1) {cout << "0\n"; continue;}
            ans.set();
            for(int j = 0;j < t.size();j ++)
                ans &= g[t[j] - 'a'] << (t.size() - j - 1);
            cout << (ans >> (l + t.size() - 1)).count() - (ans >> (r + 1)).count() << "\n";
        }
    }

}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(192)\) [ARC061E] すぬけ君の地下鉄旅行

难度 \(^*2400\)最短路;图论建模

对每个连通的相同颜色边见虚点跑最短路。

代码
/**
 *    author: sunkuangzheng
 *    created: 12.03.2024 16:58:05
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
const int N = 3e6+5;
using namespace std;
int T,n,m,dis[N],u,v,w,fa[N],vis[N]; vector<pair<int,int>> g[N],ed[N];
int fd(int x){return x == fa[x] ? x : fa[x] = fd(fa[x]);}
void mg(int u,int v){fa[fd(u)] = fd(v);}
void los(){
    cin >> n >> m; vector<int> c; int mx = 0;
    for(int i = 1;i <= n;i ++) fa[i] = i;
    for(int i = 1;i <= m;i ++)
        cin >> u >> v >> w,ed[w].emplace_back(u,v),mx = max(mx,w);
    int tot = n;
    for(int i = 1;i <= mx;i ++){
        for(auto [u,v] : ed[i]) mg(u,v);
        map<int,set<int>> mp;
        for(auto [u,v] : ed[i]) mp[fd(u)].insert(u),mp[fd(v)].insert(v);
        for(auto [x,y] : mp){
            ++tot;
            for(int j : y) g[tot].emplace_back(j,1),g[j].emplace_back(tot,1);
        }for(auto [u,v] : ed[i]) fa[u] = u,fa[v] = v;
    }queue<int> q; q.push(1),dis[1] = 0;
    for(int i = 2;i <= tot;i ++) dis[i] = 1e9;
    while(q.size()){
        int u = q.front(); q.pop();
        for(auto [v,w] : g[u]) if(dis[v] > dis[u] + w) dis[v] = dis[u] + w,q.push(v);
    }assert(dis[n] % 2 == 0);
    cout << (dis[n] == 1e9 ? -1 : dis[n] / 2);
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(193)\) CF1327G Letters and Question Marks

难度 \(^*2600\)动态规划,DP;状态压缩;AC 自动机

注意到 \(|\Sigma| = 14\),那么状压即可。问号很少,对于非问号的连续段处理每个点会走到哪,避免无用转移。

代码
/**
 *    author: sunkuangzheng
 *    created: 12.03.2024 18:18:13
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
using ll = long long;
#define int long long
const int N = 1e3+5;
using namespace std;
int T,n,ch[N][14],ed[N],fa[N],tot,x,cnt,f[N][(1<<14)+1],lt[N],val[N]; string t;
void los(){
    cin >> n;
    auto ins = [&](int x,string t){
        int s = 0;
        for(char c : t) s = (ch[s][c - 'a'] ? ch[s][c - 'a'] : (ch[s][c - 'a'] = ++tot));
        ed[s] += x;
    };auto build = [&](){
        queue<int> q;
        for(int i = 0;i < 14;i ++) if(ch[0][i]) q.push(ch[0][i]);
        while(q.size()){
            int u = q.front(); q.pop(); ed[u] += ed[fa[u]];
            for(int i = 0;i < 14;i ++){
                if(ch[u][i]) fa[ch[u][i]] = ch[fa[u]][i],q.push(ch[u][i]);
                else ch[u][i] = ch[fa[u]][i];
            }
        }
    };for(int i = 1;i <= n;i ++) cin >> t >> x,ins(x,t); build();
    cin >> t; int p = 0;  memset(f,128,sizeof(f)),f[0][0] = 0;
    iota(lt,lt+tot+1,0);
    auto chmax = [&](int &x,int y){x = max(x,y);};
    for(char c : t){
        if(c == '?'){
            for(int i = 0;i <= tot;i ++){
                for(int j = 0;j < (1 << 14);j ++){
                    if(f[i][j] < -1e18 || __builtin_popcount(j) != cnt) continue; 
                    f[i][j] += val[i]; int p = lt[i];
                    for(int k = 0;k < 14;k ++){
                        if((j >> k) & 1) continue;
                        chmax(f[ch[p][k]][j | (1 << k)],f[i][j] + ed[ch[p][k]]);
                    }
                }lt[i] = i,val[i] = 0;
            }++ cnt;
        }else
            for(int i = 0;i <= tot;i ++) lt[i] = ch[lt[i]][c - 'a'],val[i] += ed[lt[i]];
    }int ans = -1e18;
    for(int i = 0;i <= tot;i ++) for(int j = 0;j < (1 << 14);j ++) 
        if(__builtin_popcount(j) == cnt) chmax(ans,f[i][j] + val[i]);
    cout << ans;
}signed main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}

\(\color{blue}(194)\) [ARC159F] Good Division

难度 \(^*2900\)动态规划,DP;分治

区间的合法条件是长度为偶数且不存在绝对众数。

一个区间的前缀绝对众数只有 \(\mathcal O(\log n)\) 种。

考虑分治,用 \([l,mid]\) 转移给 \([mid+1,r]\)。枚举区间的 \(\mathcal O(\log n)\) 种众数,对众数的多余出现次数(即摩尔投票的 \(cnt\))做前缀和后即可快速知道存在绝对众数的区间,减去即可。

时间复杂度 \(\mathcal O(n \log^2 n)\)

代码
/**
 *    author: sunkuangzheng
 *    created: 13.03.2024 08:14:49
**/
#include<bits/stdc++.h>
#ifdef DEBUG_LOCAL
#include <mydebug/debug.h>
#endif
#include <atcoder/modint>
using Z = atcoder::modint998244353;
using ll = long long;
const int N = 1e6+5;
using namespace std;
int T,n,a[N],vis[N]; Z f[N];
void los(){
    cin >> n,f[0] = 1;
    for(int i = 1;i <= n*2;i ++) cin >> a[i];
    auto sol = [&](auto self,int l,int r) -> void {
        if(l == r) return ;
        int mid = (l + r) / 2; self(self,l,mid);
        set<int> b; Z s = 0;
        for(int j = mid;j > l;j --){
            if((++vis[a[j*2-1]]) > (mid - j + 1)) b.insert(a[j*2-1]);
            if((++vis[a[j*2]]) > (mid - j + 1)) b.insert(a[j*2]);
        }for(int j = mid;j > l;j --) vis[a[j*2-1]] --,vis[a[j*2]] --;
        for(int j = mid + 1;j <= r;j ++){
            if((++vis[a[j*2-1]]) > (j - mid)) b.insert(a[j*2-1]);
            if((++vis[a[j*2]]) > (j - mid)) b.insert(a[j*2]);
        }for(int j = mid + 1;j <= r;j ++) vis[a[j*2-1]] --,vis[a[j*2]] --;
        for(int j = mid;j >= l;j --) s += f[j];
        for(int j = mid + 1;j <= r;j ++) f[j] += s;
        for(int x : b){
            int d = (r - l + 1) * 2,ct = 0;
            vector<Z> num(d * 2 + 10);
            num[d] += f[mid];
            for(int j = mid;j > l;j --){
                ct += (a[j*2-1] == x ? 1 : -1),ct += (a[j*2] == x ? 1 : -1);
                num[ct + d] += f[j - 1];
            }for(int j = 2 * d;j >= 0;j --) num[j] += num[j + 1]; ct = 0;
            for(int j = mid + 1;j <= r;j ++){
                ct += (a[j*2-1] == x ? 1 : -1),ct += (a[j*2] == x ? 1 : -1);
                f[j] -= num[-ct + d + 1];
            }
        }self(self,mid+1,r);
    };sol(sol,0,n);
    cout << f[n].val();
}int main(){
    ios::sync_with_stdio(0),cin.tie(0);
    for(T = 1;T --;) los();
}
posted @ 2024-02-20 12:42  sunkuangzheng  阅读(74)  评论(0编辑  收藏  举报