国庆集训模拟赛记录

2025.9.26

A 序列

OI 赛制收益者,挂了 70 分。

先考虑构造一个相邻逆序对最大的序列。

最佳的序列一定是从最大数扫到最小数,每个出现次数不为 \(0\) 的数依次放入数组末尾,并将出现次数减一,扫完最小数后重新扫最大数,一直重复即可。

构造出来后,若 \([1,j]\) 的相邻逆序对个数为 \(m\),将 \([j+1,m]\) 的部分从小到大排序,消去贡献。

具体实现用 vector,可以参考代码,复杂度为 \(O(Tn\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
const int N=1e5+10;
int n,m,ans[N];
vector<int>t[N];
map<int,int>H;
bool cmp(int a,int b){
	return a>b;
}
void work(){
	scanf("%d %d",&n,&m);
	int mx=0;
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		H[x]++,mx=max(mx,H[x]);
		t[H[x]].push_back(x);
	}
	int idx=0;
	for(int i=1;i<=mx;i++){
		sort(t[i].begin(),t[i].end(),cmp);
		for(auto v:t[i]){
			ans[++idx]=v;
		}
	}
	int now=0;
	bool flag=0;
	for(int i=1;i<=n;i++){
		if(now==m){
			sort(ans+i,ans+n+1);
			flag=1;break;
		}
		if(ans[i]>ans[i+1])now++;
	}
	if(!flag){
		printf("-1\n");
	}else{
		for(int i=1;i<=n;i++)printf("%d ",ans[i]);
		printf("\n");
	}
	H.clear();
	for(int i=1;i<=mx;i++)t[i].clear();
	for(int i=1;i<=n;i++)ans[i]=0;
	return;
}
int main(){
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)work();
	return 0;
}

B 平衡数列

容易发现若所有的 \(A_i>1\),则一个平衡数列不超过 \(20\)。因为 \(2^{20}>2\times 20\),所以一定不行。

这启发我们平衡数列个数很少,记录每个位置 \(i\) 前上一个 \(A_i>1\) 的位置。枚举区间右端点 \(r\),然后往左跳 \(nxt_l\),跳的次数一定不超过 \(20\),时间复杂度为 \(O(n\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N=2e5+10;
const ll V=5e11;
int n,a[N],pre[N],ans;
ll s[N];
int main(){
    freopen("bal.in","r",stdin);
    freopen("bal.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",a+i);
        s[i]=s[i-1]+a[i];
    }
    int x=0;
    for(int i=1;i<=n;i++){
        pre[i]=x;
        if(a[i]>1)x=i;
    }
    for(int i=1,l;i<=n;i++){
        ll prod=1,sum=0;
        int lst=i;
        for(int j=i;j&&prod<=V;j=pre[j]){
            prod*=a[j],sum+=s[lst]-s[j-1],l=pre[j];
            if(prod-sum>=0&&prod-sum<=j-1-l)ans++;
            lst=j-1;
        }
    }
    printf("%d\n",ans);
    return 0;
}

C 间谍部署

\(f_{i,j}\) 表示 \(i\) 子树内,距离 \(i\) 最近的选择的点距离 \(i\)\(j\),做一个树上背包状物即可,因为可以子树选空,所以要注意继承的情况。

时间复杂度为 \(O(n)\)

点击查看代码
#include <iostream> 
#include <cstdio>
#include <vector>
using namespace std;
typedef long long ll;
const int N=2e5+10;
const int mod=998244353;
int n,mk[N];
vector<int>G[N];
ll f[N][4],ans[4],sum;
void add(int x,int y){
    G[x].push_back(y); 
}
void dfs(int x,int fa){
    for(auto y:G[x]){
        if(y==fa)continue;
        dfs(y,x);
        for(int i=1;i<=3;i++)ans[i]=f[x][i];
        for(int i=0,s;i<=3;i++){
            s=min(3,i+1);
            ans[s]=(ans[s]+f[y][i])%mod;
        }
        for(int i=1,s;i<=3;i++){
            for(int j=0;j<=3;j++){
                if(i+j<2)continue;
                s=min(3,min(i,j+1));
                ans[s]+=f[x][i]*f[y][j]%mod;
                ans[s]%=mod;
            }
        }
        for(int i=1;i<=3;i++)f[x][i]=ans[i];
    }
    if(mk[x])f[x][0]=f[x][3]+1;
    return;
}
int main(){
    freopen("spy.in","r",stdin);
    freopen("spy.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",mk+i);
    for(int i=2,f;i<=n;i++){
        scanf("%d",&f);
        add(f,i),add(i,f);
    }
    dfs(1,0);
    for(int i=0;i<=3;i++)sum=(sum+f[1][i])%mod;
    printf("%d\n",sum);
    return 0;
}

D 小A与字符串

不难发现,字符串的循环节一定是最小循环节的倍数,所以可以求出区间最小循环节。

\(p\)\([l,r]\) 的循环节,需要满足 \([l+p,r]=[l,r-p]\),具体和 KMP 相关。可以预处理字符串 Hash 值,方便 \(O(1)\) 比较。

从小到大试倍数,直到试出第一个循环节为答案,复杂度 \(O(n\sqrt{n})\),慢。

反过来!若 \(p\) 是循环节,则一定是最小循环节的倍数,考虑逐个剔除其中质因子。

最后预处理每个数因数个数,质因子的东西,时间复杂度为 \(O(n\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
typedef long long ull;
const int N=5e5+10;
int n,m,ys[N],mk[N];
char s[N];
vector<int>pr[N];
struct Hash{
    ull p,pw[N],hs[N];
    void prework(ull Ciallo,char *s,int n){
        p=Ciallo,pw[0]=1,hs[0]=0;
        for(int i=1;i<=n;i++){
            pw[i]=pw[i-1]*p;
            hs[i]=hs[i-1]*p+s[i]-'a';
        }
        return;
    }
    ull qry(int l,int r){
        return hs[r]-hs[l-1]*pw[r-l+1];
    }
}T1,T2;
bool check(int l1,int r1,int l2,int r2){
	if(l1>r1)return 1;
    if(T1.qry(l1,r1)!=T1.qry(l2,r2))return 0;
    if(T2.qry(l1,r1)!=T2.qry(l2,r2))return 0;
    return 1;
}
void init(){
	 T1.prework(131,s,n);
     T2.prework(13331,s,n);
     for(int i=1;i<=n;i++)for(int j=i;j<=n;j+=i)ys[j]++;
     for(int i=2;i<=n;i++){
     	 if(mk[i])continue;
     	 for(int j=i;j<=n;j+=i){
     	 	 pr[j].push_back(i);
			 mk[j]=1;	 
		 }
	 }
     return;
}
int main(){
    freopen("str.in","r",stdin);
    freopen("str.out","w",stdout);
    scanf("%d %d",&n,&m);
    scanf("%s",s+1);
    init();
    for(int i=1,l,r;i<=m;i++){
        scanf("%d %d",&l,&r);
        int x=1,y=r-l+1;
        for(auto v:pr[r-l+1]){
        	while(1){
        		if(y%v!=0)break;
        		if(!check(l,r-y/v,l+y/v,r))break;
        		x*=v,y/=v;
			}
		}
        printf("%d\n",ys[x]);
    }
    return 0;
}

2025.10.1

有 n 种物品

注意到 \(a_i=b_i\) 对分差没有影响,直接扔掉。两边都想最大化自己得分,得分总和一定,等价与 \(A\) 想最大化分差,\(B\) 想最小化分差。

注意到 \(A\) 先手,则分类讨论:

\(a_i>b_i\):此时 \(A\) 已经选了 \(i\),就算不选最后也是小 \(B\) 选,因此小 \(B\) 会寻找此时的次优。

\(a_i<b_i\):若此时 \(A\) 已经选了 \(i\),则 \(B\) 跟选 \(i\) 一定更优。

前者按照分差从大到小奇偶选,后者对分差贡献一定是 \(a_i-b_i\)。时间复杂度为 \(O(n\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int n;
ll ans;
vector<ll>a;
bool cmp(ll a,ll b){
    return a>b;
}
int main(){
    freopen("nit.in","r",stdin);
    freopen("nit.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        ll x,y;
        scanf("%lld %lld",&x,&y);
        if(x==y)continue;
        if(x<y)ans+=x-y;
        else a.push_back(x-y); 
    }
    sort(a.begin(),a.end(),cmp);
    for(int i=0;i<a.size();i++){
        if(i&1)ans-=a[i];
        else ans+=a[i]; 
    }
    printf("%lld\n",ans);
    return 0;
} 

火柴排队

用排名替换具体数值,得到两个排列,然后让通过交换操作让两个排列相同,一定是最优,感性理解即可。

这个问题是可以转化为排序的最小交换此时,其实就是逆序对,树状数组维护即可,复杂度 \(O(n\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=1e5+10,mod=1e8-3;
int n,a[N],b[N],c[N],book1[N],book2[N],t[N],tot1,tot2,ans,tree[N];
int lowbit(int x){return x&(-x);}
void add(int x,int t){
    for(;x<=n;x+=lowbit(x))tree[x]+=t;
    return;
}
int sum(int x){
    int cnt=0;
    for(;x>0;x-=lowbit(x))cnt=(cnt+tree[x])%mod;
    return cnt;
}
int main(){
	freopen("MatchNOIP2013.in","r",stdin);
    freopen("MatchNOIP2013.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",a+i),book1[++tot1]=a[i];
    for(int i=1;i<=n;i++)scanf("%d",b+i),book2[++tot2]=b[i];
    sort(book1+1,book1+1+tot1);
    tot1=unique(book1+1,book1+1+tot1)-(book1+1);
    sort(book2+1,book2+1+tot2);
    tot2=unique(book2+1,book2+1+tot2)-(book2+1);
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(book1+1,book1+1+tot1,a[i])-book1;
        b[i]=lower_bound(book2+1,book2+1+tot2,b[i])-book2;
        t[a[i]]=i;
    }
    for(int i=1;i<=n;i++)c[i]=t[b[i]];
    for(int i=n;i>0;i--){
        ans=(ans+sum(c[i]-1)%mod)%mod;
        add(c[i],1);
    }
    cout<<ans<<endl;
    return 0;
}

国旗计划

断环成链后,问题转化为从每个区间出发走 \(L\) 的最小人数。

贪心不难发现,若选择某个区间 \(i\),则下一个选择的区间 \(j\) 唯一确定。

转移过程一定,但次数较多,考虑倍增,然后就没了,找后继区间可以利用题目给定的单调性双指针,时间复杂度为 \(O(n\log n)\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,f[26][N],cnt,pos[N];
struct seg{
    int l,r,id;
    void init(int a,int b,int c){
        l=a,r=b,id=c;
    }
}a[N];
bool cmp(seg a,seg b){
    return a.l<b.l;
}
signed main(){
	freopen("flagplan.in","r",stdin);
    freopen("flagplan.out","w",stdout); 
    scanf("%lld %lld",&n,&m);
    for(int i=1,c,d;i<=n;i++){
        scanf("%lld %lld",&c,&d);
        if(c>d)d+=m;
        a[++cnt].init(c,d,i);
        a[++cnt].init(c+m,d+m,i);
    }
    sort(a+1,a+1+cnt,cmp);
    for(int i=1,j=1;i<=cnt;i++){
        while(j<cnt&&a[j+1].l<=a[i].r)j++;
        if(a[i].l<=m)pos[a[i].id]=i;
        f[0][i]=j;
    }
    for(int i=1;i<=25;i++){
        for(int j=1;j<=cnt;j++){
            f[i][j]=f[i-1][f[i-1][j]];
        }
    }
    for(int i=1;i<=n;i++){
        int ans=1,x=pos[i],R=a[x].l+m;
        for(int i=25;i>=0;i--){
            int nxt=f[i][x];
            if(a[nxt].r<R){
                x=nxt;
                ans+=(1<<i);
            }
        }
        ans++;
        printf("%lld ",ans);
    }
    return 0;
}

信使

sol

2025.10.2

ICPC 欢乐赛,神人队友。

A

暴力枚举或打表,轻松做到 \(O(T)\)\(O(Tk)\)

B

注意到 \(2n\) 是一个周期,处理一下后累加即可。

C

不会

D

每个人按照当前最优策略就是全局最优策略

E

考虑走到 \(x\)

若能走到 \(x+1\):走

若不能走到 \(x+1\) 且有其他节点:必须连边

其他:回溯

不难体会其正确性。

F

费用流,小于等于 \(a_i\) 的贡献为 \(0\),大于 \(a_i\) 的贡献为 \(1\)。最大流强制选择,最小费用优化答案。

G

二分 \(k\),看序列每一段不超过 \(k\) 最大划分几段,离散化树状数组优化 dp。

H

模拟即可,推荐 string.substr

I

不会

J

神秘题目队友做了 3h。

碰撞后是否交换方向不重要,先用周期然后模拟。

K

同余分类后从小到大输出。

L

\(100\) BFS 即可,用前缀和优化。

M

注意到时间只会延迟,所以一个人开始行动后一定会到终点。

按照 \(s_i\) 排序后一个一个处理。

2025.10.4

神仙题,狗屎部分分。

A 毛一琛

不难发现比较难搞。

转化题意,当前你有一个数 \(x\),可以选择一些位置 \(i\) 进行 \(x\gets x,x\gets x+a_i,x\gets x-a_i\),最后使得 \(x=0\) 的位置选择方案数。

直接搜索是 \(O(3^n)\) 的,注意操作可能不一样,但选择操作的位置的集合是一样的,要注意去重。

考虑折半搜索,分成前一半和后一遍,前一半搜出所有可能集合,后一半去和前一半对应,注意去重。时间复杂度为 \(O(6^{\frac{n}{2}})\)

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
const int N=25,M=(1<<20);
int n,k,ans,a[N],vis[M];
map<int,vector<int>>H;
void dfs1(int x,int now,int sum){
	if(x==k+1){
		H[sum].push_back(now);
	}else{
		dfs1(x+1,now,sum);
		dfs1(x+1,now|(1<<(x-1)),sum+a[x]);
		dfs1(x+1,now|(1<<(x-1)),sum-a[x]);
	}
	return;
}
void dfs2(int x,int now,int sum){
	if(x==n+1){
		for(auto v:H[-sum]){
			vis[(now<<(n/2))|v]=1;
		}
	}else{
		dfs2(x+1,now,sum);
		dfs2(x+1,now|(1<<(x-k-1)),sum+a[x]);
		dfs2(x+1,now|(1<<(x-k-1)),sum-a[x]);
	}
	return;
}
int main(){
	freopen("subsets.in","r",stdin);
	freopen("subsets.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	k=n/2;
	dfs1(1,0,0);
	dfs2(k+1,0,0);
	for(int i=1;i<(1<<n);i++){
		if(vis[i])ans++;
	}
	printf("%d\n",ans);
	return 0;
} 

B 魔法卡片

神题!!

不难发现结论一:若 \(r-l+1\ge 20\),则答案一定为 \(\sum_{i=1}^m i^2\)

如何证明:题目的一个特殊条件是每张卡牌正反面正好凑齐 \(1\sim m\),假设我们第一张牌选择数字多的一面,则此时我们没获得的数字个数 \(x\le \frac{n}{2}\)。考虑第二张牌,显然上一步遗留的 \(x\) 张牌中要么出现在第二张牌的正面,要么在反面。我们优先考虑选择那个面会让 \(x\) 更小,显然,选择这个面后缺少的牌数 \(x'\le \dfrac{x}{2}\)。就这样,每选一次,\(x\) 缩小一倍,不超过 \(\log n\) 次就一定为 \(0\),此时可以取到 \(1\sim m\) 的所有数字。

处理掉 \(r-l+1\ge 20\) 的部分,顺利成章的考虑对于每个 \(l\),预处理出 \([l,r](r-l+1<20)\) 的答案,这部分直接爆搜的时间复杂度为 \(O(nm^2\log m)\),比较垃圾,考虑优化。我们长期以来一直被局限到正向思维,即做出决策后会拥有那些数字,考虑倒着维护我们做出决策后那些数字还未拥有,若当前未拥有的数字集合为 \(X\),要对第 \(i\) 张牌进行决策,如果第 \(i\) 张牌选正面,则未拥有的数字集合为 \(S\),若选负面,集合为 \(T\),有 \(S\cup T=X,S\cap T=\varnothing\)。这个性质非常重要。

考虑最开始决策第 \(1\) 张牌之前,集合 \(X=\{1,2,\dots,m\}\)。决策后会分成 \(S,T\),第二张牌决策后\(S\) 决策后分成 \(SS,ST\) 两个集合,\(T\) 分成 \(TS,TT\) 两个集合。不难发现 \(SS,ST,TS,TT\) 的并为 \(X\),两两交集为空。换句话说,进过 \(k\) 次决策后,不同状态所代表的集合其实是最开始的 \(X\) 分裂而成的若干小集合,其之间两两不重合。而我们要计算这个集合的权值,只需要扫一遍,也就是说,经过 \(k\) 次决策后,把计算所有状态的权值的复杂度为 \(O(m)\)

说到这里,这个题已经做完了,枚举左端点 \(O(n)\),枚举区间长度 \(O(\log m)\),搜索计算决策 \(O(m)\),总的复杂度为 \(O(nm\log m)\)。代码实现比较重要:如果用 vector 暴力维护集合,频繁的 push_back 和 clear 操作会炸。所以用静态数组,做一个类似归并排序自上而下划分的过程。加了一些剪枝,拿到了 COGS 最优解。

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N=1e6+10;
int n,m,q,p[N],g[N],lim;
long long sum,val[N][21];
vector<int>H[N]; 
const int MAXSIZE=(1<<20);
char buf[1<<20],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXSIZE,stdin),p1==p2)?EOF:*p1++)
inline void read(int &a){
	int x=0,f=1;char ch=gc();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+ch-48,ch=gc();
	return a=x*f,void();
}
void dfs(int st,int dep,int x,int l,int r){
	if(dep>=lim)return;
	if(l>r){
		long long res=sum;lim=min(lim,dep);
		for(int k=dep;k<=20;k++)val[st][k]=sum;
		return;
	}
	long long res=sum;
	for(int k=l;k<=r;k++)res-=1ll*p[k]*p[k];
	val[st][dep]=max(val[st][dep],res);
	if(x==n+1)return;
	int i=l,j=r;
	for(int k=l;k<=r;k++){
		if(H[x][p[k]])g[i++]=p[k];
		else g[j--]=p[k];
	}
	for(int k=l;k<=i-1;k++)p[k]=g[k];
	for(int k=j-1;k<=r;k++)p[k]=g[k];
	dfs(st,dep+1,x+1,l,i-1);
	dfs(st,dep+1,x+1,j+1,r);
	return;
}
int main(){
	freopen("magic.in","r",stdin);
	freopen("magic.out","w",stdout);
	read(n),read(m),read(q);
	for(int i=1;i<=n;i++){
		int x;read(x);
		H[i].resize(m+2);
		for(int j=1,p;j<=x;j++){
			read(p);
			H[i][p]=1;
		}
	}
	for(int i=1;i<=m;i++)sum+=1ll*i*i;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)p[j]=j;
		lim=20;dfs(i,0,i,1,m);
	}
	int l,r;
	while(q--){
		read(l),read(r);
		if(r-l+1>=20){
			printf("%lld\n",sum);
		}else{
			printf("%lld\n",val[l][r-l+1]);
		}
	}
	return 0;
}

C 排列

神仙题!!!

考虑枚举一个排练 \(b\),判断 \(a\) 能否到达 \(b\)

容易发现序列应该变的更加有序,关键在于如何体现有序。考虑并非排列,而是 \(01\) 序列的情况,这种情况需要 \(b\) 中的每一个 \(1\) 都在 \(a\)\(1\) 之前。

那对于排列的情况,只需要对于任意 \(k\in [1,n]\),将序列按照大于小于 \(k\) 将每个位置分成 \(01\),都满足上述限制即可。

\(dp_{i,S}\) 表示满足大于等于 \(i\) 的位置为 \(1\),排列对应的 \(01\) 串为 \(S\) 的方案数,容易发现若 \(S\) 是原串可达,则去掉 \(S\) 中任意一个元素依旧合法,直接枚举一个 \(1\) 即可,注意到 \(i\)\(S\)\(1\) 的个数,所以不必要存在。

做状压 dp 即可,时间复杂度 \(O(n2^n+n^2)\)

点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
const int M=(1<<20);
const int mod=1e9+7;
int n,cnt[M],dp[M],a[21],S,s1,s2;
bool check(int p){
	s1=0,s2=0;
	for(int k=0;k<n;k++){
		if((p>>k)&1)s1++;
		if((S>>k)&1)s2++;
		if(s2>s1)return 1;
	}
	return 0;
}
int main(){
	freopen("changgao_perm.in","r",stdin);
	freopen("changgao_perm.out","w",stdout);
	scanf("%d",&n);
	for(int i=0;i<n;i++)scanf("%d",a+i);
	for(int i=0;i<(1<<n);i++)cnt[i]=cnt[i>>1]+(i&1);
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<n;j++)if(a[j]==i)S|=(1<<j);
		for(int j=0;j<(1<<n);j++){
			if(cnt[j]!=i||check(j))continue;
			for(int k=0;k<n;k++){
				if((j>>k)&1){
					dp[j]+=dp[j^(1<<k)];
					dp[j]%=mod;
				}
			}
		}
	}
	printf("%d\n",dp[(1<<n)-1]);
	return 0;
} 

D 追忆

神人题!!!

rerererererererererererererererererererecall!!!

先做 DAG 可达性统计,得出每个点的可达点集。

维护一个后缀/前缀(我写的是后缀)集合 \(A_i\),表示集合 \(\{x|a_x\in [i,n]\}\),则满足 \(a_x\in [l,r]\) 的点集即为 \(A_{l} \backslash A_{r+1}\)。注意到你对 \(a\) 有修改操作,考虑分块,设块长为 \(T=\sqrt{n}\)\(A_i=\{x|a_x\in [iT,n]\}\),修改时只用修改 \(\sqrt{n}\) 次,单次查询维护 \(a\) 的逆排列,将散块部分暴力求即可。

将可达性点集合上述点集进行与与运算,就得到了所有满足限制条件的点的点集 \(C\),考虑这个点集中最大的 \(b\) 值。维护 \(B_i=\{x|b_x\in [iT,n]\}\),先找到最大 \(b\) 所在的块,即一个最靠后的编号 \(p\),满足 \(B_p\cap C\ne \varnothing\),在这个块里找答案即可。

二分带 \(\log\),所以不写,考虑一些很牛的东西。我们维护集合均使用 bitset,本质是一堆 unsigned long long 变量,分别编号这些变量为 \(w_{0\sim \frac{n}{w}}\)。容易发现,\(B_0\cap C\ne \varnothing\),单独考虑 \(B_0,C\) 的每一个 \(w_i\),若当 \(x\le q\) 时,\(B_{x_{w_i}}\cap C_{w_i}\ne \varnothing\),则说明答案一定大于 \(q\),且各个位的贡献独立。所以枚举 \(C\) 的每一个 \(w\),并维护一个指针 \(p\),若满足 \(B_{{p+1}_{w_i}}\cap C_{w_i}=\varnothing\),则 \(p\gets p+1\),最终走完一遍就是 \(O(\frac{n}{w}+\sqrt{n})\)

总时间复杂度为 \(O(\frac{nm}{w}+q(\sqrt{n}+\dfrac{w}{n}))\)。比较狗屎。record

posted @ 2025-10-01 18:06  zuoqingyuan111  阅读(12)  评论(0)    收藏  举报