\(\color{white}{\mathbb{荷花映日,莲叶遮天,名之以:残荷}}\)


今天再次翻车掉出前十

开题看错 \(t1\) 以为操作2的值固定发现是个简单题,然后 \(t2\) 开始大力 \(dp\) 两个小时还是过不了样例(后来发现是尽头情况处理错了),回头看 \(t1\) 发现看错题,重新想+写半个小时,最后二十分钟打了 \(t3\) 暴力,又开始调 \(t2\),直到结束也没有一个靠谱的输出

实践证明要先打好暴力(今天唯一得的分全是暴力分……)

难度判断失误,留给 \(t3\) 时间过少,而实际上 \(t3\) 更容易得到更高的分数


A. Dove 打扑克

由于每次合并都会减少一堆,所以哪怕最终每一堆个数都不一样,最多只有 \(\sqrt{n}\) 个

所以可以得出结论不同大小的堆的个数最多 \(\sqrt{n}\) 个

那么把这些存进数组里,只要保证每次操作是根号的,就可以保证在 \(m\sqrt{n}\) 的复杂度完成

统计答案时,可以用双指针维护,配合后缀和预处理,还可以保证根号复杂度


B. Cicada 与排序

对于每一个数处理其最终在每个位置的概率,再乘位置即可算出期望

设 \(g[i]\) 表示这个树到 \(i\) 位置的概率

考虑模拟归并排序的过程进行递归(只递归当前数该去的半个区间)

这样需要维护上一层向这一层的 \(g\) 数组的转移

首先维护一个 \(f\) 辅助 \(dp\) 值,\(f[i][j]\) 表示排序合并左右区间的时候左边的选到第 \(i\) 个数,此时右边选到第 \(j\) 个数的概率,这个很好转移,\(f[i][j]=(f[i-1][j]+f[i][j-1])/2\) 即可

考虑 \(g\) 通过 \(f\) 进行转移

对于 \(g[i+j]\),如果从 \(f[i][j]\) 转移会有一个问题:不能保证当前时刻选的是左区间的点

所以应该改为 \(f[i-1][j]/2\)

这样还有一个问题,如果右边已经到了尽头,那么其实左边向下一个移动的概率不再是 \(\frac{1}{2}\) 而变成了 \(1\)

那么相当于 \(\sum f[k][j-1]/2\)

这样总复杂度是 \(n^3\) 的

代码实现
#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 拿衣服

非常神奇的一道题

首先注意对于单个数 \(OR-AND=XOR\),但对于多个数不是这样的

用到一个性质,序列里前缀与和或的和最多变化 \(logn\) 次(因为每一位最多一次,不可能往回变)

再观察当右端点固定时,当左端点往左延伸时,\(max\) 单调不减,\(min\) 单调不增,那么总的值是递减时,只会在位运算突变时断崖式上升或下降一段

那么只要将位运算值相同的区间分成一小段一小段的,然后段内进行二分即可

如果直接二分是双 \(log\) 的,那么考虑二分前先判断区间最大值是否满足条件,如果满足再二分,且找到后立即停止

当插入一个新值时,之前的位运算块可能会合并,这个用链表 \(O(1)\) 维护即可

更新答案可以用线段树

代码实现
#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;
}

\(\color{white}{\mathbb{小荷才露尖尖角,早有蜻蜓立上头。}}\)

noip模拟36的更多相关文章

  1. 2021.8.11考试总结[NOIP模拟36]

    T1 Dove玩扑克 考场并查集加树状数组加桶期望$65pts$实际$80pts$,考后多开个数组记哪些数出现过,只扫出现过的数就切了.用$set$维护可以把被删没的数去掉,更快. $code:$ 1 ...

  2. NOIP 模拟 $36\; \rm Cicada 与排序$

    题解 \(by\;zj\varphi\) 设 \(rk_{i,j}\) 表示第 \(i\) 个数最后在相同的数里排第 \(j\) 位的概率. 转移时用一个 \(dp\),\(dp_{i,j,0/1}\ ...

  3. Noip模拟36 2021.8.11

    刚题的习惯还是改不了,怎么办??? T1 Dove打扑克 考场上打的动态开点线段树+并查集,考后发现自己像一个傻子,并查集就行.. 这几天恶补数据结构疯了 用树状数组维护后缀和,$siz_i$表示编号 ...

  4. NOIP模拟 36

    又是sb错误丢rank1... T1加了一句特判,暴涨80分... 要不要这么残忍...我暴力其实打的很满的好吗QAQ T1 暴力写成$while(lim[j].id==i)$少写的特判是$(j< ...

  5. NOIP 模拟 $36\; \rm Cicada 拿衣服$

    题解 \(by\;zj\varphi\) 发现右端点固定时,左端点的 \(min-max\) 单调递减,且对于 \(or\) 和 \(and\) 相减,最多有 \(\rm2logn\)个不同的值,且相 ...

  6. NOIP 模拟 $36\; \rm Dove 打扑克$

    题解 \(by\;zj\varphi\) 引理 对于一个和为 \(n\) 的数列,不同的数的个数最多为 \(\sqrt n\) 证明: 一个有 \(n\) 个不同的数的数列,和最小就是 \(n\) 的 ...

  7. 「题解」NOIP模拟测试题解乱写II(36)

    毕竟考得太频繁了于是不可能每次考试都写题解.(我解释个什么劲啊又没有人看) 甚至有的题目都没有改掉.跑过来写题解一方面是总结,另一方面也是放松了. NOIP模拟测试36 T1字符 这题我完全懵逼了.就 ...

  8. NOIP模拟赛-2018.11.6

    NOIP模拟赛 今天想着反正高一高二都要考试,那么干脆跟着高二考吧,因为高二的比赛更有技术含量(我自己带的键盘放在这里). 今天考了一套英文题?发现阅读理解还是有一些困难的. T1:有$n$个点,$m ...

  9. Nescafe #29 NOIP模拟赛

    Nescafe #29 NOIP模拟赛 不知道这种题发出来算不算侵权...毕竟有的题在$bz$上是权限题,但是在$vijos$似乎又有原题...如果这算是侵权的话请联系我,我会尽快删除,谢谢~ 今天开 ...

随机推荐

  1. web知识架构思维导图

    图片双击放大还是很清晰的.原图大小5.1M

  2. noip模拟测试22

    考试总结:这次考试题,有好多部分分,导致了我在考试过程中一心想拿到这些部分分,对于正解没有留出时间进行思考,这是一个教训,在以后的考试中我一定要留出足够的思考时间,不要被部分分限制.还有,我的部分分也 ...

  3. 2019.06.28 MERGE INTO备忘

    --保存主表 MERGE INTO dbo.DeliveryReceiving AS t USING @ReceiveMainDt AS s ON t.Id=s.id WHEN MATCHED THE ...

  4. Java流程控制04——Switch选择结构

    switch 多选择结构 switch case 语句判断一个变量与一系列值中某个值是否相等,每个支撑位一个分支. switch语句中的变量类型可以是: byte short int 或者 char ...

  5. Java_classpath

    Java_classpath 什么是classpath? classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class. 因为Java是编译型语言,源码文件是.java,而编译后的 ...

  6. python爬虫:了解JS加密爬取网易云音乐

    python爬虫:了解JS加密爬取网易云音乐 前言 大家好,我是"持之以恒_liu",之所以起这个名字,就是希望我自己无论做什么事,只要一开始选择了,那么就要坚持到底,不管结果如何 ...

  7. Linux 并发服务器编程(多进程)

    文章目录 说明 注意事项 server.c client.c 运行截图 说明 在Linux中通过流式套接字编程(TCP),实现一个并发服务器的访问回显,适合刚学完Linux套接字编程的朋友进行巩固训练 ...

  8. Mysql中的Join详解

    一.Simple Nested-Loop Join(简单的嵌套循环连接) 简单来说嵌套循环连接算法就是一个双层for 循环 ,通过循环外层表的行数据,逐个与内层表的所有行数据进行比较来获取结果,当执行 ...

  9. SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解

    前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Sprin ...

  10. NOIP 模拟 $25\; \rm string$

    题解 \(by\;zj\varphi\) 考虑对于母串的每个字符,它在匹配串中有多少前缀,多少后缀. 设 \(f_i\) 表示 \(i\) 位置匹配上的前缀,\(g_i\) 为后缀,那么答案为 \(\ ...