算法基础--个人全注释模板 排序 前缀和差分 高精度
通过题目要求直到标准代码的复杂度
64MB=1e7 int
1.1e5 o nlogn
2.1e6 o n
3.1e3 o n^2
n≤30n≤30, 指数级别, dfs+剪枝,状态压缩dp
n≤100n≤100 => O(n3)O(n3),floyd,dp,高斯消元
n≤1000n≤1000 => O(n2)O(n2),O(n2logn)O(n2logn),dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford
n≤10000n≤10000 => O(n∗n√)O(n∗n),块状链表、分块、莫队
n≤100000n≤100000 => O(nlogn)O(nlogn) => 各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树
n≤1000000n≤1000000 => O(n)O(n), 以及常数较小的 O(nlogn)O(nlogn) 算法 => 单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O(nlogn)O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa
n≤10000000n≤10000000 => O(n)O(n),双指针扫描、kmp、AC自动机、线性筛素数
n≤109n≤109 => O(n√)O(n),判断质数
n≤1018n≤1018 => O(logn)O(logn),最大公约数,快速幂,数位DP
n≤101000n≤101000 => O((logn)2)O((logn)2),高精度加减乘除
n≤10100000n≤10100000 => O(logk×loglogk),k表示位数O(logk×loglogk),k表示位数,高精度加减、FFT/NTT
结构体string 按照数字大小排序
bool cmp(node &a,node &b){
if(a.data.size()!=b.data.size()) return a.data.size()>b.data.size();//位数
return a.data>b.data; //数字大在前面 参照字典序
}
快速排序nlogn 数据约有序快排越慢
思路
1.找分界点x
2.调整区间对x嘴边都是小于的数,右边都是大于的数
可以这么做 开两个额外数组a放小于的数 b放大于的数 然后合并
优美方法:双指针 同时放中间走 i发现大于和等于x数停下来 j同理 当i和j同时i停下来就交换然后继续走 直到i和j相遇位置
3.递归处理
#include <iostream>
using namespace std;
const int N=1e6+10;
int n,q[N];
void qs(int q[] ,int l,int r) {
if(l>=r) return ;//当只有一个数或者没有数的时候退出去
int x=q[(l+r)/2],i=l-1,j=r+1;//x是任意取的值 i是左边界的前面一位,j是右边界的后面一个位置
//放置死循环的 x表示的数一定不能是下面递归所取得的数的同一边
//,就是当下面是j的时候不可以学上面取这个r,而是另外一边l
while(i<j) {
do i++;
while(q[i]<x);//左指针往右找第一个大于x的数
do j--;
while (q[j]>x);// 右指针往左找第一个小于x的数
if(i<j) swap(q[i],q[j]);//当停下的时候必然是都到了应该到达的位置
}
qs(q,l,j);//左边进行递归,j可以换成i-1
qs(q,j+1,r);//
}
}
//对于取值,当数据都一样取随机值可能超时,当数据已经排好取左边界或者右边界相当于没有取,所以取中间最稳。
快速选择算法 n 通过知道区间的个数来选择一边进行递归
#include <iostream>
using namespace std;
const int N = 100010;
int q[N];
int quick_sort(int q[], int l, int r, int k) {
if (l >= r) return q[l];
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j) {
while (q[++i] < x);//上面的优化
while (q[--j] > x);
if (i < j) swap(q[i], q[j]);
}
if (j - l + 1 >= k) return quick_sort(q, l, j, k);
else return quick_sort(q, j + 1, r, k - (j - l + 1));
//明确下标:l=0--j=5 之间一共有6个数 r是下标8 找第7个数就是在右半边也就是j+1的位置上 又因为j-l+1==6代表包括j左边有几个数 ,而所以k-(j-l+1)==1就是第一个数,而递归又是从下标j+1 开始的 下一轮下标l=6 j=7 r=8 l到j之间有j-i+1==2个数 k=1在左边就刚好
//如果一开始找的k是第8个数 那时的k-(j-l+1)==3 那么就刚好在右边满足条件
}
int main() {
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
cout << quick_sort(q, 0, n - 1, k) << endl;
return 0;
}
归并排序 只有一个辅助存变量 而在原图上把数拿出来
const int N = 1e5+10;int a[N],temp[N];
void ms(int a[],int l,int r){
//边界
if(l>=r) return ;
//mid 划分区间,递归处理
int mid=l+r>>1;
ms(a,l,mid);ms(a,mid+1,r);
//比较
int k=0, i=l,j=mid+1;
while(i<= mid&& j<=r){
if(a[i]<=a[j]) temp[k++]=a[i++];
else temp[k++]=a[j++];
}
//清扫剩饭
while(i<=mid) temp[k++]=a[i++];
while(j<=r) temp[k++]=a[j++];
//还回去
for(int i=l,j=0;i<=r;i++,j++) a[i]=temp[j];//关键一步 最长的 将i从左端l j从0开始
}
归并做 逆序对数量
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
typedef long long LL;
int a[N],t[N];
LL ms(int a[],int l,int r){
if(l>=r) return 0;
int mid=l+r>>1;
LL res=ms( a,l,mid)+ms(a,mid+1,r);
int i=l,j=mid+1,k=0;
while( i<=mid &&j<=r){
if(a[i]<=a[j]) {
t[k++]=a[i++];
}else {
t[k++]=a[j++];
res+=mid-i+1;//计算在两边区间的时候,因为通过上面两个子区间有序 左边x大于右边的时候 左边x右边到mid的也会大 根据规律相加即可
}
}
while(i<=mid) t[k++]=a[i++];
while (j <=r ) t[k++]=a[j++];
for (int i = l,j=0; i<=r; i ++,j++ ) a[i]=t[j];
return res;
}
int main()
{ int n;
cin>>n;
for (int i = 0; i < n; i ++ ) cin>>a[i];
cout<<ms(a, 0,n-1 )<<endl;
return 0;
}
前缀和 注意从下标1到n
1.一维 约等于数列的前n项和
基本操作
for(int i=1;i<=n;i++) b[i]+=b[i-1]//将一个序列变成自己的前缀和
或者
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
2.二维 重在找到推到公式 推导s[] 和 问题需要的答案
查询
如果输入的左上角的坐标 需要x1-1 就跟前缀和一样(坐标上那个点是需要的 不能减去)
如果输入的只有距离r 就要x2-r就好了
例题:子矩阵的和 :计算本点到左上角某点的和
for(int i=1;i<=n;i++){//计算s
for(int j=1;j<=m;j++){
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];//本点左边的那一块到左上角和本点上边一角到左上角的距离加上本点-减去重复计算过一次的本点左上角的s[i-1][j-1]
}
}
while(q--){int x1,y1,x2,y2;//输出二维的前缀和
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]); //注意x和y 1与2
// 最大的一块减去这一块左边的部分到再减去上边的部分最后加上重复减去的左上方的区域
}
}
拆分
对一个序列上的某区间进行操作 加减数
给定区间[l ,r ],让我们把a数组中的[ l, r]区间中的每一个数都加上c,即 a[l] + c , a[l+1] + c , a[l+2] + c ,,,,,, a[r] + c;
可以把这个序列想象为前缀和区间
构造另一个为子区间 只需要在b[i] 和b[j]这两个点分别加上一个c和减去一个c 上面的前缀和区间就实现了加某个数的操作(on->o1大大减少时间复杂度)
核心代码
for(int i=1;i<=n;i++){
b[i]=a[i]-a[i-1];//定义,拆分
}
下面也可以,而且更常用不需要找到公式insert
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;//注意是r+1
}
for(int i=1;i<=n;i++){
insert(i,i,a[i]);//这个操作可以得出a[i]的变化趋势,即拆分
//看成一开始为0,通过每个数字加上各自的a[i]变成b[i]
}
using namespace std;
const int N=1e6+10;
int a[N],b[N];
int n,m;
void insert(int l,int r,int c){
b[l]+=c;
b[r+1]-=c;//注意是r+1
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
insert(i,i,a[i]);//这个操作可以得出a[i]的变化趋势,即拆分
}
int l,r,c;
while(m--){
scanf("%d%d%d",&l,&r,&c);
insert(l,r,c);
}
for(int i=1;i<=n;i++){
b[i]+=b[i-1];
}
for(int i=1;i<=n;i++){
printf("%d ",b[i]);
}
}
二维拆分
#include<iostream>
using namespace std;
int n,m,q;
const int N=1e3+10;
int a[N][N],b[N][N];
void insert(int x1,int y1,int x2,int y2,int c){
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
int main(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
insert(i,j,i,j,a[i][j]);//b的初始话
}
}
while(q--){
int x1,y1,x2,y2,c;
cin>>x1>>y1>>x2>>y2>>c;
insert(x1,y1,x2,y2,c);//操作
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];//求和
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("%d ",b[i][j]);
}cout<<endl;
}
return 0;
}
前缀和应用:
1.公牛摄影 找到一个区间使得区间里的所有数的和为0,快速求区间和。有需要找到最长的,所以最好找当右边固定的是l在最左边的位置。
对一个区间左端点l和右端点r,算sr-s(l-1)
但后面算距离x的时候算的是xr-xl下标不对应,很麻烦,数据范围是1e9。所以需要哈希映射。
所以需要新建一个左前缀和sl`不包含xl,使用右前缀和sl包含xl,来减
点击查看代码
#include<iostream>
#include<algorithm>
#include <unordered_set>
#include<cstring>
using namespace std;
const int N = 100010;
#define x first
#define y second //当使用到pair时候的常用操作
typedef pair<int ,int >PII;//用于存储两只牛的属性 ->位置,种类,并且使用PII来表示方便
PII q[N];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;char str[2];
scanf("%d%s",&x,str);//这里读入格式是字符串%s而不是%c来读入一个字符c是为了防止比赛的输入不标准,
if(*str=='G') q[i]={x,1};
else q[i]={x,-1}; //pair使用需要大括号
}
sort(q+1,q+1+n);
unordered_map<int ,int >hash;
int last=0,res=0,sum=0;//使用last表示起点的位置,res表示最大的长度,sum表示前缀和
for(int i=1;i<=n;i++){//遍历找最长的连续和
if(!hash.count(sum)) hash[sum]=q[i].x;//没计算到一个前n项和,进行查询,如果之前没有这条sum的记录就放入,这个点的x坐标。
sum+=q[i].y;//进行前n项和的增加
if(hash.count(sum)) res=max(res,q[i].x-hash[sum]);//如果之前右这条前n项和sum的记录说明找到了一段和为0的区间,找到答案,并将他和最大值进行比较
//贪心只记录第一次出现的记录,而不是两条记录
if(i==1||q[i].y!=q[i-1].y) last=q[i].x;//双指针last和i ,当是第一个点或者本次牛的种类和上一个牛的种类不一样的时候,更新last
res=max(res,q[i].x-last);//遍历到这个点就更新这个点
}
cout<<res;
return 0;
}
}
高精度
需要将字符串逆着存入数组
保持add函数 a总是最大的
需要增加一个变量int t 能能加乘的时候进行加和乘 能减的时候当减顺便借位 (i<b.size())
vector 存c的值
main函数里面三个for是( size()-1 ; ;i--)
加减乘函数里面的for(i=0;;i++) 除是从高位开始
1.加
都需要
#include<iostream>
#include<cstring>
#include <vector>
using namespace std;
vector<int> add(vector<int>&a,vector<int>& b){
if(a.size()<b.size()) return add(b,a);
//a永远是最大的
vector<int>c;int t =0;
for(int i=0;i<a.size();i++){
//t=t+a[i]+b[i];这么写会让因为没有判断b[i]没有会造成越界
t+=a[i];
if(i<b.size()) t+=b[i];
c.push_back(t%10);
t/=10;
}
if(t) c.push_back(t);
return c;
}
int main(){
string st1,st2; //654321 123
cin>>st1>>st2;//字符串
int la=st1.length(),lb=st2.length();
vector<int >a,b;
for(int i=la-1;i>=0;i--){
a.push_back(st1[i]-'0');//字符串转int型vector
}
//
for(int j=lb-1; j>=0;j--){
b.push_back(st2[j]-'0');//123456
}
auto c=add(a,b);
for(int i=c.size()-1;i>=0;i--){
cout<<c[i];
}//从后往前输出
return 0;
}
乘:低精度把b看乘一个整数 而不是一位位的乘
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> mul(vector<int>&a ,int b){
vector<int>c;
int t=0;
for(int i=0;i<a.size()||t;i++){
if(i<a.size()) t+=a[i]*b;//这里别忘了 只有能乘才乘
c.push_back(t%10);
t/=10;
}
while(c.size()>1&&c.back()==0) c.pop_back;//去除前导0,当然倒着输出的,所以前导0在vector后面;
return c;
}
int main()
{
string s1;
cin>>s1;
vector<int>a;int b;
cin>>b;
if(b==0) {
printf("0");
return 0;
}//特判
for (int i = s1.size()-1; i >=0 ; i -- ){
a.push_back(s1[i]-'0');
}
auto c=mul(a,b);
for (int j = c.size()-1; j >= 0; j -- ){
cout<<c[j];
}
return 0;
}
高精*高精
#include <iostream>
#include <vector>
using namespace std;
vector<int> mul(vector<int> &A, vector<int> &B) {
vector<int> C(A.size() + B.size(), 0); // 初始化为 0,且999*99最多 5 位
for (int i = 0; i < A.size(); i++)
for (int j = 0; j < B.size(); j++)
C[i + j] += A[i] * B[j];
//先乘 把结果放到c里面 后面再做进位操作
int t = 0;
for (int i = 0; i < C.size()||t; i++) {
if(i<c.size() ) t += C[i];
C[i] = t % 10;
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back(); // 当数字位数大于1,就需要删去
return C;
}
int main() {
string a, b;
cin >> a >> b; // a = "1222323", b = "2323423423"
vector<int> A, B;
for (int i = a.size() - 1; i >= 0; i--)
A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--)
B.push_back(b[i] - '0');
auto C = mul(A, B);
for (int i = C.size() - 1; i >= 0; i--)
cout << C[i];
return 0;
}
减法
#include <iostream>
#include <vector>
using namespace std;
bool cmp(vector<int> &A, vector<int> &B)
{
if (A.size() != B.size()) return A.size() > B.size();
for (int i = A.size() - 1; i >= 0; i -- )//从最后一位开始看
if (A[i] != B[i])
return A[i] > B[i];
return true;
}
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
if (i < B.size()) t -= B[i];//能减才减
C.push_back((t + 10) % 10);//可能剪出了负数 要变成正数
if (t < 0) t = 1;//小于0 让下一位-1
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
string a, b;
vector<int> A, B;
cin >> a >> b;
for (int i = a.size() - 1; i >= 0; i -- ) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i -- ) B.push_back(b[i] - '0');
vector<int> C;
if (cmp(A, B)) C = sub(A, B);
else C = sub(B, A), cout << '-';
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
cout << endl;
return 0;
}
除法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> chu(vector<int> a,int k,int &r){
vector<int >c;
r=0;
for (int i = a.size()-1; i >=0; i -- ){//除法是从大数位开始的 所以从最后一位开始
r=r*10+a[i];
c.push_back(r /k );
r%=k;
}
reverse(c.begin(),c.end());//因为输出需要从size()-1开始 所以这里reverse为了和其他输出保持一致
while(c.size()>1&&c.back()==0) c.pop_back();
return c;
}
int main()
{
string s1;
int k;
cin >> s1>>k;
vector<int>a,b;
for (int i = s1.size()-1; i >=0; i -- ){
a.push_back(s1[i]-'0');
}
vector<int>c;
int r=0;
c=chu( a, k, r);
for (int i = c.size()-1; i >=0; i -- )
cout << c[i];
cout << endl<<r<<endl;
return 0;
}
差分
增减序列:https://www.acwing.com/problem/content/description/102/
每次给任意一个区间所有数+1或者-1,求多少次操作后,所有数相等
差分数组中每个数表示:相邻两数之间的高度差
但是按解题的思路应该是最小值加上其差值 虽然从结果上和max是一样的。
先让b[i]都变成同符号(p,q)的min操作 这样就可以 在一个大区间上甚至2-n进行加减操作最大程度减少操作次数
然后剩下的就是差了 因为之前操作的min也是对2-n进行操作的 所以现在剩下的某个符号的数的差值 只剩下abs(p-q)的了
2 3 0 4 -1 差分
2 2 0 0 -1 2 0 -2 0 -1
2 2 2 2 2 或者 -1 -1 - 1 -1 -1 2 0 0 0 0 或 -1 0 0 0 0
方案数 因为最后变成同符号的数了之后 可以操作跟 b1 一样 也可以是跟 bn+1一样 次数是一样的 都是改变
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int num[maxn];
inline void init()
{
int n = 0;
cin >> n;
for(int i = 1;i <= n;++i)
cin >> num[i];
for(int i = n;i > 1;--i)
num[i] -= num[i-1];
}
inline void frond()
{
long long pos = 0,neg = 0;
for(int i = 2;i <= n;++i)
{
if(a[i] > 0) pos += a[i];
else neg -= a[i];//-一个负数 等于加负数的绝对值
}
cout << min(pos,neg) + abs(pos - neg) << endl << abs(pos - neg) + 1;//左边也等于max(p,q)
//abs(正和负的差值)
}
int main(void)
{
init();
frond();
return 0;
}
最高的牛https://www.acwing.com/problem/content/103/
已知数组中的最大数,和一些数之间的关系,求原数组每个数的最大可能值
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<iostream>
#include<set>
using namespace std;
const int N = 100010;
int a[N],b[N];
//开一个set进行判重
set<pair<int,int>>exsited;
//差分模板
void insert(int l,int r,int c,int h){
b[l] -= c;
b[r] += c;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,p,h,m;
cin>>n>>p>>h>>m;
b[1] = h;
for(int i = 1;i <= m;i++){
int l,r;
cin>>l>>r;
//判断一下左右边界大小
if(l > r)swap(l,r);
//判重判断
if(!exsited.count({l,r})){
exsited.insert({l,r});
insert(l+1,r,1,h);
}
}
//构造差分数列的前缀和
for(int i = 1;i <= n;i++)
b[i] += b[i-1];
for(int i = 1;i <= n;i++){
cout<<b[i]<<endl;
}
return 0;
}
非零段划分https://www.acwing.com/problem/content/4010/
A=[3,1,2,0,0,2,0,4,5,0,2] 中的 4 个非零段依次为 [3,1,2]、[2]、[4,5] 和 [2];
找一个数p 让序列中数小于p的所有数全部转为 0 求有多少个 非零段
-》转化为 岛问题
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 500005,M = 10000;
int a[N],b[N];
int n;
int main()
{
cin >> n;
int res = 0;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
if(a[i]>a[i-1]){
//数的大小范围在 区间[ a[i-1] , a[i]-1 ]之间的所有数都+1
b[a[i-1]]++,b[a[i]]--;
}
}
int sum = 0 ;
for (int i = 0; i <= M; i ++ ){
sum+=b[i];//求前缀和知道 每个区间的 数
res = max(res,sum);
}
cout << res;
}
岛问题 比上面的高度的数据范围比数量大的多 于是想到了用map离散化(不能用unordered_map,unordered_map无法用来求前缀和)
求高度上 贡献的最大值 就是岛屿数量
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#define y second
#define x first
typedef long long LL;
using namespace std;
const int N = 100005,M = 1e9+1;
int a[N];
map<int ,int >b;//x为高度 y为差分
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];
if(a[i]>a[i-1]){
//高度的大小在区间[ a[i-1] , a[i]-1 ]之间的所有数对山的贡献大小都+1 即差分
b[a[i-1]]++,b[a[i]]--;
}
}
LL sum = 0 ,res = 0;
for (auto i:b ){
//求前缀和
sum+=i.y;
res = max(res,sum);
}
cout << res;
}
二维前缀和
const int N = 5e3+10;//只能开这么大的数组
int s[N][N];
int n,r;
int main()
{
cin >> n>>r;
r=min(5001,r);
for (int i = 0; i < n; i ++ ){
int x,y,w;
cin >> x>>y>>w;
s[++x][++y]+=w;//多放进一个点
}
for (int i = 1; i <=5001; i ++ ){
for (int j =1 ; j <= 5001; j ++ ){
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];//加法前缀和 本点+s[i-1][j]+s[i][j-1]-s[i-1][j-1](左上角多出的);
}
}
int ans=0;
for (int i = r; i <= 5001; i ++ ){
for (int j = r; j <= 5001; j ++ ){
ans=max(ans,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]);
//在r的范围内 找到最大值的 同理于上面 减去两边 但由于 于是加上s[i-r][j-r]
}
}
cout << ans<<endl;
return 0;
}
组合数问题 https://www.acwing.com/problem/content/525/
要求
是k的倍数(取模等于0) 的个数
看成n m的矩阵 只有在c[i][j]取模k结果为0为数字才可以在前缀和变成1
include
include
include
using namespace std;
const int N = 2020;
int c[N][N];
int s[N][N];
int main()
{
int t,k;cin>>t>>k;
for (int i = 0; i < N; i ++ ){
for (int j = 0; j <= i; j ++ ){
if(!j) c[i][j]=1%k;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;
}
}
for (int i = 0; i < N; i ++ ){
for (int j = 0; j < N; j ++ ){
if(j<=i&&c[i][j]==0) s[i][j]=1;
if(i>=1) s[i][j]+=s[i-1][j];
if(j>=1) s[i][j]+=s[i][j-1];
if(i>=1&&j>=1) s[i][j]-=s[i-1][j-1];
}
}
while (t -- ){
int n,m;cin>>n>>m;
cout << s[n][m]<<endl;
}
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2020;
int c[N][N];
int s[N][N];
int main()
{
int t,k;cin>>t>>k;
for (int i = 0; i < N; i ++ ){
for (int j = 0; j <= i; j ++ ){
if(!j) c[i][j]=1%k;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%k;//需要注意 k为0
}
}
for (int i = 0; i < N; i ++ ){
for (int j = 0; j < N; j ++ ){
if(j<=i&&c[i][j]==0) s[i][j]=1;//当j小于等于i的时候 才可以算
if(i>=1) s[i][j]+=s[i-1][j];//注意边界
if(j>=1) s[i][j]+=s[i][j-1];
if(i>=1&&j>=1) s[i][j]-=s[i-1][j-1];
}
}
while (t -- ){
int n,m;cin>>n>>m;
cout << s[n][m]<<endl;
}
return 0;
}