CSP-S前集训总结
2025-10-16
战绩:100+100+65+0,总榜并列 rk1。
A.小Z爱计数
简单贪心,按时间排序。对于相邻两个限制,要么直接走去,要么有归零。判断一下就行了。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;int t,c,n;
struct tmc{int a,b;}pr[N];
inline int cmp(tmc x,tmc y){
return x.a<y.a;
}inline void solve(){
cin>>c>>n;
for(int i=1;i<=n;i++)
cin>>pr[i].a>>pr[i].b;
sort(pr+1,pr+n+1,cmp);
for(int i=1;i<=n;i++){
int dt=pr[i].a-pr[i-1].a,lb=pr[i-1].b,nb=pr[i].b;
if(abs(nb)>=dt&&(dt<abs(nb-lb)||((dt-abs(nb-lb))&1))){cout<<"No\n";return;}
}cout<<"Yes\n";
}int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>t;
while(t--) solve();
return 0;
}//Dust to Dust, Ash to Ash, 彼方へ.
B.小Z爱划分
考虑大力拆位。设 \(dp_{i,j,k}\) 表示当前枚举到第 \(i\) 个数,第 \(j\) 和第 \(k\) 位的贡献。简单转移即可。
时间复杂度 \(O(n\log^2V)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,p=1e9+7;
int t,n,now[30][30][5],tmp[4];
inline int md(int x){return x>=p?x-p:x;}
inline void solve(){
for(int i=0;i<30;i++) for(int j=0;j<30;j++)
for(int k=0;k<4;k++) now[i][j][k]=!k;cin>>n;
for(int i=1,a,f;i<=n;i++){
cin>>a,f=0;
for(int j=0,cw;j<30;j++) for(int k=0;k<30;k++){
cw=((a>>j)&1)*2+((a>>k)&1);
for(int c=0;c<4;c++) tmp[c]=now[j][k][c];
for(int c=0;c<4;c++) now[j][k][c^cw]=tmp[c];
f=(f+(1ll<<(j+k))%p*now[j][k][3])%p;
}if(i==n){cout<<f<<"\n";return;}
for(int j=0;j<30;j++) for(int k=0;k<30;k++)
now[j][k][0]=md(now[j][k][0]+f);
}
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>t;
while(t--) solve();
return 0;
}
C.小Z爱优化
并不显然可以想到 \(dp\)。设 \(f_{i,j}\) 表示我们此时最大值为 \(i\),枚举到第 \(j\) 的归属,此时最小值最大是多少,则有转移方程:
发现对于每个 \(f_i\),它每个转移方程是否能够启用只会变化 \(O(n)\),使用动态 \(dp\) 优化。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int t,n,a[N],cnt,ans;
struct cz{int x,op,num;}cc[N<<1];
inline bool cmp(cz x,cz y){
return x.num<y.num;
}struct mat{int z[2][2];}sg[N<<2],cs;
inline void clear(mat &x){
memset(x.z,0,sizeof(x.z));
}inline mat operator*(mat x,mat y){
mat z;clear(z);
for(int i=0;i<2;i++) for(int j=0;j<2;j++) for(int k=0;k<2;k++)
z.z[i][j]=max(z.z[i][j],min(x.z[i][k],y.z[k][j]));
return z;
}inline void build(int x,int l,int r){
if(l==r){clear(sg[x]),sg[x].z[1][0]=2e9;return;}
int mid=(l+r)>>1;build(x<<1|1,mid+1,r);
build(x<<1,l,mid),sg[x]=sg[x<<1]*sg[x<<1|1];
}inline void chg(int x,int l,int r,int k,int num,int op){
if(l==r){sg[x].z[op][1]=num;return;}
int mid=(l+r)>>1;
if(k<=mid) chg(x<<1,l,mid,k,num,op);
else chg(x<<1|1,mid+1,r,k,num,op);
sg[x]=sg[x<<1]*sg[x<<1|1];
}inline void solve(){
cin>>n,cnt=n,ans=2e9;
for(int i=1;i<=n;i++) cin>>a[i],cc[i]={i,1,a[i]};
for(int i=2;i<=n;i++) cc[++cnt]={i,0,a[i]+a[i-1]};
sort(cc+1,cc+cnt+1,cmp),build(1,1,n);
for(int i=1;i<=cnt;i++){
chg(1,1,n,cc[i].x,cc[i].num,cc[i].op);
int mn=(cs*sg[1]).z[0][1];
if(mn) ans=min(ans,cc[i].num-mn);
}cout<<ans<<"\n";
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>t,cs.z[0][1]=2e9;
while(t--) solve();
return 0;
}//Dust to Dust, Ash to Ash, 彼方へ.
D.小Z爱考试
神人题。考虑显然最终会形成一个基环树。我们定义一个点是黑点,当且仅当 \(a_{b_i}>a_i\) 或 \(a_{b_i}+w_{b_i}<a_i\),也就是无论联络对象如何变化,\(i\) 的结果都是确定的。其它的点我们就称之为白点,是否 \(+w_i\) 由其最近的黑点的情况决定。问题转化为距离一个人最近的黑点。我们对于每一个基环上的点挂的树做树链剖分,对于每一个基环建 \(set\) 存储黑点即可。时间复杂度为 \(O(n\log n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5,p=1e9+7;
int n,q,t,mxj,a[N],to[N],w[N],sn[N],dep[N],vs[N];
int idx,cnt,tp[N],dfn[N],ps[N],inv[N],sz[N],di[N];
vector<int>cr[N],g[N];set<int>st[N];int mx[N<<2];
inline int qpow(int x,int y){
int re=1;
while(y){
if(y&1) re=re*x%p;
x=x*x%p,y>>=1;
}return re;
}inline int dfsf(int x){
if(vs[x]&&vs[x]>-2) return 0;
if(vs[x]) return ++cnt,x;vs[x]=-2;
for(int y:g[x]){
int re=dfsf(y);if(!re) continue;
if(re<0) return vs[x]=-1;cr[di[x]=cnt].push_back(x);
return vs[x]=cr[cnt].size(),x==re?-1:re;
}return vs[x]=-1,0;
}inline void dfs1(int x){
sz[x]=1,sn[x]=0;
for(int y:g[x]) if(vs[y]<0)
dep[y]=dep[x]+1,dfs1(y),sz[x]+=sz[y],sn[x]=(sz[sn[x]]<sz[y]?y:sn[x]);
}inline void dfs2(int x,int top){
tp[x]=top,ps[dfn[x]=++idx]=x;
if(sn[x]) dfs2(sn[x],top);
for(int y:g[x]) if(y!=sn[x]&&vs[y]<0) dfs2(y,y);
}inline int check(int x){
return a[to[x]]>a[x]||a[to[x]]+w[to[x]]<=a[x];
}inline void build(int x,int l,int r){
if(l==r){mx[x]=check(ps[l])*ps[l];return;}
int mid=(l+r)>>1;build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
mx[x]=mx[x<<1|1]?mx[x<<1|1]:mx[x<<1];
}inline void chg(int x,int l,int r,int k){
if(l==r){mx[x]=check(ps[l])*ps[l];return;}
int mid=(l+r)>>1;
if(k<=mid) chg(x<<1,l,mid,k);
else chg(x<<1|1,mid+1,r,k);
mx[x]=mx[x<<1|1]?mx[x<<1|1]:mx[x<<1];
}inline int maxn(int x,int l,int r,int L,int R){
if(L<=l&&r<=R) return mx[x];
int mid=(l+r)>>1,re=0;
if(R>mid) re=maxn(x<<1|1,mid+1,r,L,R);
if(!re&&L<=mid) re=max(re,maxn(x<<1,l,mid,L,R));
return re;
}inline void chge(int x){
if(!dep[x]){
if(check(x)&&!st[di[x]].count(vs[x])) st[di[x]].insert(vs[x]);
else if(!check(x)&&st[di[x]].count(vs[x])) st[di[x]].erase(vs[x]);
}chg(1,1,n,dfn[x]);
}inline int answ(int x){
int cc=0,nw=x,cd;
while(1){
cc=maxn(1,1,n,dfn[tp[x]],dfn[x]);
if(cc||!dep[tp[x]]) break;x=to[tp[x]];
}if(cc){
if(a[to[cc]]+w[to[cc]]<=a[cc]) return a[nw];
return (a[nw]+w[nw]*inv[dep[nw]-dep[cc]+1])%p;
}x=tp[x];if(!st[di[x]].size()) return a[nw];
if(*--st[di[x]].end()<vs[x]) cd=*st[di[x]].begin();
else cd=*st[di[x]].lower_bound(vs[x]);cc=cr[di[x]][cd];
if(a[to[cc]]+w[to[cc]]<=a[cc]) return a[nw];
int len=dep[nw]+(cd-vs[x]+cr[di[x]].size())%cr[di[x]].size()+1;
return (a[nw]+w[nw]*inv[len])%p;
}inline void solve(){
for(int i=1;i<=n;i++) g[i].clear(),di[i]=vs[i]=0;
for(int i=1;i<=cnt;i++) st[i].clear(),cr[i].clear();
cin>>n>>q,mxj=1,cnt=idx=0;
for(int i=1;i<=n;i++){
cin>>a[i]>>to[i]>>w[i];
g[to[i]].push_back(i),mxj=mxj*i%p;
}for(int i=1;i<=n;i++) if(!vs[i]) dfsf(i);
for(int i=1;i<=n;i++) if(vs[i]>0){
dep[i]=0,dfs1(i),dfs2(i,i),vs[i]--;
if(check(i)) st[di[i]].insert(vs[i]);
}inv[n]=qpow(mxj,p-2),build(1,1,n);
for(int i=n-1;~i;i--) inv[i]=inv[i+1]*(i+1)%p;
while(q--){
int opt,i,c;cin>>opt>>i;
if(opt==1){
cin>>c,a[i]=c,chge(i);
for(int j:g[i]) chge(j);
}else if(opt==2){
cin>>c,w[i]=c,chge(i);
for(int j:g[i]) chge(j);
}else cout<<answ(i)<<"\n";
}
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>t;
while(t--) solve();
return 0;
}//Dust to Dust, Ash to Ash, 彼方へ.
2025-10-17
100+60+100+100=360,rk2。
A.Divisors
把所有数的因数全部扔到 \(map\) 里,直接查就行。时间复杂度 \(O(m\sqrt V)\)。
#include<bits/stdc++.h>
using namespace std;
int n,m,ans[205];
unordered_map<int,int>mp;
int main(){
freopen("div.in","r",stdin);
freopen("div.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>n>>m;
for(int i=1,a;i<=m;i++){
cin>>a;
for(int j=1;j*j<a;j++)
if(a%j==0) mp[j]++,mp[a/j]++;
int j=(int)sqrt(a);
if(j*j==a) mp[j]++;
}for(auto x:mp) if(x.first<=n) ans[x.second]++;
for(int i=1;i<=m;i++) ans[0]-=ans[i];
cout<<ans[0]+n<<"\n";
for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
return 0;
}
B.Market
拿单价当值域显然是不明智的。考虑拿价值当值域,背包求最小价格,二分求解即可。时间复杂度 \(O(n^2v)\)。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=305,M=1e5+5;
int n,m,W,ans[M];ll f[N*N],ef[N*N];
struct sto{int c,v,t;}st[N];
struct que{int nt,nm,id;}qu[M];
inline int cmp(sto x,sto y){
return x.t<y.t;
}inline int cmp1(que x,que y){
return x.nt<y.nt;
}int main(){
freopen("market.in","r",stdin);
freopen("market.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>st[i].c>>st[i].v>>st[i].t,W+=st[i].v;
for(int i=1;i<=W;i++) f[i]=1e18;
sort(st+1,st+n+1,cmp),ef[W+1]=1e18;
for(int i=1;i<=m;i++) cin>>qu[i].nt>>qu[i].nm,qu[i].id=i;
sort(qu+1,qu+m+1,cmp1);
for(int i=1,j=1;i<=m;i++){
if(st[j].t<=qu[i].nt){
for(;st[j].t<=qu[i].nt&&j<=n;j++)
for(int k=W;k>=st[j].v;k--)
f[k]=min(f[k],f[k-st[j].v]+st[j].c);
for(int i=W;i;i--) ef[i]=min(ef[i+1],f[i]);
}if(j>1) ans[qu[i].id]=upper_bound(ef+1,ef+W+2,qu[i].nm)-ef-1;
}for(int i=1;i<=m;i++) cout<<ans[i]<<"\n";
return 0;
}
C.连通性
D.树
考虑直接大力 \(dp\)。设 \(f_i\) 表示处理完 \(i\) 子树内的部分,且 \(i\) 向父亲连的边方向确定的方案数。显然,两子树中有相连点对的两个儿子向根连的边方向互异;子树内中有与子树外点形成点对的儿子向根连的边方向相同。按照上述说法,建一个虚点表示与第二类儿子互异。将互异点连边,跑 \(dfs\),判断是否无解(出现奇环),同时合并连通块。转移方程为:
其中 \(cnt_i\) 表示 \(dfs\) 跑出的连通块数量。时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5,p=1e9+7;
int n,m,cnt,mn[N],vs[N],dep[N],lc[N][20];
int cr[N],f[N],num[N];vector<int>g[N],ve[N];
inline void dfs(int x,int fa){
dep[x]=dep[lc[x][0]=fa]+1;
for(int i=0;i<19;i++)
lc[x][i+1]=lc[lc[x][i]][i];
for(int y:g[x]) if(y!=fa) dfs(y,x);
}inline int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;~i;i--)
if(dep[x]-dep[y]>=(1<<i)) x=lc[x][i];
if(x==y) return x;
for(int i=19;~i;i--)
if(lc[x][i]!=lc[y][i]) x=lc[x][i],y=lc[y][i];
return lc[x][0];
}inline int ace(int x,int y){
for(int i=19;~i;i--)
if(y&(1<<i)) x=lc[x][i];
return x;
}inline void dfsp(int x){
for(int y:ve[x]){
if(vs[y]&&vs[y]%2==vs[x]%2) cout<<0,exit(0);
if(!vs[y]) vs[y]=vs[x]+1,cr[y]=cnt,dfsp(y);
}
}inline void dpfs(int x,int fa){
for(int y:g[x]) if(y!=fa) dpfs(y,x),mn[x]=min(mn[x],mn[y]);
if(mn[x]<dep[x]){
ve[n+1].clear(),cr[n+1]=vs[n+1]=0;
for(int y:g[x]) if(y!=fa&&mn[y]<dep[x])
ve[n+1].push_back(y),ve[y].push_back(n+1);
}int now=cnt+1;
for(int y:g[x]) if(y!=fa&&!cr[y]) cr[y]=++cnt,vs[y]=1,dfsp(y);
for(int y:g[x]) if(y!=fa) num[cr[y]]=num[cr[y]]+f[y];
for(int i=now;i<=cnt;i++) f[x]+=(cr[n+1]!=i)+num[i];
}int main(){
freopen("usmjer.in","r",stdin);
freopen("usmjer.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),f[n+1]=1;
cin>>n>>m,memset(mn,0x3f,sizeof(mn));
for(int i=1,u,v;i<n;i++)
cin>>u>>v,g[u].push_back(v),g[v].push_back(u);
dfs(1,0);
for(int i=1,u,v,lc,uc,vc;i<=m;i++){
cin>>u>>v,lc=lca(u,v);
if(u!=lc&&v!=lc){
uc=ace(u,dep[u]-dep[lc]-1),vc=ace(v,dep[v]-dep[lc]-1);
ve[uc].push_back(vc),ve[vc].push_back(uc);
}mn[v]=min(mn[v],dep[lc]),mn[u]=min(mn[u],dep[lc]);
}dpfs(1,0);int ans=1;
for(int i=1;i<f[1];i++) ans=(ans*2>=p?ans*2-p:ans*2);
return cout<<ans,0;
}
2025-10-18
70+100+100+0=270,rk4。
A.最长不下降子序列
实际上答案序列只有可能是 \(111\dots 222\dots 111\dots 222\) 这样的。考虑枚举第一个 \(2\) 段和第二个 \(1\) 段的临界点。设 \(sm1_i,sm2_i\) 表示前缀 \(1,2\) 的数量,\(sm_i=sm1_i-sm2_i\),三个分界点(第一、二个 \(1\) 序列末尾和第一个 \(2\) 序列末尾)分别为 \(i,j,k\),则有:
\(sm2_n\) 固定,只需要求前后缀 \(sm\) 的 \(\max\) 就行。时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,ans,cc,a[N],sm[N],mx[N],nx[N];
int main(){
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n,mx[0]=nx[n+1]=-1e9;
for(int i=1;i<=n;i++)
cin>>a[i],sm[i]=sm[i-1]+(a[i]<2?1:-1),cc+=(a[i]>1);
for(int i=1;i<=n;i++) mx[i]=max(sm[i],mx[i-1]);
for(int i=n;i;i--) nx[i]=max(sm[i],nx[i+1]);
for(int i=1;i<=n;i++)
ans=max(ans,mx[i]+nx[i]-sm[i]+cc);
return cout<<ans,0;
}
B.美食节
根据经典小性质,只需要保证最多的一直 \(\le \lceil\frac{all}2\rceil\) 即可。其它情况下选择不与上一个位置相同的最小位置即可。用 \(set\) 维护,时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,a[N],nx[N],mp[N],mn[N<<2],num[N],as[N];
struct data{int num,id;};
inline bool operator<(data x,data y){
return x.num==y.num?x.id<y.id:x.num<y.num;
}set<data>st;
inline void build(int x,int l,int r){
if(l==r){mn[x]=mp[l];return;}
int mid=(l+r)>>1;build(x<<1|1,mid+1,r);
build(x<<1,l,mid),mn[x]=min(mn[x<<1],mn[x<<1|1]);
}inline void chg(int x,int l,int r,int k){
if(l==r){mn[x]=mp[l];return;}
int mid=(l+r)>>1;
if(k<=mid) chg(x<<1,l,mid,k);
else chg(x<<1|1,mid+1,r,k);
mn[x]=min(mn[x<<1],mn[x<<1|1]);
}inline int minn(int x,int l,int r,int L,int R){
if(L>R) return 1e9;
if(L<=l&&r<=R) return mn[x];
int mid=(l+r)>>1,re=1e9;
if(R>mid) re=minn(x<<1|1,mid+1,r,L,R);
if(L<=mid) re=min(re,minn(x<<1,l,mid,L,R));
return re;
}int main(){
freopen("food.in","r",stdin);
freopen("food.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=3e5;i++) mp[i]=n+1;
for(int i=n;i;i--) nx[i]=mp[a[i]],num[a[i]]++,mp[a[i]]=i;
for(int i=1;i<=3e5;i++) if(num[i]) st.insert({num[i],mp[i]});
if((*--st.end()).num*2>n+1) cout<<-1,exit(0);build(1,1,n);
for(int i=1;i<=n;i++){
if((*--st.end()).num>(n-i+1)/2) as[i]=(*--st.end()).id;
else as[i]=min(minn(1,1,n,1,a[as[i-1]]-1),minn(1,1,n,a[as[i-1]]+1,n));
st.erase({num[a[as[i]]]--,as[i]}),mp[a[as[i]]]=nx[as[i]],chg(1,1,n,a[as[i]]);
if(num[a[as[i]]]) st.insert({num[a[as[i]]],mp[a[as[i]]]});
}for(int i=1;i<=n;i++) cout<<as[i]<<" ";
return 0;
}
C.字符串
考虑回文串要么在原串内部,要么是以一个串的一个点为中心,向左右扩展跨串。前者 \(hash+\) 二分即可,后者先找出所有可以跨串(原串中回文串可以到达边界)的位置,然后查询最长可以匹配到的字符串,然后计算另一半串是这个串的前缀的前/后缀数量,即为贡献(二分+字典树+ \(hash\))。
时间复杂度 \(O(nm\log n)\)。
#include<bits/stdc++.h>
#define SZ 5056577
#define ll long long
#define ull unsigned ll
using namespace std;
const int N=3e5+5,M=2e6+5;ll sm[M],ans;
int T,n,m,cnt,tr[M][26];string s[N],t[N];
unordered_map<ull,int>mp;
ull qp[M],hsh[M],hsc[M];
inline void add1(string &st){
int nw=1;ull hs=0;
for(int i=st.size()-1;~i;i--){
hs=hs*1145141+st[i];
if(!tr[nw][st[i]-'a']) tr[nw][st[i]-'a']=++cnt,mp[hs]=cnt;
sm[nw=tr[nw][st[i]-'a']]++;
}
}inline void add2(string &st){
int nw=1;ull hs=0;
for(int i=0;st[i];i++){
hs=hs*1145141+st[i];
if(!tr[nw][st[i]-'a']) tr[nw][st[i]-'a']=++cnt,mp[hs]=cnt;
sm[nw=tr[nw][st[i]-'a']]++;
}
}inline void dfs(int x,int fa){
sm[x]+=sm[fa];
for(int i=0;i<26;i++)
if(tr[x][i]) dfs(tr[x][i],x);
}inline ull hsq1(int l,int r){
return hsh[r]-hsh[l-1]*qp[r-l+1];
}inline ull hsq2(int l,int r){
return hsc[l]-hsc[r+1]*qp[r-l+1];
}inline void solve(){
for(int i=1;i<=cnt;i++)
for(int j=0;j<26;j++) tr[i][j]=0;
for(int i=1;i<=cnt;i++) sm[i]=0;
cin>>n>>m,cnt=1,ans=0,mp.clear();
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=m;i++) cin>>t[i];
for(int i=1;i<=n;i++) add1(s[i]);dfs(1,0);
for(int i=1,len;i<=m;i++){
len=t[i].size(),hsc[len+1]=0;
for(int j=0;t[i][j];j++) hsh[j+1]=hsh[j]*1145141+t[i][j];
for(int j=len;j;j--) hsc[j]=hsc[j+1]*1145141+t[i][j-1];
for(int j=1;j<=len;j++){
int l=1,r=min(j,len-j+1),as=0;
while(l<=r){
int mid=(l+r)>>1;
if(hsq1(j-mid+1,j)!=hsq2(j,j+mid-1)) r=mid-1;
else l=mid+1,as=mid;
}ans+=1ll*as*n;
if(as!=j) continue;
l=1,r=len-j-j+1,as=0;
while(l<=r){
int mid=(l+r)>>1;
if(!mp.count(hsq1(j+j,j+j+mid-1))) r=mid-1;
else l=mid+1,as=mid;
}if(as) ans+=sm[mp[hsq1(j+j,j+j+as-1)]];
}
}for(int i=1;i<=cnt;i++)
for(int j=0;j<26;j++) tr[i][j]=0;
for(int i=1;i<=cnt;i++) sm[i]=0;
cnt=1,mp.clear();
for(int i=1;i<=m;i++) add2(t[i]);dfs(1,0);
for(int i=1,len;i<=n;i++){
len=s[i].size(),hsc[len+1]=0;
for(int j=0;s[i][j];j++) hsh[j+1]=hsh[j]*1145141+s[i][j];
for(int j=len;j;j--) hsc[j]=hsc[j+1]*1145141+s[i][j-1];
for(int j=1;j<=len;j++){
int l=1,r=min(j,len-j+1),as=0;
while(l<=r){
int mid=(l+r)>>1;
if(hsq1(j-mid+1,j)!=hsq2(j,j+mid-1)) r=mid-1;
else l=mid+1,as=mid;
}ans+=1ll*as*m;
if(as!=len-j+1) continue;
l=1,r=j-len+j-1,as=0;
while(l<=r){
int mid=(l+r)>>1;
if(!mp.count(hsq2(j+j-len-mid,j+j-len-1))) r=mid-1;
else l=mid+1,as=mid;
}if(as) ans+=sm[mp[hsq2(j+j-len-as,j+j-len-1)]];
}
}cout<<ans<<"\n";
}signed main(){
freopen("str.in","r",stdin);
freopen("str.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0),cin>>T,qp[0]=1;
for(int i=1;i<M;i++) qp[i]=qp[i-1]*1145141;
while(T--) solve();
return 0;
}
D.概率
发现 \(E(前n>后n)=E(前n<后n)=\frac{1-E(前n=后n)}2\),问题转化为求 \(E(前n=后n)\)。由于总方案数是好求的,所以考虑求前 \(n=\) 后 \(n\) 的方案数。
考虑将题目转化为前 \(n\) 在 \([0,m]\) 中随机,后 \(n\) 在 \([-m,0]\) 中随机,和为 \(0\) 的概率。不好处理,考虑后 \(n\) 在 \([-m,0]\) 中随机等价于在 \([0,m]\) 中随机 \(-nm\),问题转化为在 \([0,m]\) 中随机 \(2n\) 个数,和为 \(nm\) 的解。考虑容斥:
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N=4004005;
int t,n,m,p,ans,jc[N],inv[N];
inline int qpow(int x,int y){
int re=1;
while(y){
if(y&1) re=1ll*re*x%p;
x=1ll*x*x%p,y>>=1;
}return re;
}inline void solve(){
cin>>n>>m,ans=0;
for(int i=0;i<=2*n;i++) if(n*m-m*i-i>=0)
ans=(ans+(i%2?p-1ll:1ll)*jc[2*n]%p*inv[i]%p*inv[2*n-i]%p*jc[n*m-m*i-i+2*n-1]%p*inv[2*n-1]%p*inv[n*m-m*i-i])%p;
cout<<(1-1ll*ans*qpow(qpow(m+1,2*n),p-2)%p+p)*inv[2]%p<<"\n";
}signed main(){
freopen("pr.in","r",stdin);
freopen("pr.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>p>>t,jc[0]=inv[0]=1;
for(int i=1;i<N;i++) jc[i]=1ll*jc[i-1]*i%p;
inv[N-1]=qpow(jc[N-1],p-2);
for(int i=N-2;i;i--) inv[i]=inv[i+1]*(i+1ll)%p;
while(t--) solve();return 0;
}

浙公网安备 33010602011771号