Evanyou Blog 彩带

洛谷P3984-数据结构 题解

题面

这题精,真的精

前言:不要被题目背景和描述误导!


Solution:

题目大意

给定一段序列,请你做到区间修改和区间询问。

区间询问即 \(L\)\(R\) 区间内,乘上下标后取模的值在条件范围内的元素的个数

暴力方法

时间不充裕者跳过

一道题目, 正解都是由简单到困难一步一步推导出来的 。我们不如先使用暴力来做。

暴力为了优化一点,我们不妨用 差分 进行区间修改的优化

差分是什么?

比如一次需要你对 \(X\)\(Y\) 区间内的所有数加上 \(K\) ,正常都是for(int i : x~y)a[i]++,之间复杂度明显为 N。

而差分,只需要另外那一个数组,在 \(X\) 位置加上 \(K\) , 在 \(Y + 1\) 位置减去 \(K\) ,到最后询问时累加前缀和即可。

差分数组定义: 差分数组 [ i ] 即 原始数组 [ i ] - 原始数组 [ i - 1 ]

差分模板:

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

const int MAXN = 10000 + 5;//定义常量

int a[MAXN];//原始序列
int b[MAXN];//差分序列 

inline int read(){//快速读入 
    int f = 1, x = 0;
    char c = getchar();

    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }

    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }

    return f * x;
}

int main(){
	int n = read();//输入元素个数
	for(int i = 1;i <= n; i += 1){//+= 1会比 ++ 更快(某机房巨佬说的) 
		a[i] = read();
		b[i] = a[i] - a[i - 1];//初始化差分数组 
	} 
	
	int m = read();//输入操作次数
	while(m--){
		int x = read(),y = read(),z = read();
		//左边界       右边界     修改数量
		b[x] += z,b[y + 1] -= z;//差分操作 
	} 
	
	for(int i = 1;i <= n; i++){
		b[i] += b[i - 1];//累加差分数组(前缀和)
		cout<<b[i]<<" ";//输出 
	}
	
	return 0;
}

回归正题

修改操作使用差分进行优化,而查询操作就需要动态暴力枚举完成了。

暴力查询操作代码:

int query(int x,int y){
	int num = 0;
	ll B[MAXN];//临时用来累加差分数组的数组
	memset(B,0,sizeof(B));
	for(int i = 1;i <= n + 1; i++){
		B[i] = b[i] + B[i - 1];
		//cout<<B[i]<<" "; 
	}
	//cout<<"\n";
	for(int i = x;i <= y; i++){
		ll shit = MOD(B[i] * i);//取模操作需要单独处理负数情况,这里我单独用了一个函数
		//cout<<i<<" "<<shit<<"\n";
		if(shit >= Min && shit <= Max) num++;
	}
	return num;
}

我们再认真读一遍题,会发现,前面的 Q 次操作是动态的(由于数组会修改),所以我们

不得不暴力查询。但是后面那只“非三次元仓鼠”的询问时静态的(数组不会变了)。

所以:

我们只要预处理前 i 位有多少个符合要求的元素即可!

正解思路总结:

首先对于前面 Q 次操作,使用差分进行修改,暴力进行查询。后面 \(F\) 次静态查询通过预处理后 O(1) 复杂度即可求出答案。 注意:该题对于long long的要求极其之严格,需要大家注意!

AC代码:

#include<bits/stdc++.h>//潇洒的万能头 
#define ll long long//懒人必备 
using namespace std;

const int MAXN = 80000 + 5;//定义常量 

int n,q,Min,Max;//如题所述,Q为操作次数 
ll mod;//mod的值 
ll b[MAXN];//差分数组 
ll a[MAXN];//预处理数组 

inline ll read(){//快速读入 
    ll f = 1, x = 0;
    char c = getchar();

    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }

    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }

    return f * x;
}

ll MOD(ll xxx){//单独处理负数取模 
	if(xxx > 0)return xxx % mod;
	return -1 * ((-xxx) % mod);
}

int query(int x,int y){//暴力询问 
	int num = 0;//累加器 
	ll B[MAXN];//临时用来累加差分数组的数组
	memset(B,0,sizeof(B));
	for(int i = 1;i <= n + 1; i++){
		B[i] = b[i] + B[i - 1];//累加前缀和 
		//cout<<B[i]<<" "; 
	}
	//cout<<"\n";
	for(int i = x;i <= y; i++){
		ll shit = MOD(B[i] * i);
		//cout<<i<<" "<<shit<<"\n";
		if(shit >= Min && shit <= Max) num++;//合法则累加 
	}
	return num;
}

int main(){//开始了 
//	freopen("dis.out","w",stdout);
	n = read(),q = read(),mod = read(),Min = read(),Max = read();//快速读入 
	for(int i = 1;i <= q; i++){//循环操作次数 
		char c;
		cin>>c;//读入操作 
		if(c == 'A'){//如果是区间修改 
			int x = read(),y = read();//读入左边界和右边界 
			ll z = read();//读入修改的值 
			b[x] += z,b[y + 1] -= z;//差分数组 O(1) 修改 
		}
		if(c == 'Q'){//如果是询问 
			int x = read(),y = read();//入左边界和右边界 
			cout<<query(x,y)<<"\n";//输出答案 
		}
	}
	ll F = read();//“非三次元仓鼠”静态操作 
	for(int i = 1;i <= n + 1; i++){ 
		b[i] += b[i - 1];//先累加差分的前缀和 
	}
	for(int i = 1;i <= n; i++){
		ll shit = MOD(b[i] * i);
		if(shit >= Min && shit <= Max) a[i]++;//合法则累加 
		a[i] += a[i - 1];//预处理前缀和 
	}
	while(F--){//循环 
		int x = read(),y = read();//读入左边界和右边界 
		cout<<a[y] - a[x - 1]<<"\n";//输出 
	}
	return 0;//end
}
posted @ 2019-10-28 14:33  御·Dragon  阅读(204)  评论(0编辑  收藏  举报



Contact with me