快读与快写
何要使用快读快写
• 通常在竞赛题目中时间是我们的一大难题,一些常见的题目还可以通过某种方式优化算法,从而提高速 度减少超时。但一些数据极为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)
【题目描述】
元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得的纪念 品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品,并且每组纪 念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的 数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。
【输入格式】
第1行包括一个整数w,为每组纪念品价格之和的上限。
第2行为一个整数n,表示购来的纪念品的总件数。
第3~n+2行每行包含一个正整数pi (5 ,表示所对应纪念品的价格。
【输出格式】
输出仅一行,包含一个整数,即最少的分组数目。
输入
100 9 90 20 20 30 50 60 70 80 90
输出
6
代码
#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;
}