noip模拟36

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


pic.png

今天再次翻车掉出前十

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

posted @ 2021-08-11 19:47  y_cx  阅读(78)  评论(0编辑  收藏  举报