vp + 补题 + 随机做题 记录九
这期杭电质量整体偏低(虽然我也提供了部分题目)。挑一些有意思的记录一下。
2026“钉耙编程”中国大学生算法设计暑期联赛(10) 1001 串串
\(A_i\) 与 \(A_{i+1}\) 的模式是重串加一个字符,因此原问题至少比找出所有重串难。
场上在考虑 \(k\) 不超过根号,但事实上由于 \(A_1\) 无法确定,这个性质用处不大。
那不妨考虑如果 \(A_1\) 确定,这个优秀串的形态是什么样的。首先长度已知,其次所有 \(A_i\) 和 \(A_{i+1}\) 拼接后去掉末尾字符是原位置的重串。
这里用到一个逆向思维:既然在 \(A_1\) 确定时能够判断每个重串是否属于这个优秀串,我们逆过来考虑每个重串能为哪些优秀串提供判据。
经过计算可以得到每个重串对于固定的 \(|A_1|\) 应该的 \(A_1\) 起始位置,称为其贡献位置。暴力的做法可以考虑枚举 \(|A_1|\),遍历所有重串,在他们的贡献位置打标记。如果一个位置被标记了 \(k-1\) 次,则这个位置是一个可行的优秀串起点,累入答案。
考虑优化。事实上,我们只需要考虑 \(|A_1|=1\) 的贡献位置,对于其他的 \(|A_1|\),所有重串贡献位置的偏移量相同,画个图会更直观。为了避免重复,我们仅考虑连续的 \(k-1\) 种长度的重串的贡献位置,用一个线段树维护(最大覆盖次数,对应该覆盖次数的位置)即可。
复杂度为 \(O(n\log^2n)\)。
这个逆向思维很有价值,类似于判定问题的转置。
代码
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=3e5+50;
int T, n, m;
struct SA{
char s[N];
int buc[N], rk[N], sa[N], od[N], id[N], ht[21][N], w, p;
bool eq(int x, int y) {
return od[x] == od[y] && od[x + w] == od[y + w];
}
void getSA() {
int m=26;
for(int i=0; i<=26; ++i) buc[i]=0;
for (int i = 1; i <= n; ++i)
++buc[rk[i] = s[i]];
for (int i = 1; i <= m; ++i)
buc[i] += buc[i - 1];
for (int i = n; i; --i)
sa[buc[rk[i]]--] = i;
for (int i = 1; i <= m; ++i)
buc[i] = 0;
w = 1;
p = 0;
while (true) {
for (int i = n; i > n - w; --i)
id[++p] = i;
for (int i = 1; i <= n; ++i)
if (sa[i] > w)
id[++p] = sa[i] - w;
for (int i = 1; i <= n; ++i)
++buc[od[i] = rk[i]];
for (int i = 1; i <= m; ++i)
buc[i] += buc[i - 1];
for (int i = n; i; --i)
sa[buc[rk[id[i]]]--] = id[i];
for (int i = 1; i <= m; ++i)
buc[i] = 0;
rk[sa[1]] = p = 1;
for (int i = 2; i <= n; ++i) {
if (!eq(sa[i], sa[i - 1]))
++p;
rk[sa[i]] = p;
}
if (p == n)
break;
w <<= 1, m = p, p = 0;
}
}
void build() {
s[n + 1] = '!';
for (int i = 1, k = 0; i <= n; ++i) {
if (k)
--k;
if (rk[i] == 1)
continue;
while (s[i + k] == s[sa[rk[i] - 1] + k])
++k;
ht[0][rk[i]] = k;
}
for (int t = 1; t < 21; ++t)
for (int i = 2; i + (1 << t) - 1 <= n; ++i)
ht[t][i] = min(ht[t - 1][i], ht[t - 1][i + (1 << (t - 1))]);
}
int qry(int x, int y) {
if(y>n) return 0;
if (x == y)
return n - x + 1;
x = rk[x], y = rk[y];
if (x > y)
swap(x, y);
int k = __lg(y - x);
return min(ht[k][x + 1], ht[k][y - (1 << k) + 1]);
}
}A, B;
char s[N];
int stk[N], top;
vector<pii> bin[N];
void work(int l, int r){
int len=r-l+1;
int la=A.qry(l, r+1);
int lb=B.qry(n-r+1, n-l+2);
if(lb<len&&la+lb>=len){
l-=lb; r+=la;
if(r==n) --r;
for(int p=len*2; l+p-1<=r; p+=2*len){
bin[p/2].ep(l, r-p+1);
}
}
}
int tr[N<<2], sz[N<<2], tag[N<<2];
void apply(int p, int v){
tr[p]+=v; tag[p]+=v;
}
void down(int p){
if(tag[p]) apply(p<<1, tag[p]), apply(p<<1|1, tag[p]), tag[p]=0;
}
void up(int p){
tr[p]=max(tr[p<<1], tr[p<<1|1]);
sz[p]=0;
if(tr[p]==tr[p<<1]) sz[p]+=sz[p<<1];
if(tr[p]==tr[p<<1|1]) sz[p]+=sz[p<<1|1];
}
void build(int p, int l, int r){
tr[p]=0; sz[p]=r-l+1; tag[p]=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(p<<1, l, mid); build(p<<1|1, mid+1, r);
}
void mdf(int p, int l, int r, int L, int R, int v){
if(L<=l&&r<=R){
apply(p, v); return ;
}
int mid=(l+r)>>1;
down(p);
if(L<=mid) mdf(p<<1, l, mid, L, R, v);
if(R>mid) mdf(p<<1|1, mid+1, r, L, R, v);
up(p);
}
void solve(){
read(n); read(m);
scanf("%s", s+1);
for(int i=1; i<=n; ++i){
A.s[i]=B.s[n-i+1]=s[i]-'a'+1;
bin[i].clear();
}
A.getSA(); A.build();
B.getSA(); B.build();
top=0;
for(int i=n; i; --i){
while(top&&A.rk[i]>A.rk[stk[top]]){
--top;
}
if(top){
work(i, stk[top]-1);
}
stk[++top]=i;
}
top=0;
for(int i=n; i; --i){
while(top&&A.rk[i]<A.rk[stk[top]]){
--top;
}
if(top){
work(i, stk[top]-1);
}
stk[++top]=i;
}
build(1, 1, n);
ll ans=0;
for(int i=1; 1ll*m*(i+i-m+1)<=n*2; ++i){
if(i>m-1){
for(auto [l, r]:bin[i-m+1]){
ll rl=l-1ll*(i-m)*(i-m+1)/2;
ll rr=r-1ll*(i-m)*(i-m+1)/2;
rl=(rl%n+n)%n+1;
rr=(rr%n+n)%n+1;
// cout<<l<<' '<<r<<' '<<-1<<endl;
if(rl<=rr) mdf(1, 1, n, rl, rr, -1);
else mdf(1, 1, n, rl, n, -1), mdf(1, 1, n, 1, rr, -1);
}
}
for(auto [l, r]:bin[i]){
ll rl=l-1ll*i*(i-1)/2;
ll rr=r-1ll*i*(i-1)/2;
rl=(rl%n+n)%n+1;
rr=(rr%n+n)%n+1;
// cout<<l<<' '<<r<<' '<<1<<endl;
if(rl<=rr) mdf(1, 1, n, rl, rr, 1);
else mdf(1, 1, n, rl, n, 1), mdf(1, 1, n, 1, rr, 1);
}
if(tr[1]==m-1) ans+=sz[1];
}
printf("%lld\n", ans);
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
solve();
}
return 0;
}
2026“钉耙编程”中国大学生算法设计暑期联赛(10) 1007 小白的烦恼
序列带颜色基本等价于升维,考虑颜色数量分治。
对于出现次数超过 \(B\) 的颜色,称为大颜色,其余为小颜色。
大颜色可以直接用前缀和算,离线后可以做到线性空间,时间单次 \(O(\frac{n}{B})\)。
小颜色比较难处理,主要问题在于最值是幂等半群信息,不具备可减性,要考虑不删或特殊的维护方式。我场上因为思路框架基于莫队,就考虑特殊维护,想办法利用上 \(a_i\in\{-1, 1\}\) 这个性质。
\(a_i\in\{-1, 1\}\) 意味着区间和绝对值不超过 \(B\),我们尝试空间换时间。
具体的,我们维护一个后缀标记数据结构 \(D\),当颜色 \(c\) 出现次数小于等于 \(i\) 时,\(D[i][c]=1\),否则 \(D[i][c]=0\)。初始化时把 \(D[0\leq i\leq B][c]\) 赋为 1,其余为 0。每次查询转化为找 \(D[k]\) 中最靠前的 1 是谁。为了和莫队平衡,我们使用单次修改 \(O(1)\),查询 \(O(\sqrt{n})\) 的数据结构,分块即可。
于是时间复杂度为 \(O(\frac{qn}{B}+q\sqrt{n}+n\sqrt{n})\),空间复杂度为 \(O(nB)\),取 \(B=\sqrt{n}\) 即可。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5, B=200, M=350;
int T, n, m, q;
int a[N], c[N];
int cnt[N];
int lp[N], rp[N], bid[N];
bool isbig[N];
int rd(){
char c=getchar();
int tot=0; bool f=0;
while(!isdigit(c)) f|=(c=='-'),c=getchar();
while(isdigit(c)) tot=(tot<<1)+(tot<<3)+(c^48),c=getchar();
return f?-tot:tot;
}
struct DS{
int sum[M];
bool v[N];
void init(int c){
if(c==0){
for(int i=1; i<=m; ++i) v[i]=0;
for(int i=1; i<=bid[m]; ++i) sum[i]=0;
}
else{
for(int i=1; i<=bid[m]; ++i) sum[i]=0;
for(int i=1; i<=m; ++i) v[i]=!isbig[i], sum[bid[i]]+=v[i];
}
}
void add(int x){
v[x]=1; sum[bid[x]]++;
}
void del(int x){
v[x]=0; sum[bid[x]]--;
}
int fnd(){
for(int i=1; i<=bid[m]; ++i){
if(!sum[i]) continue;
for(int j=lp[i]; j<=rp[i]; ++j) if(v[j]) return j;
}
return 1e9;
}
}D[B*2+10];
struct node{
int l, r, k, id;
}f[N];
int ans[N];
int sum[N];
inline void add(int x){
if(isbig[c[x]]) return ;
if(a[x]==1){
D[sum[c[x]]+B].del(c[x]);
++sum[c[x]];
}
else{
--sum[c[x]];
D[sum[c[x]]+B].add(c[x]);
}
}
inline void del(int x){
if(isbig[c[x]]) return ;
if(a[x]==1){
--sum[c[x]];
D[sum[c[x]]+B].add(c[x]);
}
else{
D[sum[c[x]]+B].del(c[x]);
++sum[c[x]];
}
}
void solve(){
n=rd(); m=rd(); q=rd();
for(int i=1; i<=m; ++i) cnt[i]=0, isbig[i]=0;
for(int i=1; i<=n; ++i) {
c[i]=rd(); cnt[c[i]]++;
}
for(int i=1; i<=n; ++i){
a[i]=rd();
}
for(int i=1; i<=m; ++i) bid[i]=(i-1)/M+1, isbig[i]=(cnt[i]>B);
for(int i=1; i<=bid[m]; ++i){
lp[i]=rp[i-1]+1; rp[i]=rp[i-1]+M;
}
rp[bid[m]]=m;
for(int i=-B; i<=B; ++i){
D[i+B].init(i>=0);
}
for(int i=1; i<=q; ++i){
f[i].l=rd(); f[i].r=rd(); f[i].k=rd();
f[i].id=i;
ans[i]=1e9;
}
for(int o=1; o<=m; ++o) if(isbig[o]){
for(int i=1; i<=n; ++i){
if(c[i]!=o) sum[i]=sum[i-1];
else sum[i]=sum[i-1]+a[i];
}
for(int i=1; i<=q; ++i){
if(sum[f[i].r]-sum[f[i].l-1]<=f[i].k){
ans[i]=min(ans[i], o);
}
}
}
sort(f+1, f+q+1, [&](node x, node y){
if((x.l>>8)!=(y.l>>8)) return x.l<y.l;
return x.r<y.r;
});
for(int i=1; i<=m; ++i) sum[i]=0;
int cl=1, cr=0;
for(int o=1; o<=q; ++o){
int l=f[o].l, r=f[o].r, id=f[o].id, k=f[o].k;
if(k<-B) continue;
k=min(k, B);
while(cr<r){
++cr;
add(cr);
}
while(cl>l){
--cl;
add(cl);
}
while(cr>r){
del(cr);
--cr;
}
while(cl<l){
del(cl);
++cl;
}
ans[id]=min(ans[id], D[k+B].fnd());
}
for(int i=1; i<=q; ++i){
if(ans[i]==1e9) ans[i]=-1;
printf("%d\n", ans[i]);
}
}
int main(){
// freopen("7.out", "w", stdout);
T=rd();
while(T--){
solve();
}
}
2026“钉耙编程”中国大学生算法设计暑期联赛(9) 1003 Secluded Sensei
第一点要求路径是最短路径,启发我们先找个数据结构存储所有最短路径,这就是最短路径树。
接下来考虑题目的闭邻域。如果我们把图中的点按照到起点的距离分层,根据最短路径树的性质,在第 \(i\) 层的点只与被第 \(i-1, i, i+1\) 这三层的点连边。
于是我们只需要记录每条路径末尾两个点就可以 DP 了,但可达性还需要 bitset 求解,所以复杂度为 \(O(\frac{n^4}{w})\)。
代码
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int N=505;
const int mod=998244353;
// #define int long long
int Test, n, m, s, t;
int rd(){
char c=getchar();
int tot=0;
while(!isdigit(c)) c=getchar();
while(isdigit(c)) tot=(tot<<1)+(tot<<3)+(c^48),c=getchar();
return tot;
}
int e[N][N];
int dep[N];
vector<int> bin[N];
int cnt[N][N];
int dpmn[N][N], dpsz[N][N];
bitset<500> msk[N][N], slf[N];
void solve(){
n=rd(); m=rd(); s=rd(); t=rd();
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
e[i][j]=0;
}
}
for(int i=1, x, y; i<=m; ++i){
x=rd(); y=rd();
e[x][y]=e[y][x]=1;
}
for(int i=1; i<=n; ++i){
dep[i]=1e9;
}
dep[s]=0;
queue<int> que;
que.push(s);
while(!que.empty()){
int x=que.front(); que.pop();
for(int y=1; y<=n; ++y) if(e[x][y]){
if(dep[y]>dep[x]+1){
dep[y]=dep[x]+1;
que.push(y);
}
}
}
// for(int i=1; i<=n; ++i) cout<<dep[i]<<endl;
for(int i=0; i<n; ++i) bin[i].clear();
for(int i=1; i<=n; ++i) if(dep[i]<n) bin[dep[i]].push_back(i);
for(int i=1; i<=n; ++i) {
for(int j=1; j<=n; ++j){
dpmn[i][j]=1e9; dpsz[i][j]=0;
}
}
for(int i=1; i<=n; ++i) if(e[s][i]){
dpmn[s][i]=1; dpsz[s][i]=1;
}
for(int i=1; i<n; ++i){
if(bin[i].empty()) break;
for(auto x:bin[i-1]){
for(auto z:bin[i+1]){
msk[x][z].reset();
for(auto y:bin[i]) if(e[x][y]||e[y][z]){
msk[x][z][y]=1;
}
}
}
for(auto x:bin[i]) {
slf[x].reset();
for(auto y:bin[i]) if(e[x][y]){
slf[x][y]=1;
}
}
for(auto y:bin[i]){
for(auto x:bin[i-1]) if(e[x][y]){
for(auto z:bin[i+1]) if(e[y][z]){
int nval=dpmn[x][y];
nval+=(slf[y]|msk[x][z]).count();
if(nval<dpmn[y][z]){
dpmn[y][z]=nval, dpsz[y][z]=dpsz[x][y];
}
else if(nval==dpmn[y][z]){
dpsz[y][z]=(dpsz[y][z]+dpsz[x][y])%mod;
}
}
}
}
}
// for(int i=1; i<=n; ++i){
// for(int j=1; j<=n; ++j) if(dep[j]==dep[i]+1){
// cout<<i<<' '<<j<<' '<<dpmn[i][j]<<endl;
// }
// }
int ansmn=1e9, anssz=0;
for(auto x:bin[dep[t]-1]) if(e[x][t]){
int curv=dpmn[x][t];
for(auto y:bin[dep[t]]) if(e[x][y]||e[t][y]) ++curv;
for(auto y:bin[dep[t]+1]) if(e[t][y]) ++curv;
if(curv<ansmn){
ansmn=curv, anssz=dpsz[x][t];
}
else if(curv==ansmn){
anssz=(anssz+dpsz[x][t])%mod;
}
}
printf("%d %d\n", ansmn, anssz);
}
signed main(){
Test=rd();
while(Test--){
solve();
}
}
2026“钉耙编程”中国大学生算法设计暑期联赛(8) 1011 数轴上的染色翻转怎么还有恢复与加密
可持久化操作只能在线处理,同时也要求不能用均摊复杂度的数据结构。先分析要完成什么。
既然不能均摊,2 操作只能考虑维护扩展操作的中心和距离,反推一个点是否被扩展影响到变黑。对于 3 操作,能想到的合适的数据结构只有平衡树了。
于是我们考虑用可持久化平衡树,这里用 fhq treap。
为了 2 操作,我们考虑维护所有黑点区间的左右端点,那么扩展相当于左端点全体减 \(x\),右端点全体加 \(x\)。我们分别维护左右端点,查询时如果小于 \(x\) 的右端点数量小于小于等于 \(x\) 的左端点数量,这说明至少有一个区间跨过了 \(x\),\(x\) 就是黑色的,否则就是白色。
接下来只剩 3 操作不好处理。对于包含在翻转区间之内的黑区间,只需要提取翻转区间对应的子树、打取反、翻转、加法标记,交换两棵子树即可。对于完全不在翻转区间内的黑区间,不需要操作。剩下的区间,我们可以先把他们分裂(比如只包含翻转区间左端点的 \([l,r]\),分裂为 \([l, L-1], [L, r]\) 两个区间),再和第一类一起翻转即可。
分裂等价于在两棵树上分别增加一些端点,需要知道到底要增加多少端点。以分裂需要新增的右端点 \(L-1\) 的数量为例,只需要计算 \((l\leq L)-(r\leq L)\) 的值即可。
于是只需要在平衡树上维护每个位置的端点的数量,支持子树加,子树取反,子树翻转即可。复杂度单 \(\log\)。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
mt19937 rnd(1234);
struct IO{
static const int S=1<<21;
char buf[S],*p1,*p2;int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
template<typename T>inline IO&operator >> (T&x){
x=0;bool f=0;char ch=gc();
while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
f?x=-x:0;return *this;
}
inline IO&operator << (const char c){pc(c);return *this;}
template<typename T>inline IO&operator << (T x){
if(x<0) pc('-'),x=-x;
do{st[++st[0]]=x%10,x/=10;}while(x);
while(st[0]) pc('0'+st[st[0]--]);return *this;
}
}fin,fout;
const int N=1e6+5;
int T, n;
int tim;
struct node{
int ls, rs;
int siz, w, pri, stp;
ll val;
ll tagadd;
bool tagrev;
}tr[N*30];
int rtl[N], rtr[N];
int idx;
int gen(int x){
if(tr[x].stp==tim) return x;
++idx;
tr[idx]=tr[x];
tr[idx].stp=tim; tr[idx].pri=rnd();
return idx;
}
int apply_rev(int x){
if(!x) return 0;
x=gen(x);
tr[x].tagrev^=1;
tr[x].tagadd*=-1; tr[x].val*=-1;
swap(tr[x].ls, tr[x].rs);
return x;
}
int apply_add(int x, ll v){
if(!x) return 0;
x=gen(x);
tr[x].val+=v;
tr[x].tagadd+=v;
return x;
}
int down(int x){
if(!x) return 0;
x=gen(x);
if(tr[x].tagrev){
tr[x].ls=apply_rev(tr[x].ls);
tr[x].rs=apply_rev(tr[x].rs);
}
if(tr[x].tagadd){
tr[x].ls=apply_add(tr[x].ls, tr[x].tagadd);
tr[x].rs=apply_add(tr[x].rs, tr[x].tagadd);
}
tr[x].tagadd=tr[x].tagrev=0;
return x;
}
int up(int x){
if(!x) return 0;
tr[x].siz=tr[x].w+tr[tr[x].ls].siz+tr[tr[x].rs].siz;
return x;
}
void Split(int rt, ll v, int &x, int &y){
if(rt==0){
x=y=0;
return ;
}
rt=down(rt);
if(tr[rt].val<=v){
Split(tr[rt].rs, v, x, y);
tr[rt].rs=x;
x=up(rt);
}
else{
Split(tr[rt].ls, v, x, y);
tr[rt].ls=y;
y=up(rt);
}
}
int Merge(int x, int y){
if(x==0||y==0) return x+y;
if(tr[x].pri<tr[y].pri){
x=down(x);
tr[x].rs=Merge(tr[x].rs, y);
return up(x);
}
y=down(y);
tr[y].ls=Merge(x, tr[y].ls);
return up(y);
}
void add(int &rt, ll x, int v){
if(!v) return ;
int xl, xm, xr;
Split(rt, x-1, xl, xr);
Split(xr, x, xm, xr);
// cout<<tr[xl].siz<<' '<<tr[xm].siz<<' '<<tr[xr].siz<<endl;
++idx;
tr[idx]=tr[xm]; tr[idx].pri=rnd(); tr[idx].val=x;
tr[idx].w+=v;
tr[idx].siz+=v;
tr[idx].stp=tim;
xm=idx;
rt=Merge(Merge(xl, xm), xr);
}
void solve(){
fin>>n;
idx=2;
int lst=0;
rtl[0]=1; rtr[0]=2;
tr[1].val=0; tr[1].tagadd=tr[1].tagrev=0; tr[1].ls=tr[1].rs=0; tr[1].w=tr[1].siz=1; tr[1].stp=0;
tr[2].val=0; tr[2].tagadd=tr[2].tagrev=0; tr[2].ls=tr[2].rs=0; tr[2].w=tr[2].siz=1; tr[2].stp=0;
for(int i=1, op; i<=n; ++i){
tim=i;
ll x, y;
rtl[i]=rtl[i-1]; rtr[i]=rtr[i-1];
fin>>op>>x;
x^=lst;
if(op==1){
add(rtl[i], x, 1);
add(rtr[i], x, 1);
}
if(op==2){
rtl[i]=apply_add(rtl[i], -x);
rtr[i]=apply_add(rtr[i], x);
}
if(op==3){
fin>>y;
y^=lst;
int xl, xm, xr, yl, ym, yr;
Split(rtl[i], x-1, xl, xr);
Split(xr, y, xm, xr);
Split(rtr[i], x-1, yl, yr);
Split(yr, y, ym, yr);
xm=apply_rev(xm);
xm=apply_add(xm, x+y);
ym=apply_rev(ym);
ym=apply_add(ym, x+y);
int add1=tr[xl].siz-tr[yl].siz;
int add2=tr[yr].siz-tr[xr].siz;
rtl[i]=Merge(Merge(xl, ym), xr);
rtr[i]=Merge(Merge(yl, xm), yr);
add(rtr[i], x-1, add1);
add(rtr[i], y, add1);
add(rtl[i], x, add2);
add(rtl[i], y+1, add2);
}
if(op==4){
rtl[i]=rtl[x]; rtr[i]=rtr[x];
}
if(op==5){
int xl, xr, yl, yr;
Split(rtl[i], x, xl, xr);
Split(rtr[i], x-1, yl, yr);
// cout<<tr[xl].siz<<' '<<tr[yl].siz<<endl;
if(tr[xl].siz==tr[yl].siz){
// cout<<"No"<<endl;
fout.pc('N'); fout.pc('o'); fout.pc('\n');
}
else{
// cout<<"Yes"<<endl;
fout.pc('Y'); fout.pc('e'); fout.pc('s'); fout.pc('\n');
++lst;
}
rtl[i]=rtl[i-1]; rtr[i]=rtr[i-1];
}
}
}
void run(){
fin>>T;
for(int i=1; i<=T; ++i){
solve();
// cout<<"finish:"<<i<<endl;
}
exit(0);
}
char __stack_space[256 << 20];
int main(){
run();
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
// #if defined(__x86_64__) || defined(_M_X64)
// __asm__ __volatile__ (
// "movq %0, %%rsp\n\t"
// "call %P1\n\t"
// :
// : "r" (__stack_space + sizeof(__stack_space)), "i" (run)
// : "memory"
// );
// #else
// __asm__ __volatile__ (
// "movl %0, %%esp\n\t"
// "call %P1\n\t"
// :
// : "r" (__stack_space + sizeof(__stack_space)), "i" (run)
// : "memory"
// );
// #endif
return 0;
}
2026“钉耙编程”中国大学生算法设计暑期联赛(7) 1001 今晚吃什么
等价于选 \(k\) 个串,满足第 \(i\) 个是第 \(i+1\) 个的严格子串的方案数。
容易发现 \(k\) 不超过根号。我们尝试直接暴力 DP。
考虑一个串 \(s\) 的子串是什么,一个经典套路是枚举 \(s\) 的前缀,查询其所有后缀,而一个串的后缀是其 fail 树上的祖先。于是原问题解法为,按长度递增顺序枚举所有串,枚举一个串时枚举其前缀并找到对应 trie 树上节点,那么他的 DP 值可以由这些节点的 fail 树祖先转移。
但直接这样做会有重复,我们可以每做完一个前缀,就把这个 fail 树链染黑,然后下一次查询时找到最浅的没被染色的点,只转移这部分树链即可。
最后是数据结构部分,我们有 \(O(n\sqrt{n})\) 次单点查询,\(O(n)\) 次区间修改(fail 子树 dfn 区间),用分块即可。
场上把 \(k\) 的上限写错了,更精细的上限应当是 \(\sqrt{2\sum|s_i|}+eps\)。警钟长鸣。
代码
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50, B=450, mod=998244353;
bool st;
int T,n;
char s[N];
int sl[N], sr[N], len[N];
int ans[N];
int lim;
vector<int> e[N];
int fa[N];
int sz[N], son[N], dfn[N], seq[N], timer, dep[N];
void dfs(int x){
dep[x]=dep[fa[x]]+1;
sz[x]=1; son[x]=0; dfn[x]=++timer; seq[timer]=x;
for(auto y:e[x]){
dfs(y); sz[x]+=sz[y];
if(sz[y]>sz[son[x]]) son[x]=y;
}
}
int tp[N], bot[N];
void dfs2(int x, int top){
tp[x]=top; bot[top]=x;
if(son[x]) dfs2(son[x], top);
for(auto y:e[x]) if(y!=son[x]) dfs2(y, y);
}
int del[N], col[N];
int fnd(int x){
while(x){
if(del[tp[x]]){
return x;
}
if(col[tp[x]]){
if(dep[col[tp[x]]]<dep[x]) {int rt=col[tp[x]]; col[tp[x]]=x; return rt;}
else return x;
}
if(x==bot[tp[x]]){
del[tp[x]]=1;
}
else {
col[tp[x]]=x;
}
x=fa[tp[x]];
}
return 0;
}
void clr(int x){
while(x){
del[tp[x]]=col[tp[x]]=0;
x=fa[tp[x]];
}
}
struct ACAM{
int tr[N][26], idx;
void build(){
queue<int> que;
fa[1]=1;
for(int i=0; i<26; ++i){
if(tr[1][i]) que.push(tr[1][i]), fa[tr[1][i]]=1;
else tr[1][i]=1;
}
while(!que.empty()){
int x=que.front(); que.pop();
for(int i=0; i<26; ++i){
if(tr[x][i]){
int v=tr[x][i];
que.push(v);
fa[v]=tr[fa[x]][i];
}
else{
tr[x][i]=tr[fa[x]][i];
}
}
}
for(int i=2; i<=idx; ++i) e[fa[i]].push_back(i);
timer=0;
fa[1]=0;
dfs(1);
dfs2(1, 1);
}
void clr(){
for(int i=1; i<=idx; ++i) {
for(int j=0; j<26; ++j) tr[i][j]=0;
e[i].clear(); fa[i]=0;
}
idx=1;
}
void ins(int i){
int p=1;
for(int j=sl[i]; j<=sr[i]; ++j){
if(!tr[p][s[j]-'a']) tr[p][s[j]-'a']=++idx;
p=tr[p][s[j]-'a'];
}
}
}A;
int lp[N], rp[N], bid[N];
struct DS{
int slf[N], tag[450];
void clr(){
for(int i=1; i<=sr[n]+1; ++i) slf[i]=0;
for(int i=1; i<=bid[sr[n]+1]; ++i) tag[i]=0;
}
void add(int l, int r, int v){
if(bid[l]==bid[r]){
for(int i=l; i<=r; ++i) slf[i]=(slf[i]+v)%mod;
return ;
}
for(int i=l; i<=rp[bid[l]]; ++i) slf[i]=(slf[i]+v)%mod;
for(int i=lp[bid[r]]; i<=r; ++i) slf[i]=(slf[i]+v)%mod;
for(int i=bid[l]+1; i<bid[r]; ++i) tag[i]=(tag[i]+v)%mod;
}
inline int get(int x){
return slf[x]+tag[bid[x]];
}
}D[636];
long long tmp[636];
bool ed;
void solve(){
scanf("%d", &n);
for(int i=1; i<=n; ++i) ans[i]=0;
A.clr();
for(int i=1; i<=n; ++i){
sl[i]=sr[i-1]+1;
scanf("%s", s+sl[i]);
len[i]=strlen(s+sl[i]);
sr[i]=sl[i]+len[i]-1;
A.ins(i);
}
A.build();
// for(int i=2; i<=A.idx; ++i) cout<<fa[i]<<' ';
// cout<<endl;
lim=1;
while(1ll*(lim+1)*(lim+2)<=2*sr[n]) ++lim;
lim=min(lim, n);
for(int i=1; i<=sr[n]+1; ++i) bid[i]=(i-1)/B+1;
for(int i=1; i<=bid[sr[n]+1]; ++i){
lp[i]=rp[i-1]+1; rp[i]=rp[i-1]+B;
}
rp[bid[sr[n]+1]]=sr[n]+1;
for(int i=1; i<=lim; ++i) D[i].clr();
for(int i=1; i<=n; ++i){
int p=1;
for(int j=1; j<=len[i]&&j<=lim; ++j) tmp[j]=0;
tmp[1]=1;
for(int j=sl[i]; j<=sr[i]; ++j){
p=A.tr[p][s[j]-'a'];
int q=fnd(p);
if(p==q) continue;
for(int k=1; k<lim&&k<=len[i-1]&&k<len[i]&&k<i; ++k){
tmp[k+1]=(tmp[k+1]+D[k].get(dfn[p]));
}
if(q!=0){
for(int k=1; k<lim&&k<=len[i-1]&&k<i; ++k){
tmp[k+1]=(tmp[k+1]+mod*2-D[k].get(dfn[q]));
}
}
}
for(int j=1; j<=len[i]&&j<=lim; ++j){
tmp[j]%=mod;
ans[j]=(ans[j]+tmp[j])%mod;
D[j].add(dfn[p], dfn[p]+sz[p]-1, tmp[j]);
}
p=1;
for(int j=sl[i]; j<=sr[i]; ++j){
p=A.tr[p][s[j]-'a'];
clr(p);
}
}
for(int i=0; i<n; ++i){
printf("%d ", ans[n-i]);
}
putchar('\n');
}
int main(){
// cout<<(&st-&ed)/1024/1024<<endl;
scanf("%d",&T);
while(T--){
solve();
}
}
2026“钉耙编程”中国大学生算法设计暑期联赛(7) 1007 今晚吃老歌
如果询问区间是 \([1, n]\),只需要线段树维护最小值,最小值出现次数,(最小值+1)出现次数。
考虑分块,包含在块内部的可以用上述线段树维护。左端点小于块的左端点的区间,只需要找到右端点最大次大的,对右端点同理找最小次小的。
事实上,线段树也是一种分块,用线段树的原因是方便维护后者。
暴力可以线段树维护 multiset,但常数过大。
常数优化考虑每次只进行 \(O(1)\) 次 multiset 操作,用线段树二分代替直接在 multiset 查询。以左端点为例,具体的,在线段树区间修改中,我们只在第一次分裂时进行 multiset 操作,然后在右侧线段树上维护每个右端点的最大次大左端点,记为 \(mx1, mx2\)。接下来只需要去线段树上二分,找到 \(L<=mx1, r<=R\) 中最大的 \(r\) 即可,次大类似。右端点同理。
剩下的就是根据覆盖情况确定线段树上查什么,这部分细节比较多。
总之复杂度 \(2\log\)。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=6e5+5, inf=1e9;
int T, n, m;
struct sgt1{
struct info{
int mn, c0, c1;
info(int _mn=0, int _c0=0, int _c1=0){
mn=_mn; c0=_c0; c1=_c1;
}
};
inline friend info operator +(info x, info y){
info z;
z.mn=min(x.mn, y.mn);
if(z.mn==x.mn) z.c0+=x.c0, z.c1+=x.c1;
else if(z.mn+1==x.mn) z.c1+=x.c0;
if(z.mn==y.mn) z.c0+=y.c0, z.c1+=y.c1;
else if(z.mn+1==y.mn) z.c1+=y.c0;
return z;
}
info tr[N<<1];
int tg[N<<1];
void clr(int p, int l, int r){
tr[p].mn=0; tr[p].c0=r-l+1; tr[p].c1=0; tg[p]=0;
if(l==r) return ;
int mid=(l+r)>>1;
clr(p<<1, l, mid); clr(p<<1|1, mid+1, r);
}
inline void apply(int p, int v){
tr[p].mn+=v;
tg[p]=tg[p]+v;
}
inline void down(int p){
if(tg[p]){
apply(p<<1, tg[p]);
apply(p<<1|1, tg[p]);
tg[p]=0;
}
}
void mdf(int p, int l, int r, int L, int R, int v){
if(L<=l&&r<=R){
apply(p, v);
return ;
}
down(p);
int mid=(l+r)>>1;
if(L<=mid) mdf(p<<1, l, mid, L, R, v);
if(R>mid) mdf(p<<1|1, mid+1, r, L, R, v);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
info get(int p, int l, int r, int L, int R){
if(L<=l&&r<=R){
return tr[p];
}
down(p);
int mid=(l+r)>>1;
if(L>mid) return get(p<<1|1, mid+1, r, L, R);
if(R<=mid) return get(p<<1, l, mid, L, R);
return get(p<<1, l, mid, L, R)+get(p<<1|1, mid+1, r, L, R);
}
}T1[20];
struct sgt2{
struct info{
int mn1, mn2;
info(int _mn1=inf, int _mn2=inf){
mn1=_mn1; mn2=_mn2;
}
};
inline friend info operator +(info x, info y){
info z;
z.mn1=min(x.mn1, y.mn1);
if(z.mn1==x.mn1) z.mn2=min(x.mn2, y.mn1);
else z.mn2=min(x.mn1, y.mn2);
return z;
}
info tr[N<<1];
void clr(int p, int l, int r){
tr[p].mn1=tr[p].mn2=inf;
if(l==r) return ;
int mid=(l+r)>>1;
clr(p<<1, l, mid); clr(p<<1|1, mid+1, r);
}
void mdf(int p, int l, int r, int x, info v){
if(l==r) {
tr[p]=v; return ;
}
int mid=(l+r)>>1;
if(x<=mid) mdf(p<<1, l, mid, x, v);
else mdf(p<<1|1, mid+1, r, x, v);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
int fnd(int p, int l, int r, int L, int R){
//L<=l, mn1<=R
if(tr[p].mn1>R) return inf-1;
if(l==r) return l;
int mid=(l+r)>>1, ret=inf-1;
if(L<=mid) ret=fnd(p<<1, l, mid, L, R);
if(ret==inf-1) ret=fnd(p<<1|1, mid+1, r, L, R);
return ret;
}
int fnd2(int p, int l, int r, int L, int R){
if(tr[p].mn1>R) return inf;
if(L<=l&&tr[p].mn2>R) return inf-1;
if(l==r) return l;
int mid=(l+r)>>1, ret=inf;
if(L<=mid) ret=fnd2(p<<1, l, mid, L, R);
if(ret==inf) ret=fnd2(p<<1|1, mid+1, r, L, R);
else if(ret==inf-1) ret=fnd(p<<1|1, mid+1, r, L, R);
return ret;
}
}T2[20];
struct sgt3{
struct info{
int mx1, mx2;
info(int _mx1=0, int _mx2=0){
mx1=_mx1; mx2=_mx2;
}
};
inline friend info operator +(info x, info y){
info z;
z.mx1=max(x.mx1, y.mx1);
if(z.mx1==x.mx1) z.mx2=max(x.mx2, y.mx1);
else z.mx2=max(x.mx1, y.mx2);
return z;
}
info tr[N<<1];
void clr(int p, int l, int r){
tr[p].mx1=tr[p].mx2=0;
if(l==r) return ;
int mid=(l+r)>>1;
clr(p<<1, l, mid); clr(p<<1|1, mid+1, r);
}
void mdf(int p, int l, int r, int x, info v){
if(l==r) {
tr[p]=v; return ;
}
int mid=(l+r)>>1;
if(x<=mid) mdf(p<<1, l, mid, x, v);
else mdf(p<<1|1, mid+1, r, x, v);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
int fnd(int p, int l, int r, int L, int R){
//L<=mx1, r<=R
if(tr[p].mx1<L) return 0;
if(l==r) return l;
int mid=(l+r)>>1, ret=0;
if(R>mid) ret=fnd(p<<1|1, mid+1, r, L, R);
if(ret==0) ret=fnd(p<<1, l, mid, L, R);
return ret;
}
int fnd2(int p, int l, int r, int L, int R){
if(tr[p].mx1<L) return -1;
if(r<=R&&tr[p].mx2<L) return 0;
if(l==r) return l;
int mid=(l+r)>>1, ret=-1;
if(R>mid) ret=fnd2(p<<1|1, mid+1, r, L, R);
if(ret==-1) ret=fnd2(p<<1, l, mid, L, R);
else if(ret==0) ret=fnd(p<<1, l, mid, L, R);
return ret;
}
}T3[20];
multiset<int> s[N<<1];
unordered_map<int, int> id[20];
int idx;
void clr(int d, int p, int l, int r){
T1[d].clr(p, l, r);
if(l==r) return ;
int mid=(l+r)>>1;
T2[d].clr(p<<1, l, mid);
T3[d].clr(p<<1|1, mid+1, r);
clr(d+1, p<<1, l, mid); clr(d+1, p<<1|1, mid+1, r);
}
void add(int d, int p, int l, int r, int L, int R){
T1[d].mdf(p, l, r, L, R, 1);
if(l==r) return ;
int mid=(l+r)>>1;
if(L>mid) {
add(d+1, p<<1|1, mid+1, r, L, R);
return ;
}
if(R<=mid) {
add(d+1, p<<1, l, mid, L, R);
return ;
}
if(id[d].find(L)==id[d].end()) id[d][L]=++idx;
if(id[d].find(R)==id[d].end()) id[d][R]=++idx;
auto &mpl=s[id[d][L]], &mpr=s[id[d][R]];
mpl.insert(R); mpr.insert(L);
{
sgt2::info f;
auto it=mpl.begin();
if(it!=mpl.end()) f.mn1=*it, ++it;
if(it!=mpl.end()) f.mn2=*it;
T2[d].mdf(p<<1, l, mid, L, f);
// cout<<"T2:"<<L<<' '<<f.mn1<<' '<<f.mn2<<endl;
}
{
sgt3::info f;
auto it=mpr.end();
if(it!=mpr.begin()) --it, f.mx1=*it;
if(it!=mpr.begin()) --it, f.mx2=*it;
T3[d].mdf(p<<1|1, mid+1, r, R, f);
// cout<<"T3:"<<R<<' '<<f.mx1<<' '<<f.mx2<<endl;
}
}
void del(int d, int p, int l, int r, int L, int R){
T1[d].mdf(p, l, r, L, R, -1);
if(l==r) return ;
int mid=(l+r)>>1;
if(L>mid) {
del(d+1, p<<1|1, mid+1, r, L, R);
return ;
}
if(R<=mid) {
del(d+1, p<<1, l, mid, L, R);
return ;
}
auto &mpl=s[id[d][L]], &mpr=s[id[d][R]];
mpl.erase(mpl.find(R)); mpr.erase(mpr.find(L));
{
sgt2::info f;
auto it=mpl.begin();
if(it!=mpl.end()) f.mn1=*it, ++it;
if(it!=mpl.end()) f.mn2=*it;
T2[d].mdf(p<<1, l, mid, L, f);
}
{
sgt3::info f;
auto it=mpr.end();
if(it!=mpr.begin()) --it, f.mx1=*it;
if(it!=mpr.begin()) --it, f.mx2=*it;
T3[d].mdf(p<<1|1, mid+1, r, R, f);
}
}
int get(int d, int p, int l, int r, int L, int R, sgt2::info llim, sgt3::info rlim){
// cout<<d<<' '<<p<<' '<<l<<' '<<r<<' '<<L<<' '<<R<<endl;
if(L<=l&&r<=R){
int l1=llim.mn1, l2=llim.mn2;
int r1=rlim.mx1, r2=rlim.mx2;
// cout<<l<<' '<<r<<' ';
// cout<<r2<<' '<<r1<<' '<<l1<<' '<<l2<<endl;
if(r1<l1){
// cout<<"Type A:"<<endl;
int ret=0;
if(max(l, r2+1)<=min(r, r1)){
sgt1::info f=T1[d].get(p, l, r, max(l, r2+1), min(r, r1));
if(f.mn==0) ret+=f.c0;
}
if(max(l, l1)<=min(r, l2-1)){
sgt1::info f=T1[d].get(p, l, r, max(l, l1), min(r, l2-1));
if(f.mn==0) ret+=f.c0;
}
if(max(l, r1+1)<=min(r, l1-1)){
sgt1::info f=T1[d].get(p, l, r, max(l, r1+1), min(r, l1-1));
if(f.mn==0) ret+=f.c1;
else if(f.mn==1) ret+=f.c0;
}
// cout<<ret<<endl;
return ret;
}
if(r2<l2){
int ret=0;
if(max(l, r2+1)<=min(r, l1-1)){
sgt1::info f=T1[d].get(p, l, r, max(l, r2+1), min(r, l1-1));
if(f.mn==0) ret+=f.c0;
}
if(max(l, r1+1)<=min(r, l2-1)){
sgt1::info f=T1[d].get(p, l, r, max(l, r1+1), min(r, l2-1));
if(f.mn==0) ret+=f.c0;
}
// cout<<ret<<endl;
return ret;
}
// cout<<0<<endl;
return 0;
}
int mid=(l+r)>>1;
if(L>mid) return get(d+1, p<<1|1, mid+1, r, L, R, llim, rlim);
if(R<=mid) return get(d+1, p<<1, l, mid, L, R, llim, rlim);
// cout<<"here"<<endl;
// cout<<T2[d].fnd(p<<1, l, mid, L, R)<<' '<<T2[d].fnd2(p<<1, l, mid, L, R)<<endl;
int lv=get(d+1, p<<1, l, mid, L, R, llim+(sgt2::info){T2[d].fnd(p<<1, l, mid, L, R), T2[d].fnd2(p<<1, l, mid, L, R)}, rlim);
// cout<<"there1"<<endl;
// cout<<T3[d].fnd(p<<1|1, mid+1, r, L, R)<<' '<<T3[d].fnd2(p<<1|1, mid+1, r, L, R)<<endl;
int rv=get(d+1, p<<1|1, mid+1, r, L, R, llim, rlim+(sgt3::info){T3[d].fnd(p<<1|1, mid+1, r, L, R), T3[d].fnd2(p<<1|1, mid+1, r, L, R)});
// cout<<"there2"<<endl;
return lv+rv;
}
void solve(){
read(n); read(m);
for(int i=0; i<20; ++i) id[i].clear();
for(int i=1; i<=idx; ++i) s[i].clear();
idx=0;
clr(0, 1, 1, n);
for(int i=1, op, l, r; i<=m; ++i){
read(op); read(l); read(r);
// cout<<op<<' '<<l<<' '<<r<<endl;
if(op==1) {
add(0, 1, 1, n, l, r);
// cout<<"add:"<<l<<' '<<r<<endl;
}
else if(op==2) {
del(0, 1, 1, n, l, r);
}
else {
printf("%d\n", get(0, 1, 1, n, l, r, sgt2::info(inf, inf), sgt3::info(0, 0)));
}
}
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
solve();
}
return 0;
}
2026“钉耙编程”中国大学生算法设计暑期联赛(6) 1004 Three Colors
神题。
用到了支配集或者叫备选集技巧,后面还有一道题用到了。
第一步注意就很惊人,设询问区间为 \([L, R]\),只需要考虑左端点距离 \(L\) 小于等于 2,右端点距离 \(R\) 小于等于 2 的子区间即可。如果存在一个合法子区间串 \(S\) 满足左端点距离 \(L\) 大于 2,且不可以继续拓展,并假设 \(a<b<c\)(出现次数)。既然不能被拓展,只能在 \(S\) 左右各放一个 \(A\) 和 \(B\)。进一步,两侧第二个不能放 C,最后可以推得,形式只能是 \(AAB+S+BAA\) 的结构,但全选可以拓展,与假设矛盾。
剩下的就是分类讨论+预处理了,最终发现可以 \(O(1)\) 求出某个端点的答案,细节较多。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e6+5;
int T, n, m;
struct solver{
int a[N];
int sum[N][3];
int jmp[N];
int jmp3[N];
inline bool chk(int x, int y, int z){
return x!=y&&y!=z&&x!=z;
}
pii srk(int l, int r){
if(r<l) return mapa(l, r);
int s[3]={0, 0, 0};
s[0]=sum[r][0]-sum[l-1][0];
s[1]=sum[r][1]-sum[l-1][1];
s[2]=sum[r][2]-sum[l-1][2];
if(chk(s[0], s[1], s[2])) return mapa(l, r);
if(s[0]==s[1]&&s[1]==s[2]){
int nr=jmp3[r];
if(nr<l){
return mapa(l, l-1);
}
if(a[nr]==a[nr-1]&&a[nr]==a[nr-2]) return srk(l, nr-3);
return mapa(l, nr-3);
}
if(s[0]==s[2]&&s[1]>s[0]){
int op=1, op2=0;
int cnt=0, nr=r;
if(a[r]==op) cnt=r-jmp[r], nr=jmp[r];
if(s[op]-cnt>s[op2]){
return mapa(l, nr-1);
}
else{
return srk(l, r-(s[op]-s[op2]));
}
}
if(s[0]==s[1]&&s[2]>s[0]){
int op=2, op2=0;
int cnt=0, nr=r;
if(a[r]==op) cnt=r-jmp[r], nr=jmp[r];
if(s[op]-cnt>s[op2]){
return mapa(l, nr-1);
}
else{
return srk(l, r-(s[op]-s[op2]));
}
}
if(s[2]==s[1]&&s[0]>s[1]){
int op=0, op2=1;
int cnt=0, nr=r;
if(a[r]==op) cnt=r-jmp[r], nr=jmp[r];
if(s[op]-cnt>s[op2]){
return mapa(l, nr-1);
}
else{
return srk(l, r-(s[op]-s[op2]));
}
}
int op=0;
if(s[0]==s[2]) op=1;
if(s[0]==s[1]) op=2;
if(a[r]==op) return mapa(l, jmp[r]-1);
if(s[(op+1)%3]-1!=s[op]) return mapa(l, r-1);
return srk(l, r-2);
}
void init(){
for(int i=1; i<=n; ++i){
for(int j=0; j<3; ++j) sum[i][j]=sum[i-1][j];
++sum[i][a[i]];
}
for(int i=1; i<=n; ++i){
if(i!=1&&a[i]==a[i-1]) jmp[i]=jmp[i-1];
else jmp[i]=i-1;
if(i>=3&&chk(a[i], a[i-1], a[i-2])) jmp3[i]=jmp3[i-3];
else jmp3[i]=i;
}
}
}S1, S2;
char s[N];
void solve(){
read(n);
scanf("%s", s+1);
for(int i=1; i<=n; ++i){
if(s[i]=='A') S1.a[i]=S2.a[n+1-i]=0;
else if(s[i]=='B') S1.a[i]=S2.a[n+1-i]=1;
else S1.a[i]=S2.a[n+1-i]=2;
}
S1.init(); S2.init();
int lst=0;
read(m);
for(int i=1, ql, qr; i<=m; ++i){
read(ql); read(qr);
ql=((ql^lst)+n-1)%n+1;
qr=((qr^lst)+n-1)%n+1;
if(ql>qr) swap(ql, qr);
pii ans=mapa(0, -1);
for(int l=ql, r=qr; l<=ql+2&&l<=r; ++l){
pii ret=S1.srk(l, r);
if(ret.se-ret.fi>ans.se-ans.fi){
ans=ret;
}
}
for(int r=qr, l=ql; r>=qr-2&&l<=r; --r){
pii ret=S2.srk(n+1-r, n+1-l);
swap(ret.fi, ret.se);
ret.fi=n+1-ret.fi; ret.se=n+1-ret.se;
if(ret.se-ret.fi>ans.se-ans.fi){
ans=ret;
}
}
if(ans.se<ans.fi) printf("%d %d\n", 0, 0), lst=0;
else printf("%d %d\n", ans.fi, ans.se), lst=ans.se-ans.fi+1;
}
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
// read(T);
T=1;
while(T--){
solve();
}
return 0;
}
2026“钉耙编程”中国大学生算法设计暑期联赛(6) 1011 Push Box
无论具体操作是什么,每个箱子都有过程中最大位置 \(mx_i\) 和最小位置 \(mn_i\),且每个箱子都至少要移动 \(2(mx_i-mn_i)\) 的距离。
我们可以先计算出 \(mx_i\) 的最小值,可以递推出来:\(mx_i=\max\{a_i, b_i, mx_{i-1}+1\}\)。\(mn_i\) 的最大值同理。
可以证明最小值可以取到 \(2\sum_{i=1}^{n} (mx_i-mn_i)\)。显然 \(mx_i\) 递增而 \(mn_i\) 递减,先按 \(a_i\) 递减顺序让所有箱子到达 \(mx_i\),再按 \(a_i\) 递增顺序让所有箱子到达 \(mn_i\),最后回到起始位置,每个箱子都没有走重复两次以上的路。
那么问题转化为,如何对于一段前缀,求出每个箱子的 \(mx_i\) 和 \(mn_i\)。两者本质相同,我们只考虑 \(mx_i\)。
虽然看上去不好动态维护,但实际上真的可以动态维护。
我们直接在主席树上维护。插入一个箱子时,可以利用区间 \(\max\) 求出这个箱子的 \(mx_i\),之后需要用 \(mx_i+j-i\) 更新 \(mx_j(j>i)\)。回顾 \(mx_i\) 递增的性质,我们只需要二分找到第一个不会被更新的位置,把前面的区间内做一个等差数列覆盖即可。离散化后单次复杂度是 \(O(\log n)\)。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=2e5+5;
const ll inf=1e10;
int T, n;
int a[N], b[N], c[N], al[N], ar[N];
ll C2(int x){
return 1ll*x*(x+1)/2ll;
}
struct sgt{
ll mx[N<<2], sum[N<<2], tag[N<<2]; int sz[N<<2];
void clr(int p, int l, int r){
tag[p]=-inf; sz[p]=0; mx[p]=-inf;
if(l==r){
sum[p]=-inf;
return ;
}
int mid=(l+r)>>1;
clr(p<<1, l, mid); clr(p<<1|1, mid+1, r);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void down(int p, int l, int r){
if(tag[p]!=-inf){
int mid=(l+r)>>1;
sum[p<<1]=tag[p]*sz[p<<1]+C2(sz[p<<1])-inf*(mid-l+1-sz[p<<1]);
tag[p<<1]=tag[p];
if(sz[p<<1]){
mx[p<<1]=tag[p]+sz[p<<1];
}
sum[p<<1|1]=(tag[p]+sz[p<<1])*sz[p<<1|1]+C2(sz[p<<1|1])-inf*(r-mid-sz[p<<1|1]);
tag[p<<1|1]=tag[p]+sz[p<<1];
if(sz[p<<1|1]){
mx[p<<1|1]=tag[p]+sz[p<<1]+sz[p<<1|1];
}
tag[p]=-inf;
}
}
int mdf(int p, int l, int r, int L, int R, ll v){
if(L<=l&&r<=R){
sum[p]=v*sz[p]+C2(sz[p])-inf*(r-l+1-sz[p]);
tag[p]=v;
if(sz[p]) mx[p]=v+sz[p];
return sz[p];
}
int mid=(l+r)>>1;
down(p, l, r);
int c=0;
if(L<=mid) c=mdf(p<<1, l, mid, L, R, v), v+=c;
if(R>mid) c+=mdf(p<<1|1, mid+1, r, L, R, v);
sum[p]=sum[p<<1]+sum[p<<1|1];
mx[p]=max(mx[p<<1], mx[p<<1|1]);
return c;
}
ll gmx(int p, int l, int r, int L, int R){
if(L<=l&&r<=R){
return mx[p];
}
int mid=(l+r)>>1;
down(p, l, r);
ll ret=-inf;
if(L<=mid) ret=max(ret, gmx(p<<1, l, mid, L, R));
if(R>mid) ret=max(ret, gmx(p<<1|1, mid+1, r, L, R));
return ret;
}
void upd(int p, int l, int r, int x, ll v){
if(l==r){
sz[p]++;
mx[p]=sum[p]=v;
return ;
}
int mid=(l+r)>>1;
down(p, l, r);
if(x<=mid) upd(p<<1, l, mid, x, v);
else upd(p<<1|1, mid+1, r, x, v);
sum[p]=sum[p<<1]+sum[p<<1|1];
mx[p]=max(mx[p<<1], mx[p<<1|1]);
sz[p]=sz[p<<1]+sz[p<<1|1];
}
pii fnd(int p, int l, int r, int L, int R, ll v){
if(L<=l&&r<=R){
if(mx[p]<v+sz[p]) return mapa(n+1, sz[p]);
if(l==r){
return mapa(l, 0);
}
int mid=(l+r)>>1;
down(p, l, r);
if(mx[p<<1]<v+sz[p<<1]) return fnd(p<<1|1, mid+1, r, L, R, v+sz[p<<1]);
return fnd(p<<1, l, mid, L, R, v);
}
int mid=(l+r)>>1;
down(p, l, r);
int ret=n+1;
int cnt=0;
if(L<=mid){
pii info=fnd(p<<1, l, mid, L, R, v);
if(info.fi!=n+1) ret=info.fi;
else v+=info.se;
cnt+=info.se;
}
if(R>mid&&ret==n+1){
pii info=fnd(p<<1|1, mid+1, r, L, R, v);
ret=info.fi;
cnt+=info.se;
}
return mapa(ret, cnt);
}
}TL, TR;
void solve(){
read(n);
for(int i=1; i<=n; ++i){
read(a[i]); c[i]=a[i];
}
sort(c+1, c+n+1);
for(int i=1; i<=n; ++i){
read(b[i]);
al[i]=min(a[i], b[i]); ar[i]=max(a[i], b[i]);
a[i]=lower_bound(c+1, c+n+1, a[i])-c;
}
TL.clr(1, 0, n); TR.clr(1, 0, n);
for(int i=1; i<=n; ++i){
{
//R
ll lst=TR.gmx(1, 0, n, 0, a[i]-1);
ll cur=max(lst+1, (ll)ar[i]);
TR.upd(1, 0, n, a[i], cur);
if(a[i]!=n) {
int id=TR.fnd(1, 0, n, a[i]+1, n, cur).fi;
if(id-1>=a[i]+1)
TR.mdf(1, 0, n, a[i]+1, id-1, cur);
}
}
{
//L
ll lst=TL.gmx(1, 0, n, 0, n+1-a[i]-1);
ll cur=max(lst+1, (ll)-al[i]);
TL.upd(1, 0, n, n+1-a[i], cur);
if(a[i]!=1) {
// cout<<TL.gmx(1, 0, n, 2, 2)<<' ';
int id=TL.fnd(1, 0, n, n+1-a[i]+1, n, cur).fi;
// cout<<i<<' '<<n+1-a[i]+1<<' '<<id-1<<' '<<cur<<endl;
if(id-1>=n+1-a[i]+1)
TL.mdf(1, 0, n, n+1-a[i]+1, id-1, cur);
}
// cout<<TL.sum[1]+inf*(n+1-i)<<endl;
}
printf("%lld%c", 2ll*(TR.sum[1]+TL.sum[1]+inf*(n+1-i)*2), " \n"[i==n]);
}
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
solve();
}
return 0;
}
2026“钉耙编程”中国大学生算法设计暑期联赛(5) 1006 三串共鸣
警示做法应当往简单去想。
c 串的贡献非常好算,难点在于 b 串,等价于求一些等长的区间的并的大小。场上考虑这个题和 CCPCF 的青鱼安魂曲那个题很像,放在 SAM 上做启发式分裂,但显然不好写。
但实际上这个题只需要考虑前缀而不是子串,可以很简单地做。
具体的,我们利用 Z 函数,求出 b 的每个位置和 a 的 lcp。从 0 开始,当要求是 lcp 为 0 时,b 的每个位置都满足要求。之后增大 lcp 的要求,一些 b 逐渐被淘汰,只需要用一个链表,维护剩下的 b 中相邻的间隔的距离即可。
两个做法复杂度都是单 \(\log\),但后者显然简单地多。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=1e6+5;
int T, n1, n2, n3;
char a[N], b[N], c[N];
int p[N], z[N];
void Z(char *s, int n){
for(int i=1; i<=n; ++i) z[i]=0;
z[1]=n;
for(int i=2, l=0, r=0; i<=n; ++i){
if(i<=r) z[i]=min(z[i-l+1], r-i+1);
while(i+z[i]<=n&&s[i+z[i]]==s[z[i]+1]) ++z[i];
if(i+z[i]-1>r) l=i, r=i+z[i]-1;
}
}
void exkmp(char *s, int n, char *t, int m){
for(int i=1; i<=n; ++i) p[i]=0;
for(int i=1, l=0, r=0; i<=n; ++i){
if(i<=r) p[i]=min(z[i-l+1], r-i+1);
while(i+p[i]<=n&&s[i+p[i]]==t[p[i]+1]) ++p[i];
if(i+p[i]-1>r) l=i, r=i+p[i]-1;
}
}
int cnt[N];
vector<int> bin[N];
int pre[N], nxt[N];
int tr1[N], tr2[N];
void add(int x, int v1, int v2){
for(; x<=n1; x+=(x&-x)) tr1[x]+=v1, tr2[x]+=v2;
}
int get1(int x){
int ret=0;
for(; x; x-=(x&-x)) ret+=tr1[x];
return ret;
}
int get2(int x){
int ret=0;
for(; x; x-=(x&-x)) ret+=tr2[x];
return ret;
}
void solve(){
read(n1); read(n2); read(n3);
scanf("%s", a+1);
scanf("%s", b+1);
scanf("%s", c+1);
Z(a, n1);
exkmp(c, n3, a, n1);
for(int i=1; i<=n1; ++i) cnt[i]=0;
for(int i=1; i<=n3; ++i) cnt[p[i]]++;
for(int i=n1-1; i>=1; --i) cnt[i]+=cnt[i+1];
exkmp(b, n2, a, n1);
for(int i=0; i<=n1; ++i) bin[i].clear(), tr1[i]=tr2[i]=0;
for(int i=1; i<=n2; ++i) bin[p[i]].ep(i);
for(int i=1; i<=n2; ++i) pre[i]=i-1, nxt[i]=i+1, add(1, 1, 1);
nxt[0]=1; pre[n2+1]=n2;
ll ans=0;
int tot=n2;
for(int w=1; w<=n1; ++w){
for(auto t:bin[w-1]){
int l=pre[t], r=nxt[t];
if(l!=0) add(t-l, -1, -(t-l));
add(r-t, -1, -(r-t));
nxt[l]=r; pre[r]=l;
if(l!=0) add(r-l, 1, r-l);
--tot;
}
int len=get2(w)+w*(tot-get1(w));
ans+=1ll*len*cnt[w];
}
printf("%lld\n", ans);
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
solve();
}
return 0;
}
2026“钉耙编程”中国大学生算法设计暑期联赛(5) 1011 树型广播
以这个题为契机浅谈一下点分树。
回忆点分治,本质上是把 \(C_{n}^{2}\) 条路径分成 \(n\) 类,即点分治的每个分治中心,而每个点都会成为一个分治中心。
具体而言,我们是递归地选择连通块重心,让重心维护(连通块内所有经过重心的路径)的信息,然后删掉重心递归各个连通块。
而点分树则是利用递归过程天然形成的树形结构,进行动态邻域信息修改查询的数据结构。我们在点分树上从操作点开始不断跳父亲,即可操作所有以操作点为起点的路径信息。选择重心保证了点分树的树高为 \(O(\log n)\),因此可以对每个点开独立的数据结构维护路径信息,操作时直接在这 \(O(\log n)\) 个父亲的数据结构上做相应操作即可。
实际维护时,需要减掉同源信息,如下图:

所以我们一般需要信息可减,但也有例外。比如这道题需要维护时间戳最大的标记,为了排除同源信息的影响,我们可以给信息打上颜色,维护颜色不同的最大和次大。
使用的数据结构由于要以距离为下标,主席树、fhq treap 会比较方便,我目前写过的点分树题里也只见到过这两个。
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double dou;
typedef pair<int,int> pii;
#define fi first
#define se second
#define mapa make_pair
typedef long double ld;
typedef unsigned long long ull;
#define ep emplace_back
template <typename T>inline void read(T &x){
x=0;char c=getchar();bool f=0;
for(;c<'0'||c>'9';c=getchar()) f|=(c=='-');
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+(c^48);
x=(f?-x:x);
}
const int N=3e5+5, mod=998244353;
int T, n, m;
vector<int> e[N];
int qv[N], qk[N], qa[N][11];
int dfn[N], timer, dep[N], seq[N];
void dfs(int x, int fa){
dep[x]=dep[fa]+1;
dfn[x]=++timer; seq[timer]=x;
for(auto y:e[x]){
if(y==fa) continue;
dfs(y, x);
seq[++timer]=x;
}
}
int lg[N], st[20][N];
void pre(){
for(int i=2; i<=timer; ++i) lg[i]=lg[i>>1]+1;
for(int i=1; i<=timer; ++i) st[0][i]=seq[i];
for(int i=1; i<=lg[timer]; ++i){
for(int j=1; j+(1<<i)-1<=timer; ++j){
st[i][j]=dep[st[i-1][j]]<dep[st[i-1][j+(1<<(i-1))]]?st[i-1][j]:st[i-1][j+(1<<(i-1))];
}
}
}
inline int lca(int x, int y){
x=dfn[x]; y=dfn[y];
if(x>y) swap(x, y);
int t=lg[y-x+1];
return dep[st[t][x]]<dep[st[t][y-(1<<t)+1]]?st[t][x]:st[t][y-(1<<t)+1];
}
inline int dis(int x, int y){
return dep[x]+dep[y]-2*dep[lca(x, y)];
}
vector<pii> fr[N];
bool ban[N];
int sz[N], mxs[N], mxd[N];
void dfs1(int x, int fa, int allsz, int &rt){
sz[x]=1; mxs[x]=0;
for(auto y:e[x]) {
if(y==fa||ban[y]) continue;
dfs1(y, x, allsz, rt);
sz[x]+=sz[y];
mxs[x]=max(mxs[x], sz[y]);
}
if(rt==0||max(mxs[x], allsz-sz[x])<max(mxs[rt], allsz-sz[rt])) rt=x;
}
void dfs2(int x, int fa, int id, int rt, int d){
sz[x]=1; fr[x].ep(rt, id);
mxd[rt]=max(mxd[rt], d);
for(auto y:e[x]) {
if(y==fa||ban[y]) continue;
dfs2(y, x, id, rt, d+1);
sz[x]+=sz[y];
}
}
void work(int x, int allsz){
int rt=x;
dfs1(x, 0, allsz, rt);
fr[rt].ep(rt, rt);
mxd[rt]=0;
for(auto y:e[rt]){
if(!ban[y]) dfs2(y, rt, y, rt, 1);
}
ban[rt]=1;
for(auto y:e[rt]){
if(!ban[y]) work(y, sz[y]);
}
}
struct info{
int mx1, c1, mx2, c2;
info(int _mx1=0, int _c1=0, int _mx2=0, int _c2=0){
mx1=_mx1; c1=_c1; mx2=_mx2; c2=_c2;
}
};
inline info operator +(info x, info y){
info ret;
vector<pii> v;
v.ep(mapa(x.mx1, x.c1));
v.ep(mapa(x.mx2, x.c2));
v.ep(mapa(y.mx1, y.c1));
v.ep(mapa(y.mx2, y.c2));
sort(v.begin(), v.end());
ret.mx1=v.back().fi; ret.c1=v.back().se;
for(int i=2; i>=0; --i){
if(v[i].se!=ret.c1){
ret.mx2=v[i].fi; ret.c2=v[i].se;
return ret;
}
}
return ret;
}
info tr[N*60]; int ls[N*60], rs[N*60];
int rt[N], idx;
void add(int &p, int l, int r, int L, int R, int v, int c){
if(!p){
p=++idx;
tr[p]=tr[0]; ls[p]=rs[p]=0;
}
if(L<=l&&r<=R){
tr[p]=tr[p]+(info){v, c, 0, 0};
return ;
}
int mid=(l+r)>>1;
if(L<=mid) add(ls[p], l, mid, L, R, v, c);
if(R>mid) add(rs[p], mid+1, r, L, R, v, c);
}
void get(int p, int l, int r, int x, info &ret){
if(!p) {
return ;
}
ret=ret+tr[p];
if(l==r) {
return ;
}
int mid=(l+r)>>1;
if(x<=mid) get(ls[p], l, mid, x, ret);
else get(rs[p], mid+1, r, x, ret);
}
void solve(){
read(n); read(m);
timer=0;
idx=0;
for(int i=1; i<=n; ++i) {
e[i].clear(); fr[i].clear();
ban[i]=0; rt[i]=0;
}
for(int i=1, x, y; i<n; ++i){
read(x); read(y);
e[x].push_back(y); e[y].push_back(x);
}
dfs(1, 0);
pre();
work(1, n);
for(int i=1, op, l, r, x; i<=m; ++i){
read(op);
if(op==1){
read(qv[i]); read(l); read(r); read(qk[i]);
for(int j=0; j<=qk[i]; ++j) read(qa[i][j]);
for(auto [fa, c]:fr[qv[i]]){
int d=dis(fa, qv[i]);
if(d>r) continue;
if(l-d>mxd[fa]) continue;
// cout<<fa<<' '<<max(0, l-d)<<' '<<r-d<<' '<<i<<' '<<c<<endl;
if(fa==qv[i]) c=0;
add(rt[fa], 0, mxd[fa], max(0, l-d), min(mxd[fa], r-d), i, c);
}
}
else{
read(x);
int id=0;
for(auto [fa, c]:fr[x]){
int d=dis(fa, x);
info cur;
get(rt[fa], 0, mxd[fa], d, cur);
// cout<<fa<<' '<<d<<endl;
// cout<<cur.mx1<<' '<<cur.c1<<' '<<cur.mx2<<' '<<cur.c2<<endl;
if(cur.c1!=c) id=max(id, cur.mx1);
else id=max(id, cur.mx2);
}
// cout<<"ans:";
if(id==0){
printf("0\n");
continue;
}
ll ans=0, pw=1;
int d=dis(qv[id], x);
for(int j=0; j<=qk[id]; ++j, pw=pw*d%mod){
ans=(ans+pw*qa[id][j])%mod;
}
printf("%lld\n", ans);
}
}
}
int main(){
// freopen("D:\\nya\\acm\\A\\test.in","r",stdin);
// freopen("D:\\nya\\acm\\A\\test.out","w",stdout);
read(T);
while(T--){
solve();
}
return 0;
}

浙公网安备 33010602011771号