做题笔记 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
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
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}\) 可以用矩阵快速幂优化:
总时间复杂度 \(\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}\) 的数量,概率是:
其中如果 \(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\),可以用 bitset
的 and
运算实现这一过程。时间复杂度 \(\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();
}