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{小荷才露尖尖角,早有蜻蜓立上头。}}\)