noip模拟36
今天再次翻车掉出前十
开题看错 以为操作2的值固定发现是个简单题,然后 开始大力 两个小时还是过不了样例(后来发现是尽头情况处理错了),回头看 发现看错题,重新想+写半个小时,最后二十分钟打了 暴力,又开始调 ,直到结束也没有一个靠谱的输出
实践证明要先打好暴力(今天唯一得的分全是暴力分……)
难度判断失误,留给 时间过少,而实际上 更容易得到更高的分数
A. Dove 打扑克
由于每次合并都会减少一堆,所以哪怕最终每一堆个数都不一样,最多只有 个
所以可以得出结论不同大小的堆的个数最多 个
那么把这些存进数组里,只要保证每次操作是根号的,就可以保证在 的复杂度完成
统计答案时,可以用双指针维护,配合后缀和预处理,还可以保证根号复杂度
B. Cicada 与排序
对于每一个数处理其最终在每个位置的概率,再乘位置即可算出期望
设 表示这个树到 位置的概率
考虑模拟归并排序的过程进行递归(只递归当前数该去的半个区间)
这样需要维护上一层向这一层的 数组的转移
首先维护一个 辅助 值, 表示排序合并左右区间的时候左边的选到第 个数,此时右边选到第 个数的概率,这个很好转移, 即可
考虑 通过 进行转移
对于 ,如果从 转移会有一个问题:不能保证当前时刻选的是左区间的点
所以应该改为
这样还有一个问题,如果右边已经到了尽头,那么其实左边向下一个移动的概率不再是 而变成了
那么相当于
这样总复杂度是 的
代码实现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=505;
const int mod=998244353;
int n,f[maxn][maxn],g[maxn],h[maxn],a[maxn],b[maxn],posl,posr,pos[maxn],ans[maxn],inv2;
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
int po(int a,int b=mod-2){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b>>=1;
}
return ans;
}
void solve(int l,int r,int pos,int w){
if(l==r){
g[l]=1;
return ;
}
for(int i=l;i<=r;i++)b[i]=a[i];
sort(b+l,b+r+1);
b[l-1]=0;
b[r+1]=0;
int st=0,ed=0;
for(int i=l;i<=r;i++){
if(b[i]!=b[i-1]&&b[i]==w)st=i;
if(b[i]!=b[i+1]&&b[i]==w)ed=i;
}
// if(w==3)cout<<st<<" "<<ed<<endl;
int mid=l+r>>1;
if(pos<=mid){
solve(l,mid,pos,w);
for(int i=l;i<=mid;i++)b[i]=a[i];
sort(b+l,b+mid+1);
b[l-1]=b[mid+1]=0;
for(int i=l;i<=mid;i++){
if(b[i]!=b[i-1]&&b[i]==w)posl=i;
if(b[i]!=b[i+1]&&b[i]==w)posr=i;
}
int num=0;
for(int i=mid+1;i<=r;i++)if(a[i]==w)num++;
for(int i=0;i<=posr-posl;i++){
for(int j=0;j<=num-1;j++){
h[st+i+j]=(h[st+i+j]+g[posl+i]*f[i][j]%mod*inv2)%mod;
}
int sum=0;
if(!num)sum=1;
else{
for(int j=0;j<=i;j++)sum=(sum+f[j][num-1])%mod;
sum=sum*inv2%mod;
}
h[st+i+num]=(h[st+i+num]+g[i+posl]*sum)%mod;
}
for(int i=l;i<=r;i++)g[i]=0;
for(int i=st;i<=ed;i++){
g[i]=h[i],h[i]=0;
// if(pos==1)cout<<l<<" "<<r<<" "<<i<<" "<<g[i]<<endl;
}
}
else{
solve(mid+1,r,pos,w);
for(int i=mid+1;i<=r;i++)b[i]=a[i];
sort(b+mid+1,b+r+1);
b[mid]=b[r+1]=0;
for(int i=mid+1;i<=r;i++){
if(b[i]!=b[i-1]&&b[i]==w)posl=i;
if(b[i]!=b[i+1]&&b[i]==w)posr=i;
}
int num=0;
for(int i=l;i<=mid;i++)if(a[i]==w)num++;
for(int i=0;i<=posr-posl;i++){
for(int j=0;j<=num-1;j++){
h[st+i+j]=(h[st+i+j]+g[posl+i]*f[i][j]%mod*inv2)%mod;
// if(pos==5)cout<<"hhh "<<st+i+j<<" "<<h[st+i+j]<<endl;
}
int sum=0;
if(!num)sum=1;
else{
for(int j=0;j<=i;j++)sum=(sum+f[j][num-1])%mod;
sum=sum*inv2%mod;
}
h[st+i+num]=(h[st+i+num]+g[i+posl]*sum)%mod;
// if(pos==5)cout<<"ppp "<<st+i+num<<" "<<h[st+i+num]<<" "<<i<<" "<<sum<<endl;
}
// if(w==4)cout<<"ggg "<<l<<" "<<r<<" "<<st<<" "<<ed<<" "<<h[st]<<endl;
for(int i=l;i<=r;i++)g[i]=0;
for(int i=st;i<=ed;i++){
g[i]=h[i],h[i]=0;
// if(pos==5)cout<<"ggg "<<l<<" "<<r<<" "<<i<<" "<<g[i]<<endl;
}
}
return ;
}
void pre(){
f[0][0]=1;
for(int i=1;i<=n;i++)f[i][0]=f[i-1][0]*inv2%mod,f[0][i]=f[0][i-1]*inv2%mod;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=(f[i-1][j]+f[i][j-1])*inv2%mod;
// cout<<i<<" "<<j<<" "<<f[i][j]<<endl;
}
}
return ;
}
signed main(){
// freopen("sort101.in","r",stdin);
// freopen("my.out","w",stdout);
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
inv2=po(2);
pre();
for(int i=1;i<=n;i++){
memset(g,0,sizeof g);
memset(h,0,sizeof h);
solve(1,n,i,a[i]);
int ans=0;
for(int j=1;j<=n;j++){
// if(i==1)cout<<g[j]<<" ";
ans=(ans+j*g[j])%mod;
}
cout<<ans<<" ";
}
return 0;
}
C. Cicada 拿衣服
非常神奇的一道题
首先注意对于单个数 ,但对于多个数不是这样的
用到一个性质,序列里前缀与和或的和最多变化 次(因为每一位最多一次,不可能往回变)
再观察当右端点固定时,当左端点往左延伸时, 单调不减, 单调不增,那么总的值是递减时,只会在位运算突变时断崖式上升或下降一段
那么只要将位运算值相同的区间分成一小段一小段的,然后段内进行二分即可
如果直接二分是双 的,那么考虑二分前先判断区间最大值是否满足条件,如果满足再二分,且找到后立即停止
当插入一个新值时,之前的位运算块可能会合并,这个用链表 维护即可
更新答案可以用线段树
代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n,po[25],lg[maxn*15],mx[maxn][25],mn[maxn][25],k,sum[maxn],a[maxn];
int pre[maxn],suf[maxn],l[maxn],r[maxn],hd,tl,val1[maxn],val2[maxn];
int read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=x*10+ch-48;
ch=getchar();
}
return x*f;
}
void st_pre(){
int len=lg[n];
for(int j=1;j<=len;j++){
for(int i=1;i<=n;i++){
mx[i][j]=max(mx[i][j-1],mx[i+po[j-1]][j-1]);
mn[i][j]=min(mn[i][j-1],mn[i+po[j-1]][j-1]);
}
}
return ;
}
int ask_mx(int l,int r){
int len=lg[r-l+1];
return max(mx[l][len],mx[r-po[len]+1][len]);
}
int ask_mn(int l,int r){
int len=lg[r-l+1];
return min(mn[l][len],mn[r-po[len]+1][len]);
}
struct Seg{
int l,r,mx,lazy;
}t[maxn*4];
void build(int p,int l,int r){
t[p].l=l;
t[p].r=r;
if(l==r)return ;
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
return ;
}
void change(int p,int l,int r,int w){
if(t[p].l>=l&&t[p].r<=r){
t[p].mx=max(t[p].mx,w);
return ;
}
int mid=t[p].l+t[p].r>>1;
if(l<=mid)change(p<<1,l,r,w);
if(r>mid)change(p<<1|1,l,r,w);
return ;
}
void ask(int p){
t[p].mx=max(t[p].mx,t[p>>1].mx);
if(t[p].l==t[p].r){
if(!t[p].mx)cout<<"-1 ";
else printf("%d ",t[p].mx);
return ;
}
ask(p<<1);
ask(p<<1|1);
return ;
}
void update(int pos,int w){
for(int p=tl;p!=hd;p=pre[p]){
val1[p]|=w;
val2[p]&=w;
}
for(int p=tl;p!=hd&&pre[p]!=hd;p=pre[p]){
if(val1[p]-val2[p]==val1[pre[p]]-val2[pre[p]]){
pre[suf[p]]=pre[p];
suf[pre[p]]=suf[p];
r[pre[p]]=r[p];
if(p==tl)tl=pre[p];
}
}
if(tl==hd||val1[tl]!=w||val2[tl]!=w){
tl++;
pre[tl]=tl-1;
l[tl]=r[tl]=pos;
suf[tl-1]=tl;
suf[tl]=0;
val1[tl]=val2[tl]=w;
}
else r[tl]=pos;
return ;
}
bool check(int l,int r,int val1,int val2){
return ask_mn(l,r)-ask_mx(l,r)+val1-val2>=k;
}
void todoask(int p,int pos){
int ll=l[p],rr=r[p];
while(ll<rr){
int mid=ll+rr>>1;
// cout<<ll<<" "<<rr<<endl;
if(check(mid,pos,val1[p],val2[p]))rr=mid;
else ll=mid+1;
}
change(1,ll,pos,pos-ll+1);
return ;
}
void doask(int pos){
int i=0;
for(int p=suf[hd];p;p=suf[p]){
if(check(r[p],pos,val1[p],val2[p])){
todoask(p,pos);
return ;
}
// i++;
// if(i==70)return ;
// if(pos==412)cout<<p<<" "<<tl<<" "<<suf[p]<<endl;
if(p==tl)break;
}
}
int main(){
// freopen("naive5.in","r",stdin);
// freopen("my.out","w",stdout);
n=read();
k=read();
memset(mn,0x3f,sizeof mn);
for(int i=1;i<=n;i++){
a[i]=read();
mx[i][0]=mn[i][0]=a[i];
}
po[0]=1;
for(int i=1;i<=20;i++){
po[i]=po[i-1]*2;
for(int j=po[i-1];j<=po[i]-1;j++)lg[j]=i-1;
}
st_pre();
build(1,1,n);
// cout<<"hhh"<<endl;
for(int i=1;i<=n;i++){
update(i,a[i]);
doask(i);
// cout<<i<<endl;
}
ask(1);
cout<<endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】