【LG-P5072 [Ynoi2015]】盼君勿忘

做的第一道莫队题 QwQ。

P5072 [Ynoi2015] 盼君勿忘

给定一个序列,每次查询一个区间 [ l , r ] [l,r] [l,r] 中所有子序列分别去重后的和   m o d     p \bmod\ p mod p

思路

莫队 + 手写双向链表

1

题意有点绕,但注意是“分别去重”。

那么假设一个数 x x x,它在长为 n n n 的序列中出现了 m m m 次,即一共有 2 n 2^n 2n 个子序列,不含 x x x 的子序列有 2 n − m 2^{n-m} 2nm 个。

x x x 在所有去重后的子序列中出现了 ( 2 n − 2 n − m ) (2^n-2^{n-m}) (2n2nm) 次,那么 x x x 对答案的贡献为 x ∗ ( 2 n − 2 n − m ) x * (2^n-2^{n-m}) x(2n2nm)

2

所以问题转化为记录在区间 [ l , r ] [l,r] [l,r] x x x 一共出现了几次。

莫队。

同时,因为要维护出现次数为 m m m 的数,所以还要手写一个双向链表(不手写会 TLE)。然后就是莫队板子了。

3

如果用快速幂去计算 2 的若干次方的话,由于 n n n m m m 很大,就容易超时。

所以要使用光速幂。光速幂在计算固定底数、指数很大的幂时有很高的效率。

预处理 O(n) \text{O(n)} O(n),计算该底数的 1 , 2 , 3 , ⋯   , n 1,2,3,\cdots ,\sqrt{n} 1,2,3,,n 次幂和 2 n ,   3 , n ,   ⋯   , n n 2\sqrt{n},\ 3,\sqrt{n},\ \cdots ,n\sqrt{n} 2n , 3,n , ,nn 次幂。

查询 O(1) \text{O(1)} O(1) 2 x 2^x 2x 即为 p o w 1 ( x % l e n ) ∗ p o w 2 ( ⌊ x l e n ⌋ )   ( l e n = ( n ) ) pow1(x \% len) * pow2(\left\lfloor\frac{x}{len}\right\rfloor)\ (len=\sqrt(n)) pow1(x%len)pow2(lenx) (len=( n))

代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define maxn 100005
int n, m, len, tot;
int a[maxn], cnt[maxn], sum[maxn], ans[maxn];
int pw1[maxn], pw2[maxn];

inline int get(int x)
{
	return (x - 1) / len + 1;
}

struct relist{
	struct node2{
		int pre, nxt;
	}d[maxn];
	int tot;
}lst;
struct node{
	int l, r, p, id;
	friend bool operator < (const node &a, const node &b)
	{
		return get(a.l) == get(b.l) ? a.r < b.r : get(a.l) < get(b.l);
	}
}q[maxn];

inline void power_pre(int mod)
{
	pw1[0] = pw2[0] = 1;
	for(int i = 1; i <= len + 3; ++i) pw1[i] = pw1[i - 1] * 2 % mod;
	for(int i = 1; i <= len + 3; ++i) pw2[i] = pw2[i - 1] * pw1[len] % mod; 	
}

inline int qry(int k, int mod)
{
	return pw1[k % len] % mod * pw2[k / len] % mod;
}

inline void era(int x)
{
	if(x != tot)
	{
		lst.d[lst.d[x].nxt].pre = lst.d[x].pre;
		lst.d[lst.d[x].pre].nxt = lst.d[x].nxt;	
	}
	else
	{
		lst.d[lst.d[x].pre].nxt = 0;
		tot = lst.d[x].pre;
	}
	lst.d[x].pre = lst.d[x].nxt = 0;
}

inline void add_back(int x)
{
	lst.d[tot].nxt = x, lst.d[x].pre = tot;
	tot = x; 
}

inline void updt(int x, int tim)
{
	if(!(sum[cnt[a[x]]] -= a[x]))
		era(cnt[a[x]]);
	if(!sum[cnt[a[x]] += tim])
		add_back(cnt[a[x]]);
	sum[cnt[a[x]]] += a[x];
}

signed main()
{
	scanf("%lld %lld", &n, &m);
	for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
	len = ceil(sqrt(n));
	for(int i = 1; i <= m; ++i)
	{
		scanf("%lld %lld %lld", &q[i].l, &q[i].r, &q[i].p);
		q[i].id = i;	
	}	
	sort(q + 1, q + m + 1);
	for(int i = 1, l = 1, r = 0; i <= m; ++i)
	{
		power_pre(q[i].p);
		while(l > q[i].l) l -= 1, updt(l, 1);
		while(r < q[i].r) r += 1, updt(r, 1);
		while(l < q[i].l) updt(l, -1), l += 1;
		while(r > q[i].r) updt(r, -1), r -= 1;//注意这四个的顺序不能改变 
		for(int j = lst.d[0].nxt; j; j = lst.d[j].nxt)
		{
			int totl = qry(r - l + 1, q[i].p);
			int disc = qry(r - l + 1 - j, q[i].p);
			int res = ((totl - disc) * sum[j] + q[i].p) % q[i].p;
			ans[q[i].id] = (ans[q[i].id] + res + q[i].p) % q[i].p;
		}
	}
	for(int i = 1; i <= m; ++i) printf("%lld\n", ans[i]);
	return 0;
}

—— E n d End End——

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值