洛谷 P4688 [Ynoi2016]掉进兔子洞 (看到题目背景,galgame玩家狂喜)

  • 洛谷 P4688 [Ynoi2016]掉进兔子洞

题解背景

我一直以为自己只是凭本能行为的生物,如今,通过和外界的接触,才形成了现在的我

海德格尔在《艺术作品的本源》中写道:

“世界决不是立身于我们面前能让我们细细打量的对象。只要诞生与死亡、祝福与惩罚不断地使我们进

入存在,世界就始终是非对象性的东西,而我们人始终归属于它。”

世界是美丽的,每次从这里眺望街区,都会产生这种的错觉,但是没办法,美丽的事物终归是美丽的,

澄澈透丽的夏日天空,透露一言难以形容的优美

盛开的樱花树下,埋藏着尸体。

樱ノ诗的根基在于梦蝶的虚无。

樱ノ诗与梦蝶。

绽放与虚无。

世界现象的一体两面。

你还真的是个樱一般的艺术家呢!无论多少次,你都能够像是理所当然的风景一般令奇迹之花绽放。正

因为像是理所当然一般的绽放,人们丝毫不能察觉那是奇迹……哪怕花瓣散落……那棵树也会再次开出

花朵……巨大的树干深深地扎根于大地,向着高高的天空不断地生长……所以随着每次的花落,树干会

更粗,树枝会更高,树根会刺入更深的大地。这件事只有圭君知道……所以他才会画出那种画。超越极

限……

题目描述

你有一天正在写题,突然看到了这道题,一看题目背景,素晴日啊!就想

起了还没打完的樱之诗,然后就开始颓,打起了galgame

突然觉得不能这么颓,就写起了题解。

题目链接

题目分析

针对这类区间询问,而且区间之间可以互相转移的题,很明显是需要用莫队解决的,题目一次给出我们三个区间

我们需要用三个区间的总长度减去这三个区间的交集,首先考虑三个区间求交集怎么做,首先可以暴力维护,用数组存储每一个区间内

每一种颜色出现的次数,然后依次求交即可,但很明显,这种写法无论是时间还是空间都是过不去的,如何快速且方便获取区间交集?

我们发现但观察一下区间特征,假设每个数只出现一次,我们可以发现用bool数组存储+&运算似乎符合求交集的性质,但每个数并非只出

现一次,考虑一个数$ai (,针对每一个询问区间,我们维护一个bool数组,扫一遍询问区间,)ai$每在询问出

现一次,我们就把(ai+cnt-1)标为1,(cnt)(ai)目前出现的次数,这样就可以用bool数组储存询问,利用&就可以求交了,针对这类

询问区间可以互相转移的问题,我们一般采取莫队来求解,(ai)的范围过大,我们需要将其离散化,用bool很明显空间还是太大了,所以

就采用bitset来存储。

最终做法就是bitset存储,莫队维护,就可以得到答案了。

我们再观察一下数据范围,发现需要开1e10的bitset,即使是bitset也是

开不下的,所以把询问分组进行了。

时间复杂度就是普通莫队的时间复杂度

(O((n+m)sqrt{n}))

其余细节在代码里.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<bitset>
#include<cmath>
#define int long long
using namespace std;
const int maxn=1e5+100;
const int mm=35000+100;
const int mn=35000;
inline int read(){
	int ret=0;
	int f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')
			f=-f;
		ch=getchar();
	}
	while(ch<='9'&&ch>='0'){
		ret=ret*10+(ch^'0');
		ch=getchar();
	}
	return f*ret;
}
int n,m;
int a[maxn];
int bl[maxn];
int pos[maxn];
struct node{
	int id;
	int l,r;
}q[maxn*3+10];
int cnt[maxn];
bitset<maxn> b[mm],bs;
bool vis[mm];
int ans[maxn];
bool cmp(node x,node y){
	return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];//排序函数,与普通莫队的排序函数一样 
}
void add(int x){
	cnt[a[x]]++;
	bs[a[x]+cnt[a[x]]-1]=1;
	return ;
}
void del(int x){
	cnt[a[x]]--;
	bs[a[x]+cnt[a[x]]]=0;
	return ;
}
void slove(int k){
	memset(vis,false,sizeof(vis));
	memset(cnt,0,sizeof(cnt));
	memset(ans,0,sizeof(ans));
	int tot=0;
	for(int i=1;i<=k;i++){
		for(int j=1;j<=3;j++){
			q[++tot].id=i;//读入 
			q[tot].l=read();
			q[tot].r=read();
//			cout<<q[tot].l<<" "<<q[tot].r<<endl;
			ans[i]+=q[tot].r-q[tot].l+1;
		}
	}
	bs.reset(); 
	sort(q+1,q+1+tot,cmp);//排序 
	int l=1,r=0;//初始化左右指针 
	for(int i=1;i<=tot;i++){//分区间处理
		//注意处理顺序,不能让l到r的前面,cnt会变成负数,导致bitset越界 
		while(r<q[i].r){
			add(++r);
		}
		while(l<q[i].l){
			del(l++);
		}
		while(l>q[i].l){
			add(--l);
		}
		while(r>q[i].r){
			del(r--);
		}
		if(!vis[q[i].id]){
			b[q[i].id]=bs;//如果该区间未出现过
			vis[q[i].id]=true;	
		}
		else{
			b[q[i].id]&=bs;//利用&求交集 
		}
	}
	for(int i=1;i<=k;i++){
		ans[i]-=b[i].count()*3;//减去交集 
	}
	for(int i=1;i<=k;i++){
		cout<<ans[i]<<endl;//输出对应答案 
	}
	return ;
}
signed main(){
//	freopen("a.in","r",stdin);
	n=read();
	m=read();
	int si=sqrt(n); 
	for(int i=1;i<=n;i++){
		a[i]=read();
		bl[i]=a[i];
	} 
	int nu=ceil((double)n/si);
	for(int i=1;i<=nu;i++){
		for(int j=(i-1)*si+1;j<=i*si;j++){ 
			pos[j]=i;
			if(j>=n)
				break;
		} 
	}
    /*也可以这样分块
	for(int i=1;i<=n;i++){
		a[i]=read();
		bl[i]=a[i];
		pos[i]=(i-1)/si+1;
	} */
	sort(bl+1,bl+1+n);
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(bl+1,bl+1+n,a[i])-bl;//注意,不用去重 
	} 
	while(114514){//bitset开不下,所以分组执行 
		if(m<=mn){
			slove(m);
			break;
		}
		else{
			m-=mn; 
			slove(mn);
		}
	}
	return 0;
} 

完结撒花

说实话,做这道题的原因就是看见了这特殊的题目背景,想写一篇特殊的

题解,既然lxl在题目描述用素晴日,那我就在题解描述

用樱之诗了。

原文地址:https://www.cnblogs.com/rpup/p/14022727.html