联考整理 - Jan. 2026 ~ Feb. 2026
Jan. 19
A. 歌
考虑枚举最小的初段长度 \(p\) 使得存在合法划分,并对其容斥。
假设当前有若干种长度使得该序列存在合法划分,那么形态如下图所示。
图中数字相同的段有相同的颜色,首段和末端可能有残缺。
注意到大部分贡献都是重复的,设 \(f_{0/1,i}\) 表示当前选择段数奇偶性,最长段是 \(i\),可以根据上图进行 1D/1D 的转移。
时间复杂度 \(O(k^3)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("song.in");
ofstream fout("song.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
ll n,k;
struct FastMod { // 请将该结构体直接复制,粘贴到你的代码中
unsigned long long b, m;
FastMod() = default;
FastMod(unsigned long long m) : b(((unsigned __int128)1 << 64) / m), m(m) {}
unsigned long long operator()(unsigned long long a) {
unsigned long long q = (unsigned __int128)a * b >> 64;
unsigned long long r = a - q * m;
return r >= m ? r - m : r;
}
} fastmod;
int mod;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=fastmod(1ll*x*y);}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return fastmod(1ll*x*y);}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
const int K=1e3+9;
int fac[K],ifac[K];
inline void Init(int lim){
fastmod=FastMod(mod);
fac[0]=1;
for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
ifac[lim]=Inv(fac[lim]);
for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
if(m<0||m>n) return 0;
else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}
inline int iC(int n,int m){
if(m<0||m>n) return 0;
else return Mul(ifac[n],Mul(fac[m],fac[n-m]));
}
int f[2][K],pf[K][2],ipf[K][2];
inline void Solve(){
cin>>n>>k>>mod;
Init(k);
if(n<=k){
int ans=Mul(C(k,n),fac[n]);
for(int i=1;i<k;i++){
AddAs(ans,Mul(Mul(Mul(C(k,i),fac[i]),i),Mul(C(k-1,n-1-i),fac[n-1-i])));
}
cout<<ans<<endl;
}else{
int ans=0;
for(int i=0;i<=k;i++){
pf[i][0]=ipf[i][0]=1;
for(int j=1;j<=k;j++){
pf[i][j]=Mul(pf[i][j-1],fac[i]);
ipf[i][j]=Mul(ipf[i][j-1],ifac[i]);
}
}
for(int i=1;i<=k;i++){
int t=(n-i)/k%(mod-1),r=(n-i)%k;
for(int j=0;j<=k;j++){
for(int c:{0,1}){
pf[j][c]=QPow(fac[j],t+c);
ipf[j][c]=Inv(pf[j][c]);
}
}
auto F=[&](int i,int j){
if(i<r&&r<=j) return Mul(Mul(C(k-i,k-j),pf[j-i][0]),Mul(C(j-i,r-i),fac[r-i]));
else return Mul(C(k-i,k-j),pf[j-i][j<r]);
};
auto G=[&](int p){
return Mul(pf[k-p][0],Mul(C(k-p,max(r-p,0)),fac[max(r-p,0)]));
};
auto iG=[&](int p){
return Mul(ipf[k-p][0],Mul(iC(k-p,max(r-p,0)),ifac[max(r-p,0)]));
};
f[1][0]=G(0);
for(int j=0;i+j<=k;j++){
for(int p=j+1;i+p<=k;p++){
for(int c:{0,1}){
AddAs(f[!c][p],Mul(f[c][j],Mul(F(j,p),Mul(iG(j),G(p)))));
}
}
}
for(int j=0;j<=k;j++){
AddAs(ans,Mul(Mul(C(k-j,i),fac[i]),Sub(f[1][j],f[0][j])));
f[0][j]=f[1][j]=0;
}
}
cout<<ans<<endl;
}
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
B. 火力大喵
第一问答案就是最大值减最小值。
第二问就相当于查询边框上存在最大值和最小值的矩形数,考虑将其容斥成边框上没有最大值或最小值以及两个都不能有,这样就拆成三个问 \(0/1\) 矩阵中边框上都是 \(1\) 的矩形数量。
考虑分治,每次对子矩形的长边折半,枚举中线上矩形与中线的两个交点,那么对半边问的就是交点特定的 C 形数量。
不失一般性地先只考虑左半边。对于触点 \(i,j\),假设 \(i\) 向左连续 \(1\) 段比 \(j\) 短,那么贡献就是在 \(i\) 向左连续 \(1\) 段内的向下连续 \(1\) 段长度超过 \(j-i+1\) 的点的个数。枚举 \(i\) 扫描 \(j\) 可以做到单层 \(O(nm)\),总的就是 \(O(nm\log nm)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("dameow.in");
ofstream fout("dameow.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=2e3+9;
int a[N][N],b1[N][N],b2[N][N],b3[N][N],n,m;
int U[N][N],D[N][N],L[N][N],R[N][N],cnt[N],s[N][N],t[N][N];
inline void Conquer(int a[N][N],int l1,int r1,int l2,int r2,ll &ans){
if(l1==r1&&l2==r2) return ans+=a[l1][l2],void();
for(int i=l1;i<=r1;i++){
for(int j=l2;j<=r2;j++){
if(a[i][j]){
U[i][j]=(i-1>=l1?U[i-1][j]:0)+1;
L[i][j]=(j-1>=l2?L[i][j-1]:0)+1;
}else U[i][j]=L[i][j]=0;
}
}
for(int i=r1;i>=l1;i--){
for(int j=r2;j>=l2;j--){
if(a[i][j]){
D[i][j]=(i+1<=r1?D[i+1][j]:0)+1;
R[i][j]=(j+1<=r2?R[i][j+1]:0)+1;
}else D[i][j]=R[i][j]=0;
}
}
if(r1-l1>r2-l2){
int mid=l1+r1>>1;
for(int i=l2;i<=r2;i++){
s[i][i]=U[mid][i];
for(int j=mid-U[mid][i]+1;j<=mid;j++) cnt[L[j][i]]++;
for(int j=i-l2+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l2;j<i;j++) if(U[mid][j]>U[mid][i]) s[j][i]+=cnt[i-j+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
for(int j=mid-U[mid][i]+1;j<=mid;j++) cnt[R[j][i]]++;
for(int j=r2-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r2;j>i;j--) if(U[mid][j]>=U[mid][i]) s[i][j]+=cnt[j-i+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
}
for(int i=l2;i<=r2;i++){
t[i][i]=D[mid+1][i];
for(int j=mid+1;j<=mid+D[mid+1][i];j++) cnt[L[j][i]]++;
for(int j=i-l2+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l2;j<i;j++) if(D[mid+1][j]>D[mid+1][i]) t[j][i]+=cnt[i-j+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
for(int j=mid+1;j<=mid+D[mid+1][i];j++) cnt[R[j][i]]++;
for(int j=r2-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r2;j>i;j--) if(D[mid+1][j]>=D[mid+1][i]) t[i][j]+=cnt[j-i+1];
memset(cnt,0,(r2-l2+2)*sizeof int());
}
for(int i=l2;i<=r2;i++){
for(int j=i;j<=r2;j++){
ans+=s[i][j]*t[i][j];
s[i][j]=t[i][j]=0;
}
}
Conquer(a,l1,mid,l2,r2,ans);
Conquer(a,mid+1,r1,l2,r2,ans);
}else{
int mid=l2+r2>>1;
for(int i=l1;i<=r1;i++){
s[i][i]=L[i][mid];
for(int j=mid-L[i][mid]+1;j<=mid;j++) cnt[U[i][j]]++;
for(int j=i-l1+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l1;j<i;j++) if(L[j][mid]>L[i][mid]) s[j][i]+=cnt[i-j+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
for(int j=mid-L[i][mid]+1;j<=mid;j++) cnt[D[i][j]]++;
for(int j=r1-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r1;j>i;j--) if(L[j][mid]>=L[i][mid]) s[i][j]+=cnt[j-i+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
}
for(int i=l1;i<=r1;i++){
t[i][i]=R[i][mid+1];
for(int j=mid+1;j<=mid+R[i][mid+1];j++) cnt[U[i][j]]++;
for(int j=i-l1+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=l1;j<i;j++) if(R[j][mid+1]>R[i][mid+1]) t[j][i]+=cnt[i-j+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
for(int j=mid+1;j<=mid+R[i][mid+1];j++) cnt[D[i][j]]++;
for(int j=r1-i+1;j>=0;j--) cnt[j]+=cnt[j+1];
for(int j=r1;j>i;j--) if(R[j][mid+1]>=R[i][mid+1]) t[i][j]+=cnt[j-i+1];
memset(cnt,0,(r1-l1+2)*sizeof int());
}
for(int i=l1;i<=r1;i++){
for(int j=i;j<=r1;j++){
ans+=s[i][j]*t[i][j];
s[i][j]=t[i][j]=0;
}
}
Conquer(a,l1,r1,l2,mid,ans);
Conquer(a,l1,r1,mid+1,r2,ans);
}
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cin>>a[i][j];
}
int mx=INT_MIN,mn=INT_MAX;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
mx=max(mx,a[i][j]);
mn=min(mn,a[i][j]);
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b1[i][j]=(a[i][j]!=mx);
b2[i][j]=(a[i][j]!=mn);
b3[i][j]=(a[i][j]!=mx&&a[i][j]!=mn);
}
}
ll ans=0,s1=0,s2=0,s3=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) ans+=1ll*i*j;
}
Conquer(b1,1,n,1,m,s1);
Conquer(b2,1,n,1,m,s2);
Conquer(b3,1,n,1,m,s3);
ans-=s1;
ans-=s2;
ans+=s3;
cout<<mx-mn<<' '<<ans<<endl;
return 0;
}
C. 松鼠威廉梦游仙境
首先答案可以被表示成 \(\displaystyle \sum_{ax+by=k} \binom{n-1+x}{x}\binom{m-i+y}{y}(c_0+c_1x+c_2y+c_3xy)\) 的形式。
考虑模拟 Lucas 定理,从高位到低位 DP 取值。设 \(f_{i,j}\) 表示当前考虑到 \(p\) 进制第 \(i\) 位,\(ax+by\) 从第 \(i-1\) 位进位 \(j\) 次。则枚举 \(x\),根据 \(k_i\) 的取值即可反向解出 \(y\)。\(c_0+c_1x+c_2y+c_3xy\) 的贡献可以放到最低位考虑,毕竟答案对 \(p\) 取模。
时间复杂度 \(O(p(a+b)\log_p\max(k,n,m))\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("squirrel.in");
ofstream fout("squirrel.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
using ull=unsigned long long;
const int N=1e6+9;
const int lgK=64;
const int A=1e2+9;
struct FastMod{
ull b,m;
FastMod()=default;
FastMod(ull m):b(((unsigned __int128)1<<64)/m),m(m){}
ull operator()(ull a){
ull q=(unsigned __int128)a*b>>64;
ull r=a-q*m;
return r>=m?r-m:r;
}
}fastmod;
int mod;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
// inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline void MulAs(int &x,int y){x=fastmod(1ll*x*y);}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
// inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int Mul(int x,int y){return fastmod(1ll*x*y);}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
int fac[N<<1],ifac[N<<1],inv[N];
inline int fInv(int x){return Mul(ifac[x],fac[x-1]);}
inline void Init(int lim){
fastmod=FastMod(mod);
fac[0]=1;
for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
ifac[lim]=Inv(fac[lim]);
for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
for(int i=1;i<=lim;i++) inv[i]=fInv(i);
}
inline int C(int n,int m){return n<mod?Mul(fac[n],Mul(ifac[m],ifac[n-m])):0;}
int f[lgK][A],tar[lgK],ni[lgK],mi[lgK],tx[N],ty[N];
ll n,m,k,a,b,c0,c1,c2,c3;
inline void Solve(){
cin>>n>>m>>k>>a>>b>>mod>>c0>>c1>>c2>>c3;
n--,m--;
ll tmp=max(k,max(n,m));
int lgk=0;
while(tmp) tmp/=mod,lgk++;
for(int i=0;i<lgk;i++){
tar[i]=k%mod,k/=mod;
ni[i]=n%mod,n/=mod;
mi[i]=m%mod,m/=mod;
}
int ans=0;
Init(mod-1);
f[lgk][0]=1;
for(int i=lgk-1;~i;i--){
for(int j=0;j<=a+b;j++){
if(!i&&j) break ;
ll s=j;
int d=Sub(tar[i],j);
for(int x=0;x<mod;x++,SubAs(d,a),s+=a){
int y=Mul(d,inv[b]),_j=(s+b*y)/mod;
if(!f[i+1][_j]) continue ;
int w=Mul(f[i+1][_j],Mul(C(ni[i]+x,x),C(mi[i]+y,y)));
AddAs(f[i][j],w);
if(!i) AddAs(ans,Mul(w,Add(Add(c0,Mul(x,c1)),Add(Mul(y,c2),Mul(Mul(x,y),c3)))));
}
}
}
for(int i=0;i<=lgk;i++) for(int j=0;j<=a+b;j++) f[i][j]=0;
cout<<ans<<endl;
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
Jan. 20
A. 胜兵必骄
令 \(a_i=1\) 表示有箱子,否则没箱子。
考虑找到一个有箱子且领域中有节点没箱子的节点作为根,先让所有有箱子的节点全部把自己子树里到碰到箱子为止的所有没箱子的节点和其父亲的边跑一边,即每个没箱子的节点都归其到根链上的第一个有箱子的节点管。
那么现在没有跑的就是非根的有箱子节点到其父亲的边,如果该节点父亲有箱子,那么等他父亲去领域的时候快速跑掉,否则一开始就跑掉。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("wars.in");
ofstream fout("wars.out");
#define cin fin
#define cout fout
#define endl '\n'
const int N=1e6+9;
vector<int> e[N];
list<array<int,2>> s[N];
list<array<int,2>>::iterator in[N],out[N];
int a[N],n,root;
inline void DFS1(int x,int fa){
for(int y:e[x]){
if(y==fa) continue ;
DFS1(y,x);
if(!a[y]){
s[y].insert(s[y].begin(),{x,y});
s[y].insert(s[y].end(),{x,y});
s[x].splice(s[x].end(),s[y]);
}
}
}
inline void DFS2(int x,int fa,int ffa){
if(x!=root&&a[x]){
auto it=fa==ffa?out[ffa]:in[ffa];
in[x]=s[root].insert(it,{x,fa});
s[x].insert(s[x].begin(),{x,fa});
s[root].splice(it,s[x]);
out[x]=next(in[x]);
ffa=x;
}
for(int y:e[x]){
if(y==fa) continue ;
DFS2(y,x,ffa);
}
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int x=1;x<=n;x++){
if(!a[x]) continue ;
for(int y:e[x]){
if(a[y]) continue ;
root=x;
break ;
}
if(root) break ;
}
DFS1(root,0);
in[root]=s[root].begin();
out[root]=next(in[root]);
DFS2(root,0,root);
for(auto p:s[root]) cout<<p[0]<<' '<<p[1]<<endl;
return 0;
}
B. 数据恢复
先将 \(a\) 做如下变换,令 \(p_1,\ldots,p_k\) 表示其前缀最大值位置,那么将 \([p_i,p_{i+1})\) 的位置向左循环移位一次,即把 \(p_i\) 放到 \(p_{i+1}-1\) 的位置,那么前缀最大值的限制就不对最大值之后的数产生约束。
考察任意两个位置 \(i,j(i<j)\) 之间的关系。若 \(a_i<a_j\) 则无事发生,若 \(a_i>a_j\) 则必然存在约束使得其无法交换使字典序更小,因此特别关注所有逆序对的限制,设 \(x_i\) 表示位置 \(i\) 是否清晰可见:
- 若 \(c_i=c_j=0\),那么 \(x_i,x_j\) 不同时为 \(0\)。
- 若 \(c_i=1,c_j=0\),存在 \(x_i,x_j\) 不同时为 \(0\) 的限制当且仅当 \(\displaystyle \max_{k=1}^{i-1} a_k<a_j<a_i\)。
- 若 \(c_j=1\),则 \(a_i<a_j\)。
因此考虑按值域从后向前填数,设 \(f_{i,0/1,j}\) 表示当前考虑到 \(i\),最大值是否清晰可见,目前非清晰可见的 \(c_p=0\) 最小的 \(p\) 是 \(j\),转移是简单的。
线段树优化 DP 即可做到 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("recovery.in");
ofstream fout("recovery.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int N=1e6+9;
const int mod=998244353;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
struct Vec{
int a[2];
Vec(){memset(a,0,sizeof a);}
Vec(int x,int y){a[0]=x,a[1]=y;}
inline int& operator [](int p){return a[p];}
friend inline Vec operator +(Vec A,Vec B){return Vec(Add(A[0],B[0]),Add(A[1],B[1]));}
};
struct Node{
int l,r;
Vec dat;
int tag;
}tr[N<<2];
int pw[N];
inline void PushUp(int x){tr[x].dat=tr[x<<1].dat+tr[x<<1|1].dat;}
inline void Push(int x,int k){
tr[x].tag+=k;
tr[x].dat[0]=tr[x].dat[1]=Mul(pw[k-1],Add(tr[x].dat[0],tr[x].dat[1]));
}
inline void PushDown(int x){
if(tr[x].tag){
Push(x<<1,tr[x].tag);
Push(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r,tr[x].dat=Vec(0,0),tr[x].tag=0;
if(tr[x].l==tr[x].r) return ;
int mid=tr[x].l+tr[x].r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
inline void Set(int x,int pos,Vec k){
if(tr[x].l==tr[x].r) return tr[x].dat=k,void();
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) Set(x<<1,pos,k);
else Set(x<<1|1,pos,k);
PushUp(x);
}
inline Vec Query(int x,int l,int r){
if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(r<=mid) return Query(x<<1,l,r);
else if(l>mid) return Query(x<<1|1,l,r);
else return Query(x<<1,l,r)+Query(x<<1|1,l,r);
}
int a[N],c[N],id[N],pmx[N],n;
inline void Solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int mx=0;
vector<int> p;
for(int i=1;i<=n;i++){
mx=max(mx,a[i]);
if(a[i]==mx) p.push_back(i),c[i]=1;
else c[i]=0;
}
p.push_back(n+1);
for(int i=0;i+1<p.size();i++){
for(int j=p[i];j+1<p[i+1];j++){
swap(a[j],a[j+1]);
swap(c[j],c[j+1]);
}
}
for(int i=1;i<=n;i++){
id[a[i]]=i;
pmx[i]=max(pmx[i-1],a[i]);
}
pw[0]=1;
for(int i=1;i<=n;i++) pw[i]=Mul(2,pw[i-1]);
Build(1,0,n+1);
Set(1,n+1,{1,0});
for(int i=n,cur=0;i>=1;i--){
if(c[id[i]]){
cur=pmx[id[i]-1];
Push(1,1);
}else{
Vec v=Query(1,id[i],id[i]),u=Query(1,id[i],n+1);
if(i<=cur) AddAs(v[0],u[0]);
AddAs(v[1],u[1]);
Set(1,id[i],v);
}
}
cout<<Add(tr[1].dat[0],tr[1].dat[1])<<endl;
}
signed main(){
int o,T;
cin>>o>>T;
while(T--) Solve();
return 0;
}
Jan. 22
A. 冻鱼
首先 \(a_i>0\) 的数一定是一个连续段,不是一般性地,把前缀 \(0\) 段和后缀 \(0\) 段先删去。
由于 \(a_1\neq 0,a_n\neq 0\),因此路径一定经过左右端点,换言之,\(a\) 是基于以下数组的:
1 2 2 2 2 1 1 1 1 2 2 2 2 1
^ ^ ^ ^
1 s t n
(因为 1 2 2 2 3 3 3 3 3 3 2 2 2 1 是可以通过上述数组进一步加工得到的。)
考虑先令 \(a_i\leftarrow a_i-2+[i=1\vee i=n]\),后续再给 \((l,r)\) 加 \(1\)。
可以发现,所有可以在数组上做的操作全部可以归约到让相邻两项同时加 \(1\),所以再处理后的 \(a\) 的合法条件为 \(\displaystyle \forall i,\sum_{j=1}^i(-1)^{i-j}a_i\geq 0\) 且 \(\displaystyle \sum_{i=1}^n (-1)^i a_i=0\)。
设 \(b_i=\sum_{j=1}^i(-1)^{i-j}a_i\)。记 \(p_{0/1}\) 第一个 \(b<0\) 且和 \(r\) 奇偶相同/不同的位置之前根据 \(l,r\) 的奇偶性分讨:
- \(l,r\) 奇偶不同:前缀无影响,后缀影响抵消,相当于给 \(b_{l+1},b_{l+3},\ldots,b_{r-2}\) 加 \(1\),则 \(l\) 必须小于 \(p_0\)。需要保证 \([1,l],[r,n]\) 本身合法且 \(p_1\geq r\)。
- \(l,r\) 奇偶相同:前缀无影响,相当于给 \(b_{l+1},b_{l+3},\ldots,b_{r-1}\) 加 \(1\),给 \(b_r,b_{r+1},...,b_{n}\) 加上 \((-1)^{i-r+1}\),则 \(l\) 必须小于 \(p_1\)。需要保证 \([1,l]\) 本身合法,\([r,n]\) 修改后合法且 \(p_0\geq r\)。
\(l=r\) 直接归到 \(l,r\) 奇偶相同的情况即可。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e6+9;
ifstream fin("coldfish.in");
ofstream fout("coldfish.out");
#define cin fin
#define cout fout
#define endl '\n'
int a[N],f[N],g[N],h[N],n;
inline void Solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
a[n+1]=0;
int L=0,R=n+1;
while(!a[L]&&L<=n) L++;
while(!a[R]&&R>=1) R--;
if(!R) return cout<<0<<endl,void();
else if(L==R) return cout<<(a[L]==1)<<endl,void();
a[L]++,a[R]++;
for(int i=L;i<=R;i++) a[i]-=2,a[i]-=a[i-1];
int p[2]={n+1,n+1};
for(int i=L;i<=R;i++){
if(a[i]<0){
if(a[i]==-1) p[i&1]=min(p[i&1],i);
else return cout<<0<<endl,void();
}
}
ll ans=0,cnt=0;
f[R+1]=g[R+1]=h[R+1]=1;
for(int i=R;i>=L;i--){
f[i]=(a[i]>=0)&f[i+1];
g[i]=(a[i]-(i&1)+(~i&1)>=0)&g[i+1];
h[i]=(a[i]+(i&1)-(~i&1)>=0)&h[i+1];
if(i==R){
f[i]&=a[i]==0;
g[i]&=a[i]-(i&1)+(~i&1)==0;
h[i]&=a[i]+(i&1)-(~i&1)==0;
}
if(~i&1){
if(p[1]>=i){
if(f[i]) ans+=(min(i-1,p[0]-1)-L>>1)+1;
}
if(p[0]>=i){
if(h[i]) ans+=(min(i-2,p[1]-1)-L>>1)+1;
}
if(p[0]>=i&&p[1]>=i&&h[i]) cnt++;
}else{
if(p[0]>=i){
if(f[i]) ans+=(min(i-1,p[1]-1)-L>>1)+1;
}
if(p[1]>=i){
if(g[i]) ans+=(min(i-2,p[0]-1)-L>>1)+1;
}
if(p[0]>=i&&p[1]>=i&&g[i]) cnt++;
}
}
ans<<=1;
cout<<ans+cnt<<endl;
}
signed main(){
int T;
cin>>T;
while(T--) Solve();
return 0;
}
B. 狐狸
先建出 ACAM,\(t\) 在 \(s\) 中的出现次数即为 fail 树上 \(t\) 子树内在遍历 \(s\) 中经过的节点数。
考虑把每个节点挂在 fail 树上第一个成为某个串的结尾节点的祖先下,那么把遍历 \(s\) 中经过的节点在该树上建出虚树,即可通过简单的统计计算出每组本质不同的\(t\) 在 \(s\) 中的出现次数。按答案为下标记录下来,询问时直接二分即可。
时间复杂度 \(O(L(|\Sigma|+\log n))\)。
#include<bits/stdc++.h>
using namespace std;
ifstream fin("fox.in");
ofstream fout("fox.out");
#define cin fin
#define cout fout
#define endl '\n'
using ll=long long;
const int L=5e6+9;
const int Z=26;
const int lgN=20;
const int N=6e5+9;
namespace ACAM{
struct Node{
int son[Z],fail,cnt,vld;
}tr[L];
int cnt;
inline int Allc(){return ++cnt;}
inline int Insert(string &s){
int x=0;
for(char c:s){
c-='a';
if(!tr[x].son[c]) tr[x].son[c]=Allc();
x=tr[x].son[c];
}
tr[x].cnt++;
return x;
}
inline void Build(){
queue<int> q;
for(int i=0;i<Z;i++){
if(!tr[0].son[i]) continue ;
tr[tr[0].son[i]].fail=0;
q.push(tr[0].son[i]);
}
while(q.size()){
int x=q.front();
q.pop();
tr[x].vld=tr[tr[x].fail].vld;
if(tr[x].cnt) tr[x].vld=x;
for(int i=0;i<Z;i++){
if(tr[x].son[i]){
tr[tr[x].son[i]].fail=tr[tr[x].fail].son[i];
q.push(tr[x].son[i]);
}else tr[x].son[i]=tr[tr[x].fail].son[i];
}
}
}
inline void Get(string &s,vector<int> &v){
int x=0;
v.push_back(x);
for(char c:s){
c-='a';
if(!tr[x].son[c]) tr[x].son[c]=Allc();
x=tr[x].son[c];
v.push_back(x);
}
}
}
string s[N];
vector<int> e[N],imp;
int fa[N],dep[N],elr[N<<1],pos[N],n,m,q,Tid,ecnt;
inline void DFS(int x){
elr[++ecnt]=x;
pos[x]=ecnt;
for(int y:e[x]){
fa[y]=x;
dep[y]=dep[x]+ACAM::tr[imp[y]].cnt;
DFS(y);
elr[++ecnt]=x;
}
}
int mn[lgN][N];
inline void InitLCA(){
for(int i=1;i<=ecnt;i++) mn[0][i]=pos[elr[i]];
for(int k=1;k<=__lg(ecnt);k++){
for(int i=1;i+(1<<k)-1<=ecnt;i++){
mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<k-1)]);
}
}
}
inline int LCA(int x,int y){
x=pos[x],y=pos[y];
if(x>y) swap(x,y);
int k=__lg(y-x+1);
return elr[min(mn[k][x],mn[k][y-(1<<k)+1])];
}
ll ans[L];
vector<int> ve[N],node;
inline void GetVir(vector<int> &v){
node=v;
sort(v.begin(),v.end(),[](int x,int y){return pos[x]<pos[y];});
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=0;i+1<v.size();i++) node.push_back(LCA(v[i],v[i+1]));
sort(node.begin(),node.end(),[](int x,int y){return pos[x]<pos[y];});
node.erase(unique(node.begin(),node.end()),node.end());
for(int i=0;i+1<node.size();i++) ve[LCA(node[i],node[i+1])].push_back(node[i+1]);
}
int cnt[N];
inline void Calc(int x,int fa){
for(int y:ve[x]){
Calc(y,x);
cnt[x]+=cnt[y];
}
ans[cnt[x]]+=dep[x]-dep[fa];
}
inline void VirClear(){
for(int x:node) cnt[x]=0,ve[x].clear();
}
signed main(){
cin>>Tid>>n;
for(int i=1;i<=n;i++) cin>>s[i],m=max(m,(signed)s[i].size());
for(int i=1;i<=n;i++) imp.push_back(ACAM::Insert(s[i]));
ACAM::Build();
imp.push_back(0);
sort(imp.begin(),imp.end());
imp.erase(unique(imp.begin(),imp.end()),imp.end());
for(int i=1;i<imp.size();i++){
int u=ACAM::tr[ACAM::tr[imp[i]].fail].vld;
u=lower_bound(imp.begin(),imp.end(),u)-imp.begin();
e[u].push_back(i);
}
DFS(0),InitLCA();
for(int i=1;i<=n;i++){
vector<int> v;
ACAM::Get(s[i],v);
for(int &x:v){
x=ACAM::tr[x].vld;
x=lower_bound(imp.begin(),imp.end(),x)-imp.begin();
cnt[x]++;
}
GetVir(v);
Calc(0,0);
VirClear();
}
ans[0]=1ll*n*n;
for(int i=1;i<=m;i++) ans[0]-=ans[i];
for(int i=1;i<=m;i++) ans[i]+=ans[i-1];
cin>>q;
while(q--){
ll k;
cin>>k;
k=1ll*n*n-k+1;
int p=lower_bound(ans,ans+m+1,k)-ans;
cout<<p<<endl;
}
return 0;
}

浙公网安备 33010602011771号