【2023.11.14】NOIP2023模拟试题-34
T1
个人认为 T1 比 T2 难。
首先我们可以把答案转化成
而考虑
对于串 2 3 5 7 8
,考虑按顺序插入
显然 1 2 3 5 7 8
。
4
顶替掉一个位置。
因此,满足
我们总结规律得出,设缺失的数为
由于 upper_bound 查询的结果一定随着
#define fi(l,r) for(int i=l;i<=r;++i)
fi(1,n){
if(apr[i]==0){
while(las<kk&&a[las+1]<i)++las;
sub=sub*(las+aprd)%P;
++aprd;
}
}
至于如果序列不是升序,我们只用去前面最长的连续一节升序序列就行了,后面的序列没卵用。为什么?因为无论插什么数都不能插在后面数之后的空位里,比如这个例子:
2 3 8 5 7
8
后面
所以 8 5 7
一定会放在末尾。
参考代码
#include<bits/stdc++.h>
using namespace std;
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define ll long long
#define P 998244353
#define N 1000005
ll qpow(ll a,ll b){
ll c=1;
while(b){
if(b&1)c=c*a%P;
b>>=1;
a=a*a%P;
}
return c;
}
ll inv(ll x){return qpow(x,P-2);}
ll fac[N],ifac[N],ans=1,sub=1;
bool apr[N]={0};
#define C(n,m) (fac[n]*ifac[m]%P*ifac[n-m]%P)
int n,kk,a[N],last=1,aprd,las;
int main(){
freopen("ordinary.in","r",stdin);
freopen("ordinary.out","w",stdout);
scanf("%d %d",&n,&kk);
fi(1,kk){
scanf("%d",&a[i]);
apr[a[i]]=1;
if(a[last]>a[last-1])++last;
}
fac[0]=1;
fi(1,n)fac[i]=fac[i-1]*i%P;
ifac[n]=inv(fac[n]);
for(int i=n-1;i>=1;--i)ifac[i]=ifac[i+1]*(i+1)%P;
ans=fac[n]*ifac[kk]%P;
fi(1,n){
if(apr[i]==0){
while(las<last&&a[las+1]<i)++las;
sub=sub*(las+aprd)%P;
++aprd;
}
}
printf("%lld\n",(ans%P-sub%P+P)%P);
return 0;
}
T2
这不比 T1 简单?
先建括号树,节点的编号
对于括号序列
我们分析一下如何操作才能得出序列 ()()()()()()()()()
:
有几步转换不止一个操作。
我们发现只需要
而且仔细观察动画,我们发现将
将
更细致地观察,我们发现:一个菊花带上父节点需要
一个链带上父节点需要
所以我们大胆推出结论:原括号树中一个菊花对应需要
#define fi(l,r) for(int i=l;i<=r;++i)
void work(int t){
fi(1,n){
if(scnt[t][i]>1)ans[t]+=2;//菊花图:ans+2
else if(scnt[t][i]==0&&scnt[t][fah[t][i]]==1)++ans[t];//链:ans+1
}
}
接下来我们考虑两个括号序列之间的转化:
通法当然可以将两棵树都转化为 ()()()()()()()()()
,但这并不保证最优解,我们考虑哪些地方是重复操作的。
注意到当两颗树的
同样的,
即:我们可以保留
显然,当子树包含相同的节点的时候才会保留,那么如何
其实我们只需要统计子树大小就行了,因为这棵树的节点编号与 dfs 序是一致的,所以任意子树的节点形成的有序序列一定完全等价于升序序列
对了,还有一个条件就是父亲要相同而且父亲也要是相等的节点,不然子树也会被父亲尝试趋同的操作而被打乱。
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define fab(sth) ff(t,0,1)
for(int i=n;i>=1;--i)
fab()
siz[t][fah[t][i]]+=siz[t][i];
fi(1,n)
if(fah[0][i]==fah[1][i]&&sam[fah[0][i]]==1&&siz[0][i]==siz[1][i])sam[i]=1;
参考代码
查看代码
#include<bits/stdc++.h>
using namespace std;
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define ll long long
#define N 500005
#define M 1000005
int fah[2][N],n,L,pcnt,stk[N],stl,posofp[M],posof[M],scnt[2][N],ans[3],siz[2][N];//ans2
bool sam[N]={0};
#define fab(sth) ff(t,0,1)
void nothing(int t){
char ch=getchar();
while(ch!='(')ch=getchar();
fi(1,L){
stk[++stl]=(ch=='(');
posof[stl]=i;
if(ch=='(')posofp[i]=++pcnt;
else while(stk[stl-1]==1&&stk[stl]==0){
fah[t][posofp[posof[stl-1]]]=posofp[posof[stl-2]];
++scnt[t][posofp[posof[stl-2]]];
stl-=2;//解析括号序列并建树:将栈顶的 fah 指向下一个元素
}
ch=getchar();
}
if(t==1){
for(int i=n;i>=1;--i)fab()siz[t][fah[t][i]]+=siz[t][i];
fi(1,n)if(fah[0][i]==fah[1][i]&&sam[fah[0][i]]==1&&siz[0][i]==siz[1][i])sam[i]=1;
}
fi(1,n){
if(sam[i]==1){
if(scnt[0][i]>1)ans[2]+=2;//菊花图:ans+2
else if(scnt[0][i]==0&&scnt[0][fah[0][i]]==1)++ans[2];//链:ans+1
}else{
if(scnt[t][i]>1)ans[t]+=2;//菊花图:ans+2
else if(scnt[t][i]==0&&scnt[t][fah[t][i]]==1)++ans[t];//链:ans+1
}
}
}
int main(){
freopen("miracle.in","r",stdin);
freopen("miracle.out","w",stdout);
scanf("%d",&L);
n=L>>1;sam[0]=1;
fi(1,n)siz[0][i]=siz[1][i]=1;
nothing(0);
stl=pcnt=0;
nothing(1);
printf("%d\n",ans[0]+ans[1]-ans[2]);
return 0;
}
说在后面
先谈一谈我对巴以战争的看法吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现