noip模拟36
\(\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的更多相关文章
- 2021.8.11考试总结[NOIP模拟36]
T1 Dove玩扑克 考场并查集加树状数组加桶期望$65pts$实际$80pts$,考后多开个数组记哪些数出现过,只扫出现过的数就切了.用$set$维护可以把被删没的数去掉,更快. $code:$ 1 ...
- NOIP 模拟 $36\; \rm Cicada 与排序$
题解 \(by\;zj\varphi\) 设 \(rk_{i,j}\) 表示第 \(i\) 个数最后在相同的数里排第 \(j\) 位的概率. 转移时用一个 \(dp\),\(dp_{i,j,0/1}\ ...
- Noip模拟36 2021.8.11
刚题的习惯还是改不了,怎么办??? T1 Dove打扑克 考场上打的动态开点线段树+并查集,考后发现自己像一个傻子,并查集就行.. 这几天恶补数据结构疯了 用树状数组维护后缀和,$siz_i$表示编号 ...
- NOIP模拟 36
又是sb错误丢rank1... T1加了一句特判,暴涨80分... 要不要这么残忍...我暴力其实打的很满的好吗QAQ T1 暴力写成$while(lim[j].id==i)$少写的特判是$(j< ...
- NOIP 模拟 $36\; \rm Cicada 拿衣服$
题解 \(by\;zj\varphi\) 发现右端点固定时,左端点的 \(min-max\) 单调递减,且对于 \(or\) 和 \(and\) 相减,最多有 \(\rm2logn\)个不同的值,且相 ...
- NOIP 模拟 $36\; \rm Dove 打扑克$
题解 \(by\;zj\varphi\) 引理 对于一个和为 \(n\) 的数列,不同的数的个数最多为 \(\sqrt n\) 证明: 一个有 \(n\) 个不同的数的数列,和最小就是 \(n\) 的 ...
- 「题解」NOIP模拟测试题解乱写II(36)
毕竟考得太频繁了于是不可能每次考试都写题解.(我解释个什么劲啊又没有人看) 甚至有的题目都没有改掉.跑过来写题解一方面是总结,另一方面也是放松了. NOIP模拟测试36 T1字符 这题我完全懵逼了.就 ...
- NOIP模拟赛-2018.11.6
NOIP模拟赛 今天想着反正高一高二都要考试,那么干脆跟着高二考吧,因为高二的比赛更有技术含量(我自己带的键盘放在这里). 今天考了一套英文题?发现阅读理解还是有一些困难的. T1:有$n$个点,$m ...
- Nescafe #29 NOIP模拟赛
Nescafe #29 NOIP模拟赛 不知道这种题发出来算不算侵权...毕竟有的题在$bz$上是权限题,但是在$vijos$似乎又有原题...如果这算是侵权的话请联系我,我会尽快删除,谢谢~ 今天开 ...
随机推荐
- web知识架构思维导图
图片双击放大还是很清晰的.原图大小5.1M
- noip模拟测试22
考试总结:这次考试题,有好多部分分,导致了我在考试过程中一心想拿到这些部分分,对于正解没有留出时间进行思考,这是一个教训,在以后的考试中我一定要留出足够的思考时间,不要被部分分限制.还有,我的部分分也 ...
- 2019.06.28 MERGE INTO备忘
--保存主表 MERGE INTO dbo.DeliveryReceiving AS t USING @ReceiveMainDt AS s ON t.Id=s.id WHEN MATCHED THE ...
- Java流程控制04——Switch选择结构
switch 多选择结构 switch case 语句判断一个变量与一系列值中某个值是否相等,每个支撑位一个分支. switch语句中的变量类型可以是: byte short int 或者 char ...
- Java_classpath
Java_classpath 什么是classpath? classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class. 因为Java是编译型语言,源码文件是.java,而编译后的 ...
- python爬虫:了解JS加密爬取网易云音乐
python爬虫:了解JS加密爬取网易云音乐 前言 大家好,我是"持之以恒_liu",之所以起这个名字,就是希望我自己无论做什么事,只要一开始选择了,那么就要坚持到底,不管结果如何 ...
- Linux 并发服务器编程(多进程)
文章目录 说明 注意事项 server.c client.c 运行截图 说明 在Linux中通过流式套接字编程(TCP),实现一个并发服务器的访问回显,适合刚学完Linux套接字编程的朋友进行巩固训练 ...
- Mysql中的Join详解
一.Simple Nested-Loop Join(简单的嵌套循环连接) 简单来说嵌套循环连接算法就是一个双层for 循环 ,通过循环外层表的行数据,逐个与内层表的所有行数据进行比较来获取结果,当执行 ...
- SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解
前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Sprin ...
- NOIP 模拟 $25\; \rm string$
题解 \(by\;zj\varphi\) 考虑对于母串的每个字符,它在匹配串中有多少前缀,多少后缀. 设 \(f_i\) 表示 \(i\) 位置匹配上的前缀,\(g_i\) 为后缀,那么答案为 \(\ ...