vp + 补题 + 随机做题 记录九

这期杭电质量整体偏低(虽然我也提供了部分题目)。挑一些有意思的记录一下。

2026“钉耙编程”中国大学生算法设计暑期联赛(10) 1001 串串

link

\(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 小白的烦恼

link

序列带颜色基本等价于升维,考虑颜色数量分治。

对于出现次数超过 \(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

link

第一点要求路径是最短路径,启发我们先找个数据结构存储所有最短路径,这就是最短路径树。

接下来考虑题目的闭邻域。如果我们把图中的点按照到起点的距离分层,根据最短路径树的性质,在第 \(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 数轴上的染色翻转怎么还有恢复与加密

link

可持久化操作只能在线处理,同时也要求不能用均摊复杂度的数据结构。先分析要完成什么。

既然不能均摊,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 今晚吃什么

link

等价于选 \(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 今晚吃老歌

link

如果询问区间是 \([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

link

神题。

用到了支配集或者叫备选集技巧,后面还有一道题用到了。

第一步注意就很惊人,设询问区间为 \([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

link

无论具体操作是什么,每个箱子都有过程中最大位置 \(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 三串共鸣

link

警示做法应当往简单去想。

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 树型广播

link

以这个题为契机浅谈一下点分树。

回忆点分治,本质上是把 \(C_{n}^{2}\) 条路径分成 \(n\) 类,即点分治的每个分治中心,而每个点都会成为一个分治中心。

具体而言,我们是递归地选择连通块重心,让重心维护(连通块内所有经过重心的路径)的信息,然后删掉重心递归各个连通块。

而点分树则是利用递归过程天然形成的树形结构,进行动态邻域信息修改查询的数据结构。我们在点分树上从操作点开始不断跳父亲,即可操作所有以操作点为起点的路径信息。选择重心保证了点分树的树高为 \(O(\log n)\),因此可以对每个点开独立的数据结构维护路径信息,操作时直接在这 \(O(\log n)\) 个父亲的数据结构上做相应操作即可。

实际维护时,需要减掉同源信息,如下图:

image

所以我们一般需要信息可减,但也有例外。比如这道题需要维护时间戳最大的标记,为了排除同源信息的影响,我们可以给信息打上颜色,维护颜色不同的最大和次大。

使用的数据结构由于要以距离为下标,主席树、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;
}
posted @ 2026-08-25 20:48  Displace  阅读(8)  评论(0)    收藏  举报