[71] (多校联训) A层冲刺NOIP2024模拟赛24
byd T3 放道这种题有什么深意吗
确实是签,但是一直在想组合意义,最后因为没提前处理逆元遗憾离场了,赛后看题解发现的确是往树上转化更简单点
赛时的组合意义代码
没过
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353,num=233;
int k;
string s;
int pi[1000002];
int fact[1000002];
int tp[1000002],ct[1000002];
vector<int>v;
int power(int a,int t){
int base=a,ans=1;
while(t){
if(t&1){
ans=ans*base%p;
}
base=base*base%p;
t>>=1;
}
return ans;
}
int C(int n,int m){
return fact[n]*power(fact[n-m]*fact[m]%p,p-2)%p;
}
signed main(){
// freopen("sam/T1/ex.in","r",stdin);
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin>>k>>s;
s=" "+s;
basenum[0]=1;
fact[0]=1;
for(int i=1;i<=(int)s.length();++i){
if(i!=(int)s.length()) h[i]=h[i-1]*num+s[i];
if(i!=(int)s.length()) basenum[i]=basenum[i-1]*num;
fact[i]=fact[i-1]*i%p;
}
for(int i=2;i<=(int)s.length()-1;++i){
int j=pi[i-1];
while(j>0 and s[j+1]!=s[i]) j=pi[j];
if(s[j+1]==s[i]) j++;
pi[i]=j;
}
for(int i=1;i<=(int)s.length()-1;++i){
int j=i;
while(j>0){
tp[j]++;
ct[i]++;
j=pi[j];
}
}
int ans=C((int)s.length(),k);
for(int i=1;i<=(int)s.length()-1;++i){
ans=(ans+C(tp[i],k)*((ct[i]+1)*(ct[i]+1)-ct[i]*ct[i])%p)%p;
}
cout<<ans<<endl;
}
组合意义的大体思路是先处理出所有 border,然后枚举每个前缀,然后再枚举以这个前缀为公共前后缀(不一定非得是 border)的前缀数量(这句话有点绕,形式化地说,设
然后是题解给的树上意义的解法
如果将
这个转化还挺有意思的
然后需要做的就只有枚举每个点作为公共 LCA 的情况,容易发现只有在子树里选才行,方案为
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
int k,ans;
int pi[1000002];
string s;
int fact[1000002],inv[1000002],invfact[1000002];
vector<int>e[1000001];
inline int C(int n,int m){
if(n<m) return 0;
return fact[n]*invfact[m]%p*invfact[n-m]%p;
}
int sizen[1000001];
void dfs(int now,int deep){
sizen[now]=1;
int tot=0;
for(int i:e[now]){
dfs(i,deep+1);
sizen[now]+=sizen[i];
tot=(tot+C(sizen[i],k))%p;
}
ans=(ans+(C(sizen[now],k)%p-tot+p)%p*(deep*deep)%p)%p;
}
signed main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin>>k>>s;
s=" "+s;
for(int i=2;i<=(int)s.length()-1;++i){
int j=pi[i-1];
while(j and s[j+1]!=s[i]) j=pi[j];
if(s[j+1]==s[i]) j++;
pi[i]=j;
}
for(int i=1;i<=(int)s.length()-1;++i){
e[pi[i]].push_back(i);
}
inv[1]=1;
for(int i=2;i<=1000001;++i){
inv[i]=inv[p%i]*(p-p/i)%p;
}
fact[0]=invfact[0]=1;
for(int i=1;i<=1000001;++i){
fact[i]=fact[i-1]*i%p,invfact[i]=invfact[i-1]*inv[i]%p;
}
dfs(0,1);
cout<<ans;
}
二分+数点做法
考虑直接钦定一个定点(题目里也是让这么求的),然后枚举区间另一个端点的所有可能情况
发现这个东西完全没有单调性,这很不好,于是考虑做 trick
怎么才能套上 trick,考虑到如果你只钦定左端点
对右端点,你用同样的情况去找满足条件的
设对左端点
这东西复杂度太高,考虑直接上个啥数据结构维护一下
上权值树状数组(线段树亦可,但是比较危险),按
另一边也是同理做
#include<bits/stdc++.h>
using namespace std;
int n,id;
int a[1000001],lg2[1000001];
int stmaxn[20][1000001],stminn[20][1000001];
inline int qmax(int l,int r){
int tmp=lg2[r-l+1];
return max(stmaxn[tmp][l],stmaxn[tmp][r-(1<<tmp)+1]);
}
inline int qmin(int l,int r){
int tmp=lg2[r-l+1];
return min(stminn[tmp][l],stminn[tmp][r-(1<<tmp)+1]);
}
struct stree{
struct tree{
int sum;
}t[1000001*4];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void change(int id,int l,int r,int pos){
if(l==r){
t[id].sum++;
return;
}
int mid(l,r);
if(pos<=mid) change(tol,l,mid,pos);
else change(tor,mid+1,r,pos);
t[id].sum=t[tol].sum+t[tor].sum;
}
int ask(int id,int l,int r,int L,int R){
if(L<=l and r<=R){
return t[id].sum;
}
int mid(l,r);
if(R<=mid) return ask(tol,l,mid,L,R);
else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
return ask(tol,l,mid,L,mid)+ask(tor,mid+1,r,mid+1,R);
}
}A,B;
int la[1000001],rb[1000001];
struct la_t{
int la,id;
inline bool operator<(const la_t&A)const{
return la>A.la;
}
}lat[1000001];
struct rb_t{
int rb,id;
inline bool operator<(const rb_t&A)const{
return rb<A.rb;
}
}rbt[1000001];
int ansla[1000001],ansrb[1000001];
int main(){
ios::sync_with_stdio(false);
cin>>n>>id;
for(int i=1;i<=n;++i){
cin>>a[i];
if(i!=1) lg2[i]=lg2[i/2]+1;
stmaxn[0][i]=stminn[0][i]=a[i];
}
for(int i=1;i<=19;++i){
for(int j=1;j<=n;++j){
stmaxn[i][j]=max(stmaxn[i-1][j],stmaxn[i-1][j+(1<<(i-1))]);
stminn[i][j]=min(stminn[i-1][j],stminn[i-1][j+(1<<(i-1))]);
}
}
for(int i=1;i<=n;++i){
int l=i+1,r=n,ans=n+1;
while(l<=r){
int mid=(l+r)/2;
if(qmax(i,mid)!=a[i] and qmin(i,mid)!=a[i]){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
lat[i].la=ans;
lat[i].id=i;
la[i]=ans;
// cout<<i<<" ["<<ans<<" "<<n<<"]"<<endl;
}
// cout<<endl;
for(int i=1;i<=n;++i){
int l=1,r=i-1,ans=0;
while(l<=r){
int mid=(l+r)/2;
if(qmax(mid,i)!=a[i] and qmin(mid,i)!=a[i]){
l=mid+1;
ans=mid;
}
else r=mid-1;
}
rbt[i].rb=ans;
rbt[i].id=i;
rb[i]=ans;
// cout<<i<<" ["<<1<<" "<<ans<<"]"<<endl;
}
sort(lat+1,lat+n+1);
sort(rbt+1,rbt+n+1);
int j=n+1;
for(int i=1;i<=n;++i){
if(lat[i].la==n+1) continue;
while(j!=lat[i].la){
j--;
if(rb[j]!=0) B.change(1,1,n,rb[j]);
}
ansla[lat[i].id]=B.ask(1,1,n,lat[i].id,n);
}
j=0;
for(int i=1;i<=n;++i){
if(rbt[i].rb==0) continue;
while(j!=rbt[i].rb){
j++;
if(la[j]!=n+1) A.change(1,1,n,la[j]);
}
ansrb[rbt[i].id]=A.ask(1,1,n,1,rbt[i].id);
}
for(int i=1;i<=n;++i){
cout<<ansla[i]<<' ';
}
cout<<endl;
for(int i=1;i<=n;++i){
cout<<ansrb[i]<<' ';
}
cout<<endl;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!