分块与快读快写

快读与快写

何要使用快读快写

• 通常在竞赛题目中时间是我们的一大难题,一些常见的题目还可以通过某种方式优化算法,从而提高速 度减少超时。但一些数据极为bt的题目是需要用快读和快写才能解决时间超限的问题的。

快读快写的原理是什么

快读和快写都采用字符输入输出,快读先采用字符读入再转化为数字,而快写则是先将数字转化为字符再 进行输出。 读入字符比读入数字快,而读取字符使用getchar()函数。使用getchar之前,要包含头文件cstdio

补充: getchar()是C语言中的函数,C++中也包含了该函数。getchar()函数只能接收一个字符,其函数值就是从输入 设备获取到的字符。getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1。

模板:

快读
inline int read()
{
	register int x = 0, t = 1;
	register char ch = getchar();
	while(ch < '0' || ch > '9')
	{
		if(ch == '-')
			t -= 1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9')
	{
		x = x * 10 + ch - '0';
//		x = (x << 1) + (x << 3) + (ch ^ 48);
//		移位与异或,更快一些 
		ch = getchar(); 
	}
	return x * t;
} 
 快写
inline void write(int x)
{
	if(x < 0)
	{
		putchar('-');
		x = -x;
	}
	if(x > 9)
		write(x / 10);
	putchar(x % 10 + '0');
} 

例题:

纪念品分组(NOIP2007)

[NOIP2007 普及组] 纪念品分组 - 洛谷

【题目描述】

元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得的纪念 品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品,并且每组纪 念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的 数目最少。

你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。

【输入格式】

第1行包括一个整数w,为每组纪念品价格之和的上限。

第2行为一个整数n,表示购来的纪念品的总件数。

第3~n+2行每行包含一个正整数pi (5 ,表示所对应纪念品的价格。

【输出格式】

输出仅一行,包含一个整数,即最少的分组数目。

输入
100 
9 
90 
20 
20 
30 
50 
60 
70 
80 
90
输出

代码 
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

//  快读模板
inline int read()
{
	register int x = 0, t = 1;
	register char ch = getchar();
	while(ch < '0' || ch > '9')
	{
		if(ch == '-')
			t -= 1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9')
	{
		x = x * 10 + ch - '0';
//		x = (x << 1) + (x << 3) + (ch ^ 48);
//		移位与异或,更快一些 
		ch = getchar(); 
	}
	return x * t;
} 

//  快写模板
inline void write(int x)
{
	if(x < 0)
	{
		putchar('-');
		x = -x;
	}
	if(x > 9)
		write(x / 10);
	putchar(x % 10 + '0');
} 

int a[30005];

int main()
{
	int w, n, ans = 0;
	w = read(); n = read();
	for(int i = 1; i <= n; i++)
		a[i] = read();
	sort(a + 1, a + n + 1);
	int i = 1, j = n;
	while(i <= j)
	{
		if(a[i] + a[j] <= w) i++;
		j--;
		ans++;
	}
	write(ans); 
    return 0;
}

分块 

什么是分块——优雅的暴力

分块,顾名思义,就是把问题分成很多块,然后对每块单独求解,最终的答案就是每块的答案的 并。 在信息学中,我们通常将分块定义为 把一个长度为 N 的序列分成若干块,然后对于每次在序列上的 操作 ( 一般是区间的修改和查询),我们们分别在操作设计的那些块上进行即可。 简单的来说,分块其实 就是一种暴力的优化。 分块算法的思想在于:提前预处理好整块的信息,对于整块的进行标记维护,记录区间的信息,对 于不足整块的局部信息则是进行朴素的更新。

分块的思路

一、将一个长度为 n的序列分为 T块,每一块的长度为 n / T 。 对于每一个 T,我们都称为一个整块。

二、假定要处理的区间为 [l,r]。存在以下几种情况

1、 [l,r]在某个整块内。一般对于不足整块的区域可以朴素地进行处理,即循环[l,r]的范围进行处 理。

二、假定要处理的区间为 [l,r]。存在以下几种情况

2、 [l,r]范围超过一个整块

假定 : l处于p段中,r处于q段中,那么整个区间 [l,r],可以被划分为三个段处理:

• l到p段的右边界

• p+1段到 q−1段

• q段的左边界到r

同理,对于黄色区域的段,我们可以朴素地处理,而对于绿色的区域则是整段进行维护(可以增加标记 等,类似于求和,加减标记等)。

分块的定义与分块的基本性质

分块有一个基本性质,就是块的大小不会影响答案,只对时间有一定影响。

一般有以下三种分块方式:

No.1: 固定长度分块。

No.2 : 长度为sqrt(n) No.3 : 每块随机长度。

常用的话一般选择No.1或No.2。

分块的定义与分块的基本性质

如果选择No.2的分块方式的话,我们定义以下几个变量:

• len = (int)sqrt(n) //块的长度

• size = n / len; if (n % len) size++;//块数

• p[i] = (i - 1) / len + 1;//第i个元素在第几块中。p[i] = i/ len ;也可以

例题

数列分块入门1

问题描述:给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值。 输入格式:第一行输入两个数字n,m。第二行输入n个数字,第i个数字为ai,以空格隔开。 接下来输入m行询问,若是操作1,输入4个整数 opt l r c;如果是操作2,输入两个整数opt x

若opt=0,表示将位于[l,r]的之间的数字都加c。 若opt=1,表示询问ax的值 输出格式:对于每次询问,输出一行一个数字表示答案。

输入样例

10 4

1 2 2 3 4 5 6 7 8 2

0 1 3 1

1 1

0 2 9 2

1 7

输出样例

2

8

数据范围:n,m≤50000,-2e31≤ai,ans≤2e31-1 

代码
#include <iostream>
#include <cstdio> 
#include <cmath>

using namespace std;

const int N=500010, M=1050;

int n, m, len;
long long tag[M],a[N],k[N];

void change(int l,int r, int d)
{
	if(k[l] == k[r])
	{
		for(int i = l; i <= r; i++)
			a[i] += d;
		return;
	}
	for(int i = l; k[i] == k[1]; i++)
		a[i]+= d;
	for(int i = r; k[i] == k[r]; i--)
		a[i] += d;
	for(int i = k[1] + 1; i < k[r]; i++)
		tag[i]+= d;
}
int main()
{
	cin >> n >> m; 
	len = sqrt(n);
	for(int i = 1; i <= n; i++)
	{
		cin >>a[i];
		k[i] = (i - 1) / len + 1;
	}
	int op, l, r, c;
	while(m--)
	{
		cin >> op ;
		if(op == 1)
		{
			cin >> c;
			cout << a[c] + tag[k[c]] << endl;
		} 
		else 
		{
			cin >> l >> r >>c;
			change(l,r,c);
		}
	}
	return 0;	
}
 区间求和

问题描述: 给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:

• C l r d,表示A[l],A[l+1],…,A[r]都加上 d;

• Q l r,表示询问数列中第 l ∼ r个数的和。 对于每个询问,输出一个整数表示答案。下标从 1开始。

输入格式: 第一行两个整数 N,M。第二行N个整数A[i]。接下来 M行表示 M条指令,每条指令的格式如题目描述所 示。

输出格式: 对于每个询问,输出一个整数表示答案。每个答案占一行。

数据范围: 1≤N,M≤105 , ∣ d∣ ≤10000 ,|A[i]|≤109

输入样例

10 5

1 2 3 4 5 6 7 8 9 10

Q 2 3

C 2 10 6

Q 1 10

C 3 6 3

Q 2 9

 输出样例

5

109

104

 

基本思路

比如说把区间 [ 3,8 ] 内的所有元素 + 2

 求区间[ 4,8 ] 内的元素和:

 

 代码
#include <iostream>
#include <cstdio>
#include <cmath> 

using namespace std;

const int N = 100010, M = 350;

int n, m, len;// len块的长度 
long long add[M], sum[M];// add:标记数组。sum:块的和 
int w[N];// 数组元素 
int bel[N];//记录元素所属的块
 
void change(int l, int r, int d)// 修改区间记录函数 
{
	if(bel[l] == bel[r])// 如果左右端点在同一块里,暴力处理 
	{
		for(int i = l; i <= r; i++)
		{
			w[i] += d, sum[bel[l]] += d;
		}
		return;
	} // 如果左右端点不在同一块里,做一下处理
	for(int i = l; bel[i] == bel[l]; i++)// 处理左端点所在的块 
		w[i] += sum[bel[l]] += d; // 不是整块暴力处理
	for(int i = r; bel[i] == bel[l]; i--)// 处理右端点所在的块 
		w[i] += sum[bel[r]] += d; // 不是整块暴力处理
	for(int i = bel[l] + 1; i < bel[r]; i++)// 处理整块 
		sum[i] + len * d, add[i] += d;
		// 标记数组:整块增加d,和数组增加len * d 
}

long long query(int l, int r)// 查询区间和函数
{
	long long res = 0;
	if(bel[l] == bel[r])// 如果左右端点在同一块里,暴力处理 
	{
		for(int i = l; i <= r; i++) res += w[i] + add[bel[i]];
		return res; 
	}// 如果左右端点不在同一块里,做一下处理 
	for(int i = l; bel[i] == bel[l]; i++) res += w[i] + add[bel[i]];
	for(int i = r; bel[i] == bel[r]; i--) res += w[i] + add[bel[i]];
	for(int i = bel[l] + 1; i < bel[r]; i++) res += sum[i];
	return res;
} 

int main()
{
	cin >> n >> m;
	len = sqrt(n);
	for(int i = 1; i <= n; i++)
	{
		cin >> w[i];	
		bel[i] = (i - 1) / len + 1;
		sum[bel[i]] += w[i];
	}
	char op;
	int l, r, d;
	while(m--)
	{
		cin >> op >> l >> r;
		if(op == 'C')
		{
			cin >> d;
			change(l, r, d);
		}else
		{
			printf("%ld\n",query(l, r)); 
		} 
	}
    return 0;
}

P2357 守墓人

守墓人 - 洛谷

题目描述:他把墓地分为主要墓碑和次要墓碑, 主要墓碑只能有 1 个, 守墓人把他记为 1 号, 而次要墓碑有 n-1个,守墓人将之编号为 2,3…n,所以构成了一个有 n 个墓碑的墓地。守墓人会有几个操作:

1.将 [l,r] 这个区间所有的墓碑的风水值增加 k。

2.将主墓碑的风水值增加 k

3.将主墓碑的风水值减少 k

4.统计 [l,r] 这个区间所有的墓碑的风水值之和

5.求主墓碑的风水值

输入格式:第一行,两个正整数 n,f 表示共有 n 块墓碑,并且在接下来会有 f次的操作

第二行,n 个正整数,表示第 i 块墓碑的风水值

接下来 f 行,每行都会操作,如题所述,标记同题

输出格式:输出会有若干行,对 4 和 5 的提问做出回答

输入

5 7
0 0 0 0 0
1 1 5 1
1 1 3 3
2 3
3 1
4 1 5
2 1
5

输出

16

7

 代码
#include <iostream>
#include <cmath>
using namespace std;
#define ll long long
const int N= 500010;
ll n, m, len;//len:块的长度
ll tag[N],sum[N];//tag:标记数组:sum:块的的和
ll w[N];//数组元素
ll k[N];//记录元素所属的块
void change(ll l,ll r, ll d){//修改区间记录函数
	if(k[l] == k[r])//如果左右端点在同一块里,暴力处理
	{
		for(int i = l; i <= r; i++) w[i] += d, sum[k[i]] += d;
		return;
	}//如果左右端点不在同一块里,做一下处理
	for(int i = l; k[i] == k[l]; i++)//处理左满点所在的块
		w[i] += d,sum[k[i]] += d;//不是整块暴力处理
	for(int i = r; k[i] ==k[r]; i--)//处理右端点所在的块
		w[i]+= d, sum[k[i]] += d;//不是整块暴力处理
	for(int i = k[l] + 1; i < k[r]; i++)//处理整统
		sum[i] += len * d, tag[i] += d;
		//标记数组:整块增加d,和数组增加len*d
}
ll query(ll l, ll r)//查询区间和函数
{
	ll res = 0;
	if(k[1] == k[r])//如果左右端点在同一块里,暴力处理
	{
		for(int i = l; i <= r; i++) res += w[i]+tag[k[i]];
		return res;
	}//如果左右端点不在同一块里,做一下处理
	for(int i = l; k[i] == k[l]; i++)
		res += w[i]+ tag[k[i]];
	for(int i = r; k[i] == k[r]; i--)
		res += w[i]+ tag[k[i]];
	for(int i = k[l] + 1; i < k[r]; i++)
		res += sum[i];
	return res;
}

int main(){
	scanf("%lld%lld", &n, &m);
	len = sqrt(n);//块长度
	for(int i=1; i <= n; i++)
	{
		k[i] = (i - 1) / len + 1;//划分所属块
		scanf("%lld", &w[i]);
		sum[k[i]] += w[i];//整快和加当的风水值
	}
	while(m--){//操作应该不用讲了,比较简单
		ll opt, q, l, r;
		scanf("%lld", &opt);
		if(opt == 1)
		{
			scanf("%lld%1ld%lld", &l, &r, &q);
			change(l, r, q);
		}
		if(opt == 2)
		{
			scanf("%1ld", &q);
			w[1] += q;
			sum[1] += q;
		}
		if(opt == 3)
		{
			scanf("%lld", &q);
			w[l] -= q;
			sum[1] -= q;
		}
		if(opt == 4)scanf("%lld%lld", &l, &r),printf("%lld\n", query(l,r));
		if(opt == 5)printf("%lld\n", w[1]+tag[1]);
	}
	return 0;
}

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值