XCPC 基础
对拍板子
介绍Windows环境下与Linux环境下的对拍
对拍去DEV
Windows环境
以DEV为例,首先建四个源代码到一个新建文件夹中,源代码分别命名为wa.cpp,data.cpp,violent.cpp,windows.cpp,除此之外什么也不需要添加
- 一份错误的/需要比对的代码,导入wa.cpp
- 一份暴力的/用来比对的代码,导入violent.cpp
- 一份用来造数据的代码
- 一份执行对拍程序的代码
wa.cpp
1 |
|
violent.cpp
1 |
|
data.cpp
1 |
|
windows.cpp
1 |
|
注意,以上所有程序均需要编译并运行
前缀和(Prefix Sum)
通过构建前缀和数组,从而能做到O(1)时间复杂度的区间查询(rangeQuery)
一维前缀和
构建前缀和数组
1 | vector<int>pre(n+1,0); |
rangeQuery
1 | pre[r]-pre[l-1]//求(l,r)区间和 |
二维前缀和
构建前缀和数组
1 | vector<vector<int>>pre(n+1,vector<int>(m+1)); |
rangeQuery
1 | pre[x2][y2]-pre[x2][y1-1]-pre[x1-1][y2]+pre[x1-1][y1-1]; |
差分(Difference)
通过构建差分数组,从而能做到O(1)时间复杂度的区间修改(rangeModify)
一维差分
- 单点修改,单点询问
构建差分数组
1 | vector<int>dif(n+5); |
rangeModify
1 | dif[l]+=k;dif[r+1]-=k;//(l,r)区间每个数都加k |
Query
1 | for(int i=1;i<=n;i++) b[i]+=b[i-1];//此时b[i]就是修改完后的第i个数的值 |
单点修改,区间询问
具体就是构建两个差分数组,每次单点修改的左端加1,右端也加1,全部操作执行完求前缀和,区间询问[l,r]的时候相减即可
1
2
3
4
5
6
7
8
9
10
11
12
13vector<int>L(n+10),R(n+10);
for(int i=0;i<k;i++){
int u,v;cin>>u>>v;
L[u]++;R[v]++;
}
for(int i=1;i<=n;i++) {
L[i]+=L[i-1];
R[i]+=R[i-1];
}
for(int i=1;i<=k;i++){
int l,r;cin>>l>>r;
cout<<R[r]-L[l-1]<<"\n";
}
二维差分
与一维差分不同的是,二维差分数组的构建与修改均可以通过差分函数来完成
1 | vector<vector<int>>dif(n+5,vector<int>(m+5)); |
构建差分数组
1 | for(int i=1;i<=n;i++){ |
rangeModify
1 | modify(x1,y1,x2,y2,x);//对左上角为(x1,y1),右下角为(x2,y2)的矩阵每个数都加上x |
Query
前缀和求
1 | for(int i=1;i<=n;i++){ |
间隔差分
[l,r]中间隔k个数字修改,其中r=l + k * n, 相当于[l,l+k,l+2 * k,… l + n * k]这么多数修改
构建差分数组
1 | pre[l]++;pre[r]--;//注意此时一定是最右侧的点进行减,不是最右边减一 |
求和
1 | for(int i=1;i<=sum;i++)//sum为整个数组大范围,写成这样,即使多次修改区间也能在一次遍历的时间里算完 |
高精度
版本一 太全,不实用
1 | const int MAXN = 5005;//位数 |
声明方式
1 | bign a;//除了声明外其他如整型般使用 |
版本二
STL
int128库函数自定义
int128范围是$-2^{127}-2^{127-1}$
unsigned int128范围是$0-2^{128}$,约39位数
给int128 赋初值不能直接用一个很大的数字,比如1E100,这样赋出来是LONG_LONG_MAX
可以使用字符串,然后一直累乘
1 | using i128 = __int128; |
常用库函数重载
1 | using i64 = long long; |
sqrt
对long long使用sqrt,类型转换为long double
1 | x=(long double)sqrt(x);//x为long long类型 |
pdbs
头文件及命名空间,注意编译器开g++
1 |
|
平衡二叉树常见成员函数
1 | empty() / size() |
代码
1 |
|
防卡unordered_map
1 | unordered_map<int,int>mp; |
卡时
1 | auto start=clock();//程序开头 |
1
1 | 1.find(str,pos)//找到的返回下标,找不到返回string::npos |
进制转换
10进制转其他进制(n是数字,k是进制)
大于10的数字用字符表示
1 | void solve(){ |
其他进制转十进制
1 | unordered_map<char,int>it; |
字符串进制转化
1 | stoi(s) //s需要是字符串,只能将string类型字符串转化为10进制,且不超过int范围 |
二分
左端点减1,右端点加1
基姆拉尔森计算公式
计算自1970年的某年某月某日是星期几
1 | const int d[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
三分模板
整数三分
每次都将区间变为[l,mid1],(mid1,mid2],(mid2,r]三个区间
根据条件判断将l,r指针移动至哪个区间,复杂度为log以3为底(区间大小)
1 | void solve(){ |
实数三分
1 | const double eps = 1e-9;//根据条件判断 |
Trick
比赛相关
- 认真读题至少2遍,必要时和队友确认题意
- 想思路前务必带入样例验证思路,否则请重复步骤1
- 初始化不要多次声明vector(特别是二分这种多次用到初始化的),用遍历的方式去赋初始值
- 遇到m*(n+1)/2,不要先定义k=(n+1)/2,m可能是偶数
切入点/典中典
- 子序列选择问题,暴力做法是枚举所有可能的子序列,当然这一定做不到,复杂度是$2^n$级别的
sol1 一般这类问题都可以通过dp解决,找到能够”拼接”起来的转移方程
sol2 通过组合数解决,注意应该直接计算保留的子序列方案数 not 删除的种类数
q个询问此类问题一般都是复杂度O(1)[提前预处理],O(logn)[预处理后在线询问]
区间[l,r]询问,转换成前缀和处理,即pre[r]-pre[l-1]
red求能组成多少个不同的red,只需要求每个’e’前面多少个r和后面多少个d,两者相乘即可
数论
- 将数x分开,每块最多t,分开的操作为拆成两个和为x的正整数,求次数
1 | ans += (a[i] - 1) / t; |
- 中位数就想二分
- 二进制乘以2,相当于整个数左移一位,末位补0,乘$2^{k}$就是左移k位
- 6x+8y+10z,取数不限可以遍历>=6的所有偶数
- 对于一个数组中的gcd,从左到右gcd如果递减,每次起码缩小一半(原因就是gcd一旦变化,一定会变成原有gcd的因数),所以gcd的变化是log级别的
- 递推数列具有模周期性,对于斐波那契数列,这个周期不会超过 60× 模数。
- 皮亚诺定理:模数为k的循环节长度不会超过6k
- 当题面给定数字范围可能超过long long的时候,首先应该想到按位贪心等算法,而不是高精度或i128
操作问题
- 数组中异或最终变成某个确定数的问题,可以使用前/后缀异或和的方法dp转移
- 循环移位 - 加长数组至2倍,然后直接找循环节出现的起始和结束位置
- $操作a_{i}-=1;a_{i+1}+=1$,无操作限制求极差,将使得序列变成不递减序列,二分求极大极小做差
- 操作$a_i+=x$,次数不限,最终要求到某个数/求mex…,可以用dp,dp[a[i]+x]+=(dp[$a[i]$]-1),或者对x取模,用map存储,
图论
- 有其他增项的最短路
- 有无某种特性可以点数*2为无,点数 *2+1为有
DEBUG
- 当程序连输入的数据都输出不来,就是程序中某段卡死了