洛谷P5072 [YNOI2015]盼君勿忘 莫队+unordered_set+毒瘤卡常

在太阳西斜的这个世界里,置身天上之森。等这场战争结束之后,不归之人与望眼欲穿的众人, 人人本着正义之名,长存不灭的过去、逐渐消逝的未来。我回来了,纵使日薄西山,即便看不到未来,此时此刻的光辉,盼君勿忘。————世界上最幸福的女孩

珂朵莉最可爱了,珂朵莉的题最毒瘤了qwq

题目链接:传送门

这是一个自带大常数选手被毒瘤 l x l lxl lxl卡常数,从开 O 2 O2 O2 82 82 82分到不开 O 2 O2 O2就珂 A C AC AC的故事……

先上图QWQ

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解题思路

一开始没看懂题……“子序列”是不需要连续的……
序列上一段区间内的问题,不带修改, n ≤ 100000 n\leq 100000 n100000又是YNOI的题,容易想到莫队。
发现每次大力去重显然复杂度爆炸,又因为 a i &lt; = 1 0 5 a_i&lt;=10^5 ai<=105,因此考虑每个数对答案的贡献。
考虑区间 [ l , r ] [l,r] [l,r]。显然这个区间的子序列总数为 2 r − l + 1 2^{r-l+1} 2rl+1
假设一个数 x x x在区间 [ l , r ] [l,r] [l,r]中出现了 k k k次,那么不含 x x x的子序列总个数为 2 r − l + 1 − k 2^{r-l+1-k} 2rl+1k
所以 x x x对答案的贡献为 x ∗ ( 2 r − l + 1 − 2 r − l + 1 − k ) x*(2^{r-l+1}-2^{r-l+1-k}) x(2rl+12rl+1k)
发现大莉统计答案也会爆,所以开一个set(这里为了减小复杂度,用了unordered_set)来保存所有出现过的出现过的次数。
也就是说,比如若 [ l , r ] [l,r] [l,r]中有一个数出现了 1 1 1次,有一个数出现了 3 3 3次,那么 s e t set set里就保存了 1 , 3. 1,3. 1,3.
然后每次就珂以大莉莫队,修改在set上乱改一波,询问大莉统计就珂以了qwq
时间复杂度 O ( m n l o g n ) O(m\sqrt{n}logn) O(mn logn) (莫队 O ( m n ) O(m\sqrt{n}) O(mn ),快速幂 O ( l o g n ) O(logn) O(logn)
……
…………
………………
TLE???
掐指一算, m ∗ n ∗ l o g n m*\sqrt{n}*logn mn logn差不多为 1 0 5 ∗ 300 ∗ 16 = 4.8 ∗ 1 0 8 10^5*300*16=4.8*10^8 10530016=4.8108……
珂能其他题卡一卡就过去了,但是毒瘤的YNOI题肯定是不会放过这种错误时间复杂度过的qwq
于是考虑优化。

优化过程

首先把 n \sqrt n n 优化掉不太现实(不然lxl就不会把n,m只开到1e5),因为莫队必然会带 n \sqrt n n 的复杂度qwq。
所以考虑把快速幂的 log ⁡ n \log n logn的复杂度去掉(以下想法参考这个题解):
发现如果把 2 1 , 2 2 , 2 3 , . . . , 2 n − 1 2^1,2^2,2^3,...,2^{\sqrt n-1} 21,22,23,...,2n 1 2 n , 2 2 n , . . . , 2 n 2^{\sqrt n},2^{2\sqrt n},...,2^n 2n ,22n ,...,2n预处理出来,
就珂以把一个 2 x 2^x 2x表示成 2 k n ∗ 2 q 2^{k\sqrt n}*2^q 2kn 2q,然后 O ( 1 ) O(1) O(1)求。这里 k = x / n , q = x k=x/\sqrt{n},q=x k=x/n ,q=x m o d mod mod n \sqrt n n
于是大莉写一波啊qwq
时间复杂度 O ( m n ) O(m\sqrt{n}) O(mn )
……
…………
………………
TLE?????
笑容逐渐消失.jpg
发现总是有几个点过不去……时间复杂度应该已经最优了……于是又要卡一波常数……

卡常技巧

1. 1. 1.快读快写、 r e g i s t e r register register i n l i n e inline inline(对于像我这样的大常数选手来说,这是必备的qwq)

2. 2. 2.莫队的奇偶性玄学优化(参考这篇博客qwq

3. 3. 3.减少取模次数:
假设 p w 1 [ i ] pw1[i] pw1[i]表示 2 i 2^i 2i,那么原本写的是:

pw1[i]=(pw1[i-1]<<1)%mod;

要改成:

pw1[i]=(pw1[i-1]<<1)<mod?pw1[i-1]<<1:(pw1[i-1]<<1)-mod;

实测这样珂以很有效地减小常数

4. 4. 4.减小除法与取模次数:
发现算 2 2 2的幂时还是要重复算很多次除法和取模,这里珂以把 i n \frac{i}{\sqrt n} n i i i i m o d mod mod n \sqrt n n 都预处理出来,然后直接调用qwq。
这样也珂以大减一波常数qwq

5. 5. 5. + + r ++r ++r l − − l-- l之类的东西和附近的操作合并一下,比如 a [ + + r ] a[++r] a[++r] a [ l − − ] a[l--] a[l]

提交……
终于过了QWQ
索然无味……

毒瘤代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<unordered_set>
#define re register int
using namespace std;
typedef long long ll;
typedef unordered_set<int>::iterator svi;
inline char getc() {    //优化的getchar 
    static char buf[262145],*fs,*ft;
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<18,stdin)),fs==ft)?EOF:*fs++;
}
int read() {
	re x=0,f=1;
	char ch=getc();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getc();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<1)+(x<<3)+ch-'0';
		ch=getc();
	}
	return x*f;
}
inline void write(int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=100005;
int n,m,siz,a[Size],belong[Size];
struct Query {
	int id,l,r,p;
} Q[Size];
inline bool comp(Query x,Query y) {
	if(belong[x.l]!=belong[y.l])	return x.l<y.l;
	if(belong[x.l]&1)	return x.r<y.r;
	return x.r>y.r;
}
ll pw1[Size],pw2[Size];
int MOD[Size],DIV[Size];
inline ll fastpow(int n,int mod) {
	//算 2^n %mod 的值 
	return pw1[MOD[n]]*pw2[DIV[n]]%mod;
}
int vis[Size],val[Size],ans[Size];
unordered_set<int> Union;
void add(int x) {
	if(vis[x]) {
		val[vis[x]]-=x;
		if(!val[vis[x]]) {
			Union.erase(vis[x]);
		}
	}
	if(!val[++vis[x]])	Union.insert(vis[x]);
	val[vis[x]]+=x;
}
void del(int x) {
	val[vis[x]]-=x;
	if(!val[vis[x]])	Union.erase(vis[x]);
	if(--vis[x]) {
		if(!val[vis[x]]) {
			Union.insert(vis[x]);
		}
		val[vis[x]]+=x;
	}
}
int main() {
	n=read();
	m=read();
	//发现siz好像取m/sqrt(n)时常数小一点 
	//这里取sqrt(n)好像也是珂以的qwq 
	siz=m/sqrt(n);
	for(re i=1; i<=n; i++) {
		//预处理出i/siz和i%siz的值 
		MOD[i]=i%siz;
		DIV[i]=i/siz;
	}
	for(re i=1; i<=n; i++) {
		a[i]=read();
		belong[i]=i/siz+1;
	}
	for(re i=1; i<=m; i++) {
		Q[i].l=read();
		Q[i].r=read();
		Q[i].p=read();
		Q[i].id=i;
	}
	sort(Q+1,Q+1+m,comp);
	pw1[0]=pw2[0]=1;
	int l=1,r=0;
	for(int i=1; i<=m; i++) {
		while(r<Q[i].r)	add(a[++r]);
		while(r>Q[i].r)	del(a[r--]);
		while(l<Q[i].l)	del(a[l++]);
		while(l>Q[i].l)	add(a[--l]);
		int mod=Q[i].p;
		//每次暴力O(sqrt(n))预处理2的幂 
		for(re i=1; i<=siz; i++) {
//			pw1[i]=(pw1[i-1]<<1)%mod;
			pw1[i]=(pw1[i-1]<<1)<mod?pw1[i-1]<<1:(pw1[i-1]<<1)-mod;
		}
		pw2[1]=pw1[siz];
		for(re i=2; i<=siz; i++) {
			pw2[i]=pw2[i-1]*pw2[1]%mod;
		}
		int len=Q[i].r-Q[i].l+1;
		re sum=0;
		for(svi it=Union.begin(); it!=Union.end(); it++) {
			//恶臭无比 
			sum=(sum+val[*it]*(fastpow(len,mod)-fastpow(len-(*it),mod)+mod))%mod;
		}
		ans[Q[i].id]=sum;
	}
	for(re i=1; i<=m; i++) {
		write(ans[i]);
		putchar(10);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值