Showball 算法模板
Showball 算法整理及模板(2023)
火车头
#include<bits/stdc++.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define all(u) u.begin(), u.end()
#define endl '\n'
#define debug(x) cout<<#x<<":"<<x<<endl;
typedef pair<int, int> PII;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10, M = 105;
const int mod = 1e9 + 7;
const int cases = 0;
void Showball(){
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T=1;
if(cases) cin>>T;
while(T--)
Showball();
return 0;
}
一、基础算法
1.快速排序
分治思想+递归处理
void quick_sort(int q[], int l, int r)//分治思想
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)//保证[l,j]的值小于等于x,[j+1,r]的值大于等于x
{
do i ++ ; while (q[i] < x);//双指针
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
//递归处理
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
2.第k个数(快速选择算法:O(n) )
运用快排思想,将区间分成两部分,左部分的元素个数为\(sl\),如果\(k\leq sl\)则只需要递归左部分,反之,递归右部分,但次数变成了第\(k-sl\),时间复杂度为O(n)。
拓展:STL中有一个nth_element()函数可以直接用来求解第k小的数(常数大)。用法:nth_element(a,a+k,a+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){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
int sl=j-l+1;
if(k<=sl) return quick_sort(q,l,j,k);
else return quick_sort(q,j+1,r,k-sl);
}
3.归并排序
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
归并排序求逆序对的数量
LL merge_sort(int q[], int l, int r)
{
if (l >= r) return 0;
int mid = l + r >> 1;
LL res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else
{
res += mid - i + 1;
tmp[k ++ ] = q[j ++ ];
}
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
return res;
}
4.高精度
高精加
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); 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;
}
//用字符串读入,倒序
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');
auto C = add(A, B);
for (int i = C.size() - 1; i >= 0; i -- ) cout << C[i];
高精减
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;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
//负号处理
if (cmp(A, B)) C = sub(A, B);
else C = sub(B, A), cout << '-';
高精乘
vector<int> mul(vector<int> &A, vector<int> &B) {
vector<int> C(A.size() + B.size() + 7, 0); // 初始化为 0,C的size可以大一点
for (int i = 0; i < A.size(); i++)
for (int j = 0; j < B.size(); j++)
C[i + j] += A[i] * B[j];
int t = 0;
for (int i = 0; i < C.size(); i++) { // i = C.size() - 1时 t 一定小于 10
t += C[i];
C[i] = t % 10;
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back(); // 必须要去前导 0,因为最高位很可能是 0
return C;
}
高精除
vector<int> div(vector<int> &A, int b, 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 / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
5.前缀和与差分
一维前缀和与差分
int a[N],s[N];//前缀和数组
s[i]=s[i-1]+a[i];//s[i]表示a1+a2+...+ai
所以查询[l,r]的值之和就可以s[r]-s[l-1]
差分数组b[i]=a[i]-a[i-1]
所以对差分数组求前缀和就可以得到原数组。
差分数组一般用来维护给一段区间加上或减去一直相同的值
比如给[l,r]区间的每个值都加上c
void insert(int l, int r, int c)
{
b[l] += c;
b[r + 1] -= c;
}
构造差分数组 for(int i=1;i<=n;i++) insert(i,i,a[i]);
或者 for(int i=1;i<=n;i++) b[i]=a[i]-a[i-1];
最后对b数组求一次前缀和就可以得到结果
对原数组差分后可以发现正数之和加上负数之和等于0
拓展:
//对于1 0 0 0 0
//求两次前缀和可以得到
//1 2 3 4 5 ...
/*对于1 1 0 0 0 0
求三次前缀和可以得到
1 4 9 16 25 36 ....
*/
多项式前缀和:
/*
在数组a的l,r区间加上一个多项式
然后前缀和。
P函数表示求长度为len的数组a,cnt次前缀和
D函数表示求长度为len的数组a,cnt次差分
f函数:a数组表示多项式系数,k表示最高次项
p函数:其实为-f(x+len)
*/
LL a[N],ki[N],coef1[N],coef2[N];
int n,m,q;
void P(LL a[],int len,int cnt=1){
while(cnt--){
for(int i=1;i<=len;i++){
a[i]+=a[i-1];
if(a[i]>=mod) a[i]-=mod;
}
}
}
void D(LL a[],int len,int cnt=1){
while(cnt--){
for(int i=len;i;i--){
a[i]-=a[i-1];
if(a[i]<0) a[i]+=mod;
}
}
}
LL f(int x,LL a[],int k){
LL res=0;
LL base=1;
for(int i=k;i>=0;i--){
res+=base*a[i]%mod;
if(res>=mod) res-=mod;
base=base*x%mod;
}
return res;
}
LL g(int x,LL a[],int k,int l,int r){
return (mod-f(x+r-l+1,a,k))%mod;
}
void Showball(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++) cin>>a[i];
D(a,n,6);
int l,r,k;
while(m--){
cin>>l>>r>>k;
for(int i=0;i<=k;i++){
cin>>ki[i];
}
for(int i=1;i<=10;i++){
coef1[i]=f(i,ki,k);
coef2[i]=g(i,ki,k,l,r);
}
D(coef1,10,6);
D(coef2,10,6);
for(int i=1;i<=10;i++){
a[l+i-1]+=coef1[i];
if(a[l+i-1]>=mod) a[l+i-1]-=mod;
a[r+i]+=coef2[i];
if(a[r+i]>=mod) a[r+i]-=mod;
}
}
P(a,n,7);
while(q--){
int l,r;
cin>>l>>r;
cout<<(a[r]-a[l-1]+mod)%mod<<endl;
}
}
高阶前缀和
高维前缀和
二维前缀和与差分
S[i, j] = 第i行j列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
//本质就是右下角减去左边一列和上边一行。相交元素计算了两遍,所以再加回来一次。
生成前缀和数组
//下标从1开始
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
//二维差分
给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
前缀和是一种思想,对于可以逆向消去前一步的操作,我们都可以采用类似的思想去解决问题。
比如前缀置换。对于一个0-9的序列,我们每次操作都去交换两个数的位置,然后现在给你l和r。只让你执行l到r之间的操作。求出操作后的序列。
int n,m;
int s[N][10];
void Showball(){
cin>>n>>m;
for(int i=0;i<10;i++) s[0][i]=i;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
memcpy(s[i],s[i-1],sizeof s[i]);
swap(s[i][x],s[i][y]);
}
while(m--){
int l,r;
cin>>l>>r;
int p[10];
for(int i=0;i<10;i++) p[s[l-1][i]]=i;
for(int i=0;i<10;i++) cout<<p[s[r][i]]<<" \n"[i==9];
}
}
6.位运算
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
判断n是否为2的幂次:n&(n-1)==0?yes:no
若A^B = C,则A^C = B
若GCD(A,B) = A^B = C , 则C = A-B
经典性质:a+b=a^b+2*(a&b);
可能的解题思路:
连续与运算结果只会不变或变小,连续或结果只会不变或变大。
前缀异或和
7.二分
//整数二分
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
//实数二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
//STL
二分
lower_bound(a,a+n,m):返回数组a中,0~n里第一个大于等于m的指针
int pos=lower_bound(a,a+n,m)-a:返回数组a中,0~n里第一个大于等于m的位置
upper_bound(a,a+n,m):返回数组a中,0~n里第一个大于m的指针
int pos=upper_bound(a,a+n,m)-a:返回数组a中,0~n里第一个大于m的位置
lower_bound(seq2, seq2+6, 7, greater<int>()):加greater<int>()后,lower变小于等于,upper变小于
//二分答案
区间为[0,n-1]
int l=-1,r=n;
while(l+1!=r){
int mid=l+(r-l)>>1;
if(check(mid)) l=mid;
else r=mid;
}
看情况return l或者r.
8.离散化
// std::vector<int> a, b; // b 是 a 的一个副本
std::sort(a.begin(), a.end());
a.erase(std::unique(a.begin(), a.end()), a.end());
for (int i = 0; i < n; ++i)
b[i] = std::lower_bound(a.begin(), a.end(), b[i]) - a.begin();
9.RMQ问题--区间最大值
//ST表解决RMQ问题 询问的时候需要RMQ(l-1,r-1)
int f[N][18];
void RMQ_init(vector<int> A) {
int n = A.size();
for (int i = 0; i < n; ++i) f[i][0] = A[i];
for (int j = 1; (1 << j) <= n; ++j) // 枚举长度 2^j
for (int i = 0; i + (1 << j) - 1 < n; ++i) {
f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
int RMQ(int L, int R) {
int k = 0;
while ((1 << (k + 1)) <= R - L + 1) ++k;
return max(f[L][k], f[R - (1 << k) + 1][k]);
}
已知 \(x \oplus (s - x) = t\),其中 \(s\) 和 \(t\) 是常数。我们可以通过推导来求解 \(x\)。首先,根据异或运算的性质,我们知道 \(a \oplus a = 0\),即任何数与自身进行异或运算的结果都是 0。因此,我们可以对方程进行简化:\(x \oplus (s - x) = t\)将 \(s - x\) 表示为 \((s \oplus x)\):\(x \oplus (s \oplus x) = t\)根据异或运算的交换律,我们可以改变顺序:\((s \oplus x) \oplus x = t\)再次利用异或运算的性质,我们可以将左边简化为 \(s \oplus (x \oplus x)\),而 \(x \oplus x\) 结果是 0:\(s \oplus (x \oplus x) = t\)即:\(s \oplus 0 = t\)由于异或运算的自反性质 (\(a \oplus 0 = a\)),上述方程可以简化为:\(s = t\)因此,如果给定的方程 \(x \oplus (s - x) = t\) 成立,那么 \(s = t\)。这意味着只有在 \(s\) 和 \(t\) 相等的情况下,方程有解。总结起来,如果 \(x \oplus (s - x) = t\) 且 \(s = t\),那么方程有解,解为 \(x = s = t\)。否则,方程无解。****
二、数据结构
1.单链表
// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 在链表头插入一个数a
void insert(int a)
{
e[idx] = a, ne[idx] = head, head = idx ++ ;
}
// 将头结点删除,需要保证头结点存在
void remove()
{
head = ne[head];
}
int e[N],ne[N],idx,head;
//初始化
void init(){
head=-1;
idx=0;
}
//将x插入到头结点之后
void add_to_head(int x){
e[idx]=x;ne[idx]=head;head=idx++;
}
//将x插入到k点的后面
void add(int k,int x){
e[idx]=x;ne[idx]=ne[k];ne[k]=idx++;
}
//删除k后面的点
void remove(int k){
ne[k]=ne[ne[k]];
}
2.双链表
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
3.栈
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
4.队列
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
5.单调栈
//维护栈内元素单调
int a[N],f[N];
stack<int> s;
int n;
cin>>n;
vector<int> ans;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=n;i>=1;i--){
while(s.size()&&a[s.top()]<=a[i]) s.pop();
f[i]=s.size()?s.top():0;
s.push(i);
}
for(int i=1;i<=n;i++) cout<<f[i]<<" ";
6.单调队列
\\滑动窗口
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k, q[N], a[N];//q[N]存的是数组下标
int main()
{
int tt = -1, hh=0;//hh队列头 tt队列尾
cin.tie(0);
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i = 0; i <n; i ++) cin>>a[i];
for(int i = 0; i < n; i ++)
{
//维持滑动窗口的大小
//当队列不为空(hh <= tt) 且 当当前滑动窗口的大小(i - q[hh] + 1)>我们设定的
//滑动窗口的大小(k),队列弹出队列头元素以维持滑动窗口的大小
if(hh <= tt && k < i - q[hh] + 1) hh ++;
//构造单调递增队列
//当队列不为空(hh <= tt) 且 当队列队尾元素>=当前元素(a[i])时,那么队尾元素
//就一定不是当前窗口最小值,删去队尾元素,加入当前元素(q[ ++ tt] = i)
while(hh <= tt && a[q[tt]] >= a[i]) tt --;
q[ ++ tt] = i;
if(i + 1 >= k) printf("%d ", a[q[hh]]);
}
puts("");
hh = 0,tt = -1;
for(int i = 0; i < n; i ++)
{
if(hh <= tt && k < i - q[hh] + 1) hh ++;
while(hh <= tt && a[q[tt]] <= a[i]) tt --;
q[ ++ tt] = i;
if(i + 1 >= k ) printf("%d ", a[q[hh]]);
}
return 0;
}
7.KMP(字符串匹配)
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
例题:最小循环覆盖=n-ne[n]
扩展KMP
从s中的每一位字符开始最多可以匹配多少位的p中的字符
以线性时间复杂度求出一个字符串s和他的任意后缀的最长公共前缀长度
void exkmp(char s[],int z[]){
int L=1,R=0;
z[1]=0;
int n=strlen(s+1);
for(int i=2;i<=n;i++){
if(i>R) z[i]=0;
else{
int k=i-L+1;
z[i]=min(z[k],R-i+1);
}
while(i+z[i]<=n&&s[z[i]+1]==s[i+z[i]]){
++z[i];
}
if(i+z[i]-1>R){
L=i,R=i+z[i]-1;
}
}
z[1]=n;
}
Manachar(马拉车)
解决最长回文子串问题,思路和扩展KMP算法相似
int manacher(char s[],int p[]){
int n=strlen(s+1);
m=0;
t[++m]='$';
for(int i=1;i<=n;i++){
t[++m]=s[i];t[++m]='$';
}
int M=0,R=0;
for(int i=1;i<=m;i++){
if(i>R){
p[i]=1;
}else{
p[i]=min(p[M*2-i],R-i+1);
}
while(i-p[i]>0&&i+p[i]<=m&&t[i-p[i]]==t[i+p[i]])
++p[i];
if(i+p[i]-1>R){
M=i,R=i+p[i]-1;
}
}
int res=0;
for(int i=1;i<=m;i++){
res=max(res,p[i]);
}
return res-1;
}
线性基
ULL n;
//线性基
vector<ULL> B;
void insert(ULL x){
for(auto b:B){
x=min(x,b^x);
}
for(auto &b:B){
b=min(b,b^x);
}
if(x) B.eb(x);
}
//第k大异或
ULL kthXor(ULL k){
sort(all(B));
ULL ans=0;
if(B.size()<n) k--;
for(auto b:B){
if(k&1) ans^=b;
k>>=1;
}
return (k?-1:ans);
}
8.Trie树(字典树)
//用法1:检索字符串:判断前缀数
int son[N][65], cnt[N], idx;
char s[N];
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
int getnum(char x){
if(x>='A'&&x<='Z')
return x-'A';
else if(x>='a'&&x<='z')
return x-'a'+26;
else
return x-'0'+52;
}
// 插入一个字符串
void insert(char *str)
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = getnum(str[i]);
if (!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
cnt[p] ++ ;//求前缀数
}
cnt[p] ++ ;//前字符串数
}
// 查询字符串出现的次数
int query(char *str)
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = getnum(str[i]);
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
01-Trie树
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10,M=3100010;
int a[N],son[M][2],idx;
void insert(int x){
int p=0;
for(int i=30;~i;i--){
int &s=son[p][x>>i&1];
if(!s) s=++idx;
p=s;
}
}
int search(int x){
int p=0,res=0;
for(int i=30;~i;i--){
int s=x>>i&1;
if(son[p][!s]){
res+=1<<i;
p=son[p][!s];
}
else p=son[p][s];
}
return res;
}
int main()
{
int n;
cin>>n;
int res=0;
for(int i=0;i<n;i++) {cin>>a[i];insert(a[i]);}
for(int i=0;i<n;i++) res=max(res,search(a[i]));
cout<<res<<"\n";
return 0;
}
9.并查集
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集:
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
struct DSU{
vector<int> p, sz1, sz2;
DSU(int n) : p(n + 1), sz1(n + 1, 1), sz2(n + 1, 0){
iota(p.begin(), p.end(), 0);
}
int find(int x){
return p[x] == x ? x : p[x] = find(p[x]);
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y){
x = find(x), y = find(y);
sz2[x] += 1;
if (x == y) return false;
if (sz1[x] < sz1[y]) swap(x, y);
sz1[x] += sz1[y];
sz2[x] += sz2[y];
p[y] = x;
return true;
}
};
10.堆
手写堆一般比较少用
直接用STL优先队列当堆使用
大根堆 priority_queue<int> q;
小根堆 priority_queue<int,vector<int>,greater<int> > q
//手写堆
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;
// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a], hp[b]);
swap(h[a], h[b]);
}
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
heap_swap(u, t);
down(t);
}
}
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
heap_swap(u, u / 2);
u >>= 1;
}
}
// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
11.哈希
1.普通哈希
一般直接用map和unordered_map,注意codeferces上unordered_map会被卡的。
2.字符串哈希
就是把字符串映射到一个数上,这样便于字符串匹配
//1.自然溢出 单哈希
ULL p[N], P = 131;
ULL h[N];
ULL get(int l, int r) {
return h[r] - h[l - 1] * p[r - l + 1];
}
char str[N];
void solve() {
cin >> n >> m;
cin >> str + 1;
p[0] = 1, h[0] = 0;
for (int i = 1; i <= n; i++) {
p[i] = p[i - 1] * P;
h[i] = h[i - 1] * P + str[i] - '0' + 1;
}
while (m--)
{
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get(l1, r1) == get(l2, r2))cout << "Yes\n";
else cout << "No\n";
}
}
//2.双哈希(保险,稍慢一些)
const int mod=1e9+7,mod1=1e9+9
ll p1[N], P1 = 131, p2[N], P2 = 13331;
ll h[N], h2[N];
//乘法开ll,mod取int
int get1(int l, int r) {
return (h[r] - (h[l - 1] * p1[r - l + 1]) % mod + mod) % mod;
}
int get2(int l, int r) {
return (h2[r] - (h2[l - 1] * p2[r - l + 1]) % mod1 + mod1) % mod1;
}
char str[N];
void solve() {
cin >> n >> m;
cin >> str + 1;
p1[0] = p2[0] = 1;
for (int i = 1; i <= n; i++) {
p1[i] = (p1[i - 1] * P1) % mod;
p2[i] = (p2[i - 1] * P2) % mod1;
h[i] = ((h[i - 1] * P1) % mod + str[i] - '0' + 1) % mod;
h2[i] = ((h2[i - 1] * P2) % mod1 + str[i] - '0' + 1) % mod1;
}
while (m--)
{
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if (get1(l1, r1) == get1(l2, r2) && get2(l1, r1) == get2(l2, r2))cout << "Yes\n";
else cout << "No\n";
}
}
vector<LL> p1(n+1),p2(n+1),h1(n+1),h2(n+1);
p1[0]=p2[0]=1;
for(int i=1;i<=n;i++){
p1[i]=(p1[i-1]*P1)%mod;
p2[i]=(p2[i-1]*P2)%mod1;
h1[i]=((h1[i-1]*P1)%mod+s[i]-'0'+1)%mod;
h2[i]=((h2[i-1]*P2)%mod1+s[i]-'0'+1)%mod1;
}
auto get1=[&](int l,int r){return (h1[r]-(h1[l-1]*p1[r-l+1])%mod+mod)%mod;};
auto get2=[&](int l,int r){return (h2[r]-(h2[l-1]*p2[r-l+1])%mod1+mod1)%mod1;};
线段树维护字符串哈希 (判断是否回文)
ULL p[N];
string s;
//线段树
struct node{
int l,r;
ULL h1,h2;//正反哈希值
node operator+(const node &u)const{
node ans;
ans.l=l,ans.r=u.r;
ans.h1=h1*p[u.r-u.l+1]+u.h1;
ans.h2=u.h2*p[r-l+1]+h2;
return ans;
}
}tr[N*4];
#define ls u<<1
#define rs u<<1|1
void pushup(int u){
tr[u]=tr[ls]+tr[rs];
}
void build(int u,int l,int r){
tr[u].l=l,tr[u].r=r;
if(l==r){
tr[u].h1=tr[u].h2=s[l];
return;
}
int mid=l+r>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(u);
}
void modify(int u,int x,int v){//单点修改
if(tr[u].l==tr[u].r) tr[u].h1=tr[u].h2=v;
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(ls,x,v);
else modify(rs,x,v);
pushup(u);
}
}
node query(int u,int l,int r){//区间查询
if(l<=tr[u].l&&tr[u].r<=r){
return tr[u];
}
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(ls,l,r);
if(l>mid) return query(rs,l,r);
return query(ls,l,r)+query(rs,l,r);
}
void Showball(){
int n,q;
cin>>n>>q;
cin>>s;
s="?"+s;
p[0]=1;
for(int i=1;i<=n;i++) p[i]=p[i-1]*131;
build(1,1,n);
while(q--){
int op,x,y;
char c;
cin>>op>>x;
if(op==1){
cin>>c;
modify(1,x,c);
}else{
cin>>y;
node ans=query(1,x,y);
cout<<(ans.h1==ans.h2?"Yes\n":"No\n");
}
}
}
12.树状数组
单点修改,区间查询
O(n) 建树
void init() {
for (int i = 1; i <= n; ++i) {
t[i] += a[i];
int j = i + lowbit(i);
if (j <= n) t[j] += t[i];
}
}
void add(int x, int k) {
while (x <= n) { // 不能越界
c[x] + = k;
x + = lowbit(x);
}
}
int getsum(int x) { // a[1]..a[x]的和
int ans = 0;
while (x > 0) {
ans + = c[x];
x - = lowbit(x);
}
return ans;
}
区间修改,单点查询
区间修改用差分解决,单点查询用树状数组解决
//区间[l,r]每个数+d
先添加差分数组
add(l,d);
add(r+1,-d);
区间修改,区间查询
区间修改继续使用差分数组,区间求和则需要对差分数组的前缀和数组再求一次前缀和。通过公式推导,我们发现可以维护两个树状数组。一个是差分数组$$b[i]$$,一个是数组\(i*b[i]\)。这样共和公式为b数组的前缀和乘上(n+1)-i*b数组的前缀和。
int n,m;
LL tr1[N],tr2[N];
int a[N];
int lowbit(int x){
return x&-x;
}
void add(LL tr[],int x,LL c){
while(x<=n){
tr[x]+=c;
x+=lowbit(x);
}
}
LL getsum(LL tr[],int x){
LL res=0;
while(x>0){
res+=tr[x];
x-=lowbit(x);
}
return res;
}
LL sum(int x){
LL res=getsum(tr1,x)*(x+1)-getsum(tr2,x);
return res;
}
void Showball(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
int b=a[i]-a[i-1];
add(tr1,i,b);
add(tr2,i,(LL)i*b);
}
while(m--){
string op;
int l,r,d;
cin>>op>>l>>r;
if(op=="C"){
cin>>d;
add(tr1,l,d);add(tr1,r+1,-d);
add(tr2,l,(LL)l*d);add(tr2,r+1,(LL)(r+1)*(-d));
}
else cout<<sum(r)-sum(l-1)<<endl;
}
}
应用:求剩余区间第k小,并且删除这个数。
树状数组+二分
void Showball() {
cin>>n;
for(int i=2;i<=n;i++) cin>>h[i];
for(int i=1;i<=n;i++) add(i,1);
for(int i=n;i;i--){
int k=h[i]+1;
int l=1,r=n;
while(l<r){
int mid=l+r>>1;
if(getsum(mid)>=k) r=mid;
else l=mid+1;
}
ans[i]=l;
add(l,-1);
}
for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
}
可以用树状数组来维护状态数组从而解决一些问题。
13.线段树
//线段树
struct node{
int l,r;
LL sum;
LL tag;
}tr[N*4];
#define ls u<<1
#define rs u<<1|1
void pushup(node &u,node &l,node &r){
u.sum=l.sum+r.sum;
}
void pushup(int u){
pushup(tr[u],tr[ls],tr[rs]);
}
void addtag(node &u,LL tag){
}
void pushdown(int u){
addtag(tr[ls],tr[u].tag);
addtag(tr[rs],tr[u].tag);
tr[u].tag=0;
}
void build(int u,int l,int r){
tr[u].l=l,tr[u].r=r;
tr[u].tag=0;
if(l==r){
tr[u].sum=w[r];
return;
}
int mid=l+r>>1;
build(ls,l,mid),build(rs,mid+1,r);
pushup(u);
}
void modify(int u,int x,int v){//单点修改
if(tr[u].l==x&&tr[u].r==x) tr[u]={x,x,v,v,v,v};
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(ls,x,v);
else modify(rs,x,v);
pushup(u);
}
}
void modify(int u,int l,int r,LL tag){//区间修改
if(l<=tr[u].l&&tr[u].r<=r) {
addtag(tr[u],tag);
return;
}
int mid=tr[u].l+tr[u].r>>1;
pushdown(u);
if(l<=mid) modify(ls,l,r,tag);
if(r>mid) modify(rs,l,r,add,tag);
pushup(u);
}
int query(int u,int l,int r){//区间查询
if(l<=tr[u].l&&tr[u].r<=r){
return tr[u].sum;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
LL sum=0;
if(l<=mid) sum+=query(ls,l,r);
if(r>mid) sum+=query(rs,l,r);
return sum;
}
int query(int u,int x){//单点查询
if(tr[u].r<x||tr[u].l>x) return 0;
if(tr[u].l==tr[u].r) return tr[u].sum;
pushdown(u);
return query(ls,x)+query(rs,x);
}
区间和
struct node{
int l,r;
int sum;
}tr[4*N];
int n,m;
int w[N];
void pushup(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void build(int u,int l,int r){
if(l==r) tr[u]={l,r,w[r]};
else{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
}
int query(int u,int l,int r){
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
int mid=tr[u].l+tr[u].r>>1;
int sum=0;
if(l<=mid) sum+=query(u<<1,l,r);
if(r>mid) sum+=query(u<<1|1,l,r);
return sum;
}
void modify(int u,int x,int v){
if(tr[u].l==x&&tr[u].r==x) tr[u].sum+=v;
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
区间最大子段和
int n,m;
int w[N];
struct Node{
int l,r;
int tmax;//最大字段和
int lmax;//最大前缀
int rmax;//最大后缀
int sum;//区间和
}tr[N*4];
void pushup(Node &u,Node &l,Node &r){
u.sum=l.sum+r.sum;
u.tmax=max({l.tmax,r.tmax,l.rmax+r.lmax});
u.lmax=max(l.lmax,l.sum+r.lmax);
u.rmax=max(r.rmax,r.sum+l.rmax);
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l==r) tr[u]={l,r,w[r],w[r],w[r],w[r]};
else{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int x,int v){
if(tr[u].l==x&&tr[u].r==x) tr[u]={x,x,v,v,v,v};
else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
Node query(int u,int l,int r){
if(l<=tr[u].l&&tr[u].r<=r) return tr[u];
else{
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r);
if(l>mid) return query(u<<1|1,l,r);
else{
Node res;
auto left=query(u<<1,l,r);
auto right=query(u<<1|1,l,r);
pushup(res,left,right);
return res;
}
}
}
void Showball(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>w[i];
}
build(1,1,n);
int k,a,b;
while(m--){
cin>>k>>a>>b;
if(k==2) modify(1,a,b);
else {
if(a>b) swap(a,b);
cout<<query(1,a,b).tmax<<endl;
}
}
}
区间GCD
LL w[N];
int n,m;
struct Node{
int l,r;
LL sum,d;
}tr[N*4];
LL gcd(LL a,LL b){
return b?gcd(b,a%b):a;
}
void pushup(Node &u,Node &l,Node &r){
u.sum=l.sum+r.sum;
u.d=gcd(l.d,r.d);
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l==r){
LL b=w[r]-w[r-1];
tr[u]={l,r,b,b};
}else{
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
pushup(u);
}
}
void modify(int u,int x,LL v){
if(tr[u].l==x&&tr[u].r==x) {
LL b=tr[u].sum+v;
tr[u]={x,x,b,b};
}else{
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) modify(u<<1,x,v);
else modify(u<<1|1,x,v);
pushup(u);
}
}
Node query(int u,int l,int r){
if(l>r) return (Node){0,0,0,0};
if(l<=tr[u].l&&tr[u].r<=r) return tr[u];
else{
int mid=tr[u].l+tr[u].r>>1;
if(r<=mid) return query(u<<1,l,r);
else if(l>mid) return query(u<<1|1,l,r);
else{
auto left=query(u<<1,l,r);
auto right=query(u<<1|1,l,r);
Node res;
pushup(res,left,right);
return res;
}
}
}
void Showball(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i];
build(1,1,n);
string op;
int l,r;
LL d;
while(m--){
cin>>op;
if(op=="C"){
cin>>l>>r>>d;
modify(1,l,d);
if(r+1<=n) modify(1,r+1,-d);
}else{
cin>>l>>r;
auto left=query(1,1,l);
auto right=(Node){0,0,0,0};
if(l+1<=r) right=query(1,l+1,r);
cout<<abs(gcd(left.sum,right.d))<<endl;
}
}
}
区间修改+区间查询 懒标记
int n, m;
int w[N];
struct Node
{
int l, r;
LL sum, add;
}tr[N * 4];
void pushup(int u)
{
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.add)
{
left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r, w[r], 0};
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int d)
{
if (tr[u].l >= l && tr[u].r <= r)
{
tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
tr[u].add += d;
}
else // 一定要分裂
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
LL query(int u, int l, int r)
{
if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL sum = 0;
if (l <= mid) sum = query(u << 1, l, r);
if (r > mid) sum += query(u << 1 | 1, l, r);
return sum;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
build(1, 1, n);
char op[2];
int l, r, d;
while (m -- )
{
scanf("%s%d%d", op, &l, &r);
if (*op == 'C')
{
scanf("%d", &d);
modify(1, l, r, d
);
}
else printf("%lld\n", query(1, l, r));
}
return 0;
}
吉司机线段树
线段树维护区间最值操作与区间历史最值的模板
struct SegTree{
//区间和、区间最大值、区间严格次大值、区间历史最大值、区间最大值出现的次数
int sum,max1,max2,maxhis,maxnum,l,r;
//区间最大值、区间历史最大值、区间非最大值、区间历史非最大值
int lazy1,lazy2,lazy3,lazy4;
void clear() {lazy1=lazy2=lazy3=lazy4=0;}
}tr[N*4];
int w[N];
void pushup(int u){
//更新区间最大值、区间历史最大值
tr[u].max1=max(tr[u<<1].max1,tr[u<<1|1].max1);
tr[u].maxhis=max(tr[u<<1].maxhis,tr[u<<1|1].maxhis);
//更新区间和
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
//更新最大值出现次数和区间严格次大值
if(tr[u<<1].max1==tr[u<<1|1].max1) {
tr[u].maxnum=tr[u<<1].maxnum+tr[u<<1|1].maxnum;
tr[u].max2=max(tr[u<<1].max2,tr[u<<1|1].max2);
}else if(tr[u<<1].max1>tr[u<<1|1].max1){
tr[u].maxnum=tr[u<<1].maxnum;
tr[u].max2=max(tr[u<<1].max2,tr[u<<1|1].max1);
}else{
tr[u].maxnum=tr[u<<1|1].maxnum;
tr[u].max2=max(tr[u<<1].max1,tr[u<<1|1].max2);
}
}
void build(int u,int l,int r){
tr[u].l=l,tr[u].r=r;
if(l==r){
tr[u].sum=tr[u].max1=tr[u].maxhis=w[l];
tr[u].max2=-inf;tr[u].maxnum=1;
return;
}
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void update(int u,int l1,int l2,int l3,int l4){
//更新区间和
tr[u].sum+=tr[u].maxnum*l1+(tr[u].r-tr[u].l+1-tr[u].maxnum)*l3;
// 更新区间历史最大值
tr[u].maxhis=max(tr[u].maxhis,tr[u].max1+l2);
//更新区间最大值和区间严格次大值
tr[u].max1+=l1;
if(tr[u].max2!=-inf) tr[u].max2+=l3;
//更新懒标记
tr[u].lazy2=max(tr[u].lazy2,tr[u].lazy1+l2);
tr[u].lazy1+=l1;
tr[u].lazy4=max(tr[u].lazy4,tr[u].lazy3+l4);
tr[u].lazy3+=l3;
}
void pushdown(int u){
int maxn=max(tr[u<<1].max1,tr[u<<1|1].max1);
if(tr[u<<1].max1==maxn) update(u<<1,tr[u].lazy1,tr[u].lazy2,tr[u].lazy3,tr[u].lazy4);
else update(u<<1,tr[u].lazy3,tr[u].lazy4,tr[u].lazy3,tr[u].lazy4);
if(tr[u<<1|1].max1==maxn) update(u<<1|1,tr[u].lazy1,tr[u].lazy2,tr[u].lazy3,tr[u].lazy4);
else update(u<<1|1,tr[u].lazy3,tr[u].lazy4,tr[u].lazy3,tr[u].lazy4);
tr[u].clear();
}
void modify1(int u,int l,int r,int k){
if(tr[u].r<l||tr[u].l>r) return;
if(l<=tr[u].l&&tr[u].r<=r) {
update(u,k,k,k,k);
return;
}
pushdown(u);
modify1(u<<1,l,r,k);
modify1(u<<1|1,l,r,k);
pushup(u);
}
void modify2(int u,int l,int r,int v){
if(tr[u].r<l||tr[u].l>r||tr[u].max1<=v) return;
if(l<=tr[u].l&&tr[u].r<=r&&tr[u].max2<v) {
update(u,v-tr[u].max1,v-tr[u].max1,0,0);
return;
}
pushdown(u);
modify2(u<<1,l,r,v);
modify2(u<<1|1,l,r,v);
pushup(u);
}
int query1(int u,int l,int r){
if(tr[u].r<l||tr[u].l>r) return 0;
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
pushdown(u);
return query1(u<<1,l,r)+query1(u<<1|1,l,r);
}
int query2(int u,int l,int r){
if(tr[u].r<l||tr[u].l>r) return -inf;
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].max1;
pushdown(u);
return max(query2(u<<1,l,r),query2(u<<1|1,l,r));
}
int query3(int u,int l,int r){
if(tr[u].r<l||tr[u].l>r) return -inf;
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].maxhis;
pushdown(u);
return max(query3(u<<1,l,r),query3(u<<1|1,l,r));
}
14.区间MEX
int a[N];
int lastpos[N*4];
#define ls u<<1
#define rs u<<1|1
void modify(int u,int l,int r,int x,int v){//单点修改
if(l==r) lastpos[u]=v;
else{
int mid=l+r>>1;
if(x<=mid) modify(ls,l,mid,x,v);
else if(x>mid) modify(rs,mid+1,r,x,v);
lastpos[u]=min(lastpos[ls],lastpos[rs]);
}
}
int query(int u,int l,int r,int pos){
if(l==r){
return l;
}
int mid=l+r>>1;
if(lastpos[ls]<pos) return query(ls,l,mid,pos);
else return query(rs,mid+1,r,pos);
}
struct ask{
int l,r;
int id;
int mex;
}v[N];
void Showball(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) {
cin>>a[i];
a[i]++;
}
memset(lastpos,-1,sizeof lastpos);
for(int i=1;i<=m;i++){
cin>>v[i].l>>v[i].r;
v[i].id=i;
}
sort(v+1,v+m+1,[](ask a,ask b){return a.r<b.r;});
for(int i=1,p=1;i<=n;i++){
modify(1,1,n+1,a[i],i);
while(p<=m&&v[p].r==i){
v[p].mex=query(1,1,n+1,v[p].l);
p++;
}
}
sort(v+1,v+m+1,[](ask a,ask b){return a.id<b.id;});
for(int i=1;i<=m;i++){
cout<<v[i].mex-1<<endl;
}
}
15.区间不同元素数量
struct node{
int id,pos;
};
vector<node> q[N];
int ans[N];
int c[N],a[N],pre[N];
int n,m;
int lowbit(int x){
return x&-x;
}
void add(int x,int k){
while(x<=n){
c[x]+=k;
x+=lowbit(x);
}
}
int sum(int x){
int res=0;
while(x>0){
res+=c[x];
x-=lowbit(x);
}
return res;
}
void Showball(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
cin>>m;
int l,r;
for(int i=1;i<=m;i++){
cin>>l>>r;
q[r].pb({i,l});
}
for(int i=1;i<=n;i++){
if(pre[a[i]]){
add(pre[a[i]],-1);
add(i,1);
pre[a[i]]=i;
}else{
add(i,1);
pre[a[i]]=i;
}
for(auto j:q[i]){
ans[j.id]=sum(i)-sum(j.pos-1);
}
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<endl;
}
}
16.树链剖分
将树从x到y结点最短路径上所有节点的值都加上z
求树从x到y结点最短路径上所有节点的值之和
将以x为根节点的子树内所有节点值都加上z
求以x为根节点的子树内所有节点值之和
vector<int> e[N];
int fa[N],dep[N],son[N],sz[N],id[N];
int top[N],dfn;
//预处理各个信息
void dfs1(int u,int father){
fa[u]=father;
dep[u]=dep[father]+1;
sz[u]=1;
for(auto v:e[u]){
if(v==father) continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v]) son[u]=v;
}
}
void dfs2(int u,int tp){
id[u]=++dfn;
w[dfn]=a[u];
top[u]=tp;
if(!son[u]) return;
dfs2(son[u],tp);
for(auto v:e[u]){
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
int qRange(int u,int v){
int res=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
//线段树区间查询[id[top[u]],id[u]]
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
//加上两点之间的答案[id[u],id[v]]
return res;
}
void upRange(int u,int v,int k){
k%=p;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
//线段树修改[id[top[u]],id[u]]
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
//修改两点之间[id[u],id[v]]
}
int qSon(int u){
//线段树查询[id[u],id[u]+sz[u]-1]
}
void upSon(int u,int k){
//线段树修改[id[u],id[u]+sz[u]-1]
}
边权转点权
//把边权作为子节点的点权
区间改为[id[u]+1,id[v]];
17.树上启发式合并(dsu on tree)
用于\(O(logn)\)求出每个子树的信息。
int tot;
int id[N];
LL c[N],ans[N];
vector<int> g[N];
int n;
struct node{
int mx_cnt=0;
LL mx_sum=0;
map<int,int> cnt;
vector<int> list;
void add(int u){
cnt[c[u]]++;
if(cnt[c[u]]>mx_cnt){
mx_cnt=cnt[c[u]];
mx_sum=c[u];
}else if(cnt[c[u]]==mx_cnt) mx_sum+=c[u];
list.push_back(u);
}
int size(){
return list.size();
}
}sub[N];
void dfs(int u,int fa){
id[u]=++tot;
int mx_son=-1,mx_sz=0;
for(auto v:g[u]){
if(v==fa) continue;
dfs(v,u);
if(sub[id[v]].size()>mx_sz){
mx_sz=sub[id[v]].size();
mx_son=v;
}
}
if(mx_son!=-1) id[u]=id[mx_son];
for(auto v:g[u]){
if(v==fa||v==mx_son) continue;
for(auto son:sub[id[v]].list){
sub[id[u]].add(son);
}
}
sub[id[u]].add(u);
ans[u]=sub[id[u]].mx_sum;
}
17.倍增求k级祖先
void dfs(int u,int fa){
dep[u]=dep[fa]+1;
//预处理
f[u][0]=fa;
for(int i=0;i<20;i++) f[u][i]=f[f[u][i-1]][i-1];
//work
}
int get_fa(int u,int k){
for (int i=0;i<20;i++)
if((1<<i)&k) u=f[u][i];
return u;
}
三、搜索与图论
1.DFS
深度优先搜索 将元素放进栈里
递归求指数型枚举: 3
3
2
2 3
1
1 3
1 2
1 2 3
void dfs(int u){
if(u>n){
for(int i=1;i<=n;i++){
if(st[i]==1) printf("%d ",i);
}
puts("");
return;
}
st[u]=2;
dfs(u+1);
st[u]=0;
st[u]=1;
dfs(u+1);
st[u]=0;
}
递归求排列型枚举:3
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
void dfs(int u){
if(u>n){
for(int i=1;i<=n;i++) cout<<path[i]<<" \n"[i==n];
return;
}
for(int i=1;i<=n;i++){
if(!st[i]){
path[u]=i;
st[i]=1;
dfs(u+1);
st[i]=0;
}
}
}
递归求组合型枚举:5 3
void dfs(int u,int start){
if(u+n-start<m) return;
if(u==m+1){
for(int i=1;i<=m;i++){
cout<<path[i]<<" ";
}
cout<<endl;
return;
}
for(int i=start;i<=n;i++){
path[u]=i;
dfs(u+1,i+1);
path[u]=0;
}
}
2.BFS
广度有限搜索 将元素放进队列里
由于以上两种搜索方式我只学习了基础用法,待补充
0-1BFS
对于权值为0和 1的最短路问题
例如在走迷宫问题中,你可以花 1 个金币走 5 步,也可以不花金币走 1 步,这就可以用 0-1 BFS 解决。
使用双端队列,一种情况入队列头,一种情况入队列尾。
//CF 173B
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
int dist[M][M][4];
char g[M][M];
int n,m;
deque<int> q;
void add_front(int x,int y,int dir,int d){
if(d<dist[x][y][dir]){
dist[x][y][dir]=d;
q.push_front(dir);
q.push_front(y);
q.push_front(x);
}
}
void add_back(int x,int y,int dir,int d){
if(d<dist[x][y][dir]){
dist[x][y][dir]=d;
q.push_back(x);
q.push_back(y);
q.push_back(dir);
}
}
void bfs(){
add_front(0,0,2,0);
while(!q.empty()){
int x=q[0],y=q[1],dir=q[2];
q.pop_front();
q.pop_front();
q.pop_front();
int d=dist[x][y][dir];
int nx=x+dx[dir],ny=y+dy[dir];
if(nx>=0&&nx<n&&ny>=0&&ny<m){
add_front(nx,ny,dir,d);
}
if(g[x][y]=='#'){
for(int i=0;i<4;i++){
if(i!=dir){
add_back(x,y,i,d+1);
}
}
}
}
}
void Showball() {
cin>>n>>m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
cin>>g[i][j];
}
}
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
for(int k=0;k<4;k++){
dist[i][j][k]=inf;
}
}
}
bfs();
if(dist[n-1][m-1][2]==inf) cout<<-1<<endl;
else cout<<dist[n-1][m-1][2]<<endl;
}
3.树
1.树的遍历
//DFS
int dfs(int u)
{
st[u] = true;
int size = 0, sum = 0;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (st[j]) continue;
int s = dfs(j);
size = max(size, s);
sum += s;
}
size = max(size, n - sum - 1);
ans = min(ans, size);
return sum + 1;
}
//BFS
int bfs()
{
memset(d, -1, sizeof d);
queue<int> q;
d[1] = 0;
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (d[j] == -1)
{
d[j] = d[t] + 1;
q.push(j);
}
}
}
return d[n];
}
2.树的直径
定义:树上最长的简单路径
思路:2次dfs求直径
//vector存图
vector<int> edge[N];
int st[N];
int dis[N];
void dfs(int u){
for(int i=0;i<edge[u].size();i++){
int j=edge[u][i];
if(st[j]) continue;
st[j]=true;
dis[j]=dis[u]+1;//加上边权
dfs(j);
}
}
void solve(){
int n,m;
cin>>n>>m;
while(m--){
int u,v;
cin>>u>>v;
edge[u].pb(v);
edge[v].pb(u);
}
st[1]=1;
dfs(1);
int maxlen=0,Q,W,ans=0;
for(int i=1;i<=n;i++){
if(dis[i]>maxlen) maxlen=dis[i],Q=i;
dis[i]=st[i]=0;
}
st[Q]=1;
dfs(Q);
for(int i=1;i<=n;i++){
if(dis[i]>ans) ans=dis[i],W=i;
}
cout<<ans<<endl;
}
//邻接表存图
int e[2*N],ne[2*N],h[N],idx;
bool st[N];
int dis[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(st[j]) continue;
st[j]=true;
dis[j]=dis[u]+1;
dfs(j);
}
}
void solve(){
int n,m;
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(a,b);
add(b,a);
}
int maxlen=0,Q,W,ans=0;
st[1]=true;
dfs(1);
for(int i=1;i<=n;i++){
if(dis[i]>maxlen) maxlen=dis[i],Q=i;
dis[i]=st[i]=0;
}
st[Q]=1;
dfs(Q);
for(int i=1;i<=n;i++){
if(dis[i]>ans) ans=dis[i],W=i;
}
cout<<ans<<endl;
}
3.二叉树的遍历
已知后序遍历和中序遍历,构造出这棵树,并且返回层序遍历
int a[35],b[35],p[35];
//a数组存后序遍历,b数组存中序遍历。p数组存中序遍历每个权值的位置
vector<int> level[35];
void build(int al,int ar,int bl,int br,int d){
if(al>ar) return;
int val = a[ar];
level[d].pb(val);
int k=p[val];
build(al,al+k-1-bl,bl,k-1,d+1);
build(al+k-bl,ar-1,k+1,br,d+1);
}
void Showball() {
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) cin>>b[i];
for(int i=0;i<n;i++) p[b[i]]=i;
build(0,n-1,0,n-1,0);
for(int i=0;i<n;i++){
for(auto x:level[i]){
cout<<x<<" ";
}
}
}
void dfs(string in,string post){//已知中序后序求前序
if(post.empty()) return;
char root=post.back();
int k=in.find(root);
post.erase(post.end()-1);
string postLeft=post.substr(0,k),postRight=post.substr(k);
string inLeft=in.substr(0,k),inRight=in.substr(k+1);
cout<<root;
dfs(inLeft,postLeft);
dfs(inRight,postRight);
}
4.树的重心
找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。
//求树的重心和最短距离和
int fa[N],si[N],r[N],d[N];
vector<int> e[N];
int dfs(int u,int f){
si[u]=1;
fa[u]=f;
for(auto v:e[u]){
if(v==f) continue;
si[u]+=dfs(v,u);
}
return si[u];
}
void dfs1(int u,int fa){
for(auto v:e[u]){
if(v==fa) continue;
d[v]=d[u]+1;
dfs1(v,u);
}
}
void Showball(){
int n;
cin>>n;
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
e[a].pb(b);
e[b].pb(a);
}
dfs(1,0);
int minn=inf;
for(int i=1;i<=n;i++){
r[i]=n-si[i];
for(auto j:e[i]){
if(fa[j]==i) r[i]=max(r[i],si[j]);
}
minn=min(minn,r[i]);
}
int pos=0;
for(int i=1;i<=n;i++){
if(r[i]==minn) {pos=i;break;}
}
dfs1(pos,0);
int ans=0;
for(int i=1;i<=n;i++) ans+=d[i];
cout<<pos<<" "<<ans<<endl;
}
5.LCA(最近公共祖先)
倍增求LCA
vector<int> e[N];
int depth[N],fa[N][22];
int q[N];//队列
void bfs(int root){//预处理
memset(depth,0x3f,sizeof depth);
depth[0]=0,depth[root]=1;
int hh=0,tt=0;
q[0]=root;
while(hh<=tt){
int u=q[hh++];
for(auto v:e[u]){
if(depth[v]>depth[u]+1){
depth[v]=depth[u]+1;
q[++tt]=v;
fa[v][0]=u;
for(int i=1;i<=18;i++)
fa[v][i]=fa[fa[v][i-1]][i-1];
}
}
}
}
int lca(int a,int b){
if(depth[a]<depth[b]) swap(a,b);
for(int i=18;i>=0;i--){
if(depth[fa[a][i]]>=depth[b]){
a=fa[a][i];
}
}
if(a==b) return a;
for(int i=18;i>=0;i--){
if(fa[a][i]!=fa[b][i]){
a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
}
tarjan求LCA
vector<int> e[N];
vector<PII> query[N];
int res[N],p[N],st[N];
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
void tarjan(int u){
st[u]=1;
for(auto v:e[u]){
if(!st[v]){
tarjan(v);
p[v]=u;
}
}
//处理询问
for(auto [v,id]:query[u]){
if(st[v]==2){
res[id]=find(v);
}
}
st[u]=2;
}
void Showball(){
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
e[u].pb(v);
e[v].pb(u);
}
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
if(a==b) {res[i]=a;continue;}
query[a].pb({b,i});
query[b].pb({a,i});
}
for(int i=1;i<=n;i++) p[i]=i;
tarjan(s);
for(int i=0;i<m;i++){
cout<<res[i]<<endl;
}
}
树链剖分求LCA
vector<int> e[N];
int fa[N],dep[N],son[N],sz[N];
int top[N];
//预处理各个信息
void dfs1(int u,int father){
fa[u]=father;
dep[u]=dep[father]+1;
sz[u]=1;
for(auto v:e[u]){
if(v==father) continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v]) son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp;
if(!son[u]) return;
dfs2(son[u],tp);
for(auto v:e[u]){
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
u=fa[top[u]];
}
return dep[u]<dep[v]?u:v;
}
4.拓扑排序
//先将入度为0的点当做起点放入队列,然后遍历起点能到的所有点,bfs即可
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int h[N],ne[N],e[N],idx;
int d[N],q[N];
int n,m;
void add(int a,int b){
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
bool topsort(){
int hh=0,tt=-1;
for(int i=1;i<=n;i++){
if(!d[i]) q[++tt]=i;
}
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(--d[j]==0){
q[++tt]=j;
}
}
}
return tt==n-1;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
add(a,b);
d[b]++;
}
if(topsort()){
for(int i=0;i<n;i++) cout<<q[i]<<" ";
}
else puts("-1");
return 0;
}
5.最短路
1.Dijkstra算法
朴素版本
int n,m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=1;i<n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[j]<dist[t])) t=j;
}
for(int j=1;j<=n;j++){
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
st[t]=true;
}
if(dist[n]==inf) return -1;
else return dist[n];
}
void solve(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b]=min(g[a][b],c);
}
cout<<dijkstra()<<endl;
}
int main()
{
io;
solve();
return 0;
}
堆优化版本
int n,m;
int h[N],e[N],w[N],ne[N],idx;
int dist[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII> > heap;
heap.push({0,1});
while(heap.size()){
auto t=heap.top();
heap.pop();
int ver=t.ss,dis=t.ff;
if(st[ver]) continue;
st[ver]=true;
for(int i=h[ver];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dis+w[i]){
dist[j]=dis+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==inf) return -1;
return dist[n];
}
void solve(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dijkstra()<<endl;
}
int main()
{
io;
solve();
return 0;
}
最新版
vector<PII> e[N];
int dis[N];
bool st[N];
void dijkstra(int root){
memset(dis,0x3f,sizeof dis);
memset(st,0,sizeof st);
dis[root]=0;
priority_queue<PII,vector<PII>,greater<PII>> q;
q.push({0,root});
while(!q.empty()){
auto [dist,u]=q.top();
q.pop();
if(st[u]) continue;
st[u]=true;
for(auto [v,w]:e[u]){
if(dis[v]>dist+w){
dis[v]=dist+w;
q.push({dis[v],v});
}
}
}
}
对于多个起点到多个终点的最短路。可以建立一个虚拟原点,然后从原点向所有起点建一条边权为0的边,然后直接跑单源最短路即可。
最短路计数
计算最短路,和次短路的数量
#include<bits/stdc++.h>
using namespace std;
#define ff first
#define ss second
#define eb emplace_back
#define all(u) u.begin(), u.end()
#define endl '\n'
#define debug(x) cout<<#x<<":"<<x<<endl;
typedef pair<int, int> PII;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1000+ 10, M = 105;
const int mod = 1e9 + 7;
const int cases = 1;
struct node{
int id,dis,type;
bool operator<(const node& u)const{
return dis>u.dis;
}
};
int dis[N][2],cnt[N][2];
bool st[N][2];
vector<PII> e[N];
int n,m;
int s,t;
int dijkstra(){
memset(dis,0x3f,sizeof dis);
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
dis[s][0]=0;cnt[s][0]=1;
priority_queue<node> q;
q.push({s,0,0});
while(!q.empty()){
auto [u,dist,type]=q.top();
q.pop();
int count=cnt[u][type];
if(st[u][type]) continue;
st[u][type]=true;
for(auto [v,w]:e[u]){
if(dis[v][0]>dist+w){
//更新次小值
dis[v][1]=dis[v][0];cnt[v][1]=cnt[v][0];
q.push({v,dis[v][1],1});
//更新最小值
dis[v][0]=dist+w;cnt[v][0]=count;
q.push({v,dis[v][0],0});
}else if(dis[v][0]==dist+w){
cnt[v][0]+=count;
}else if(dis[v][1]>dist+w){
dis[v][1]=dist+w;
cnt[v][1]=count;
q.push({v,dis[v][1],1});
}else if(dis[v][1]==dist+w){
cnt[v][1]+=count;
}
}
}
int res=cnt[t][0];
if(dis[t][0]+1==dis[t][1]) res+=cnt[t][1];
return res;
}
void Showball(){
cin>>n>>m;
for(int i=1;i<=n;i++) e[i].clear();
while(m--){
int u,v,w;
cin>>u>>v>>w;
e[u].eb(v,w);
}
cin>>s>>t;
cout<<dijkstra()<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T=1;
if(cases) cin>>T;
while(T--)
Showball();
return 0;
}
2.bellman_ford算法
//有边数限制的最短路算法
//for n次
//for 所有边 a,b,w (松弛操作)
//dist[b] = min(dist[b],back[a] + w)
struct node{
int a,b,w;
}edge[M];
int dist[N];
int last[N];
int n,m,k;
void bellman_ford(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<k;i++){
memcpy(last,dist,sizeof dist);
for(int j=0;j<m;j++){
int a=edge[j].a,b=edge[j].b,w=edge[j].w;
dist[b]=min(dist[b],last[a]+w);
}
}
}
void solve(){
cin>>n>>m>>k;
for(int i=0;i<m;i++){
int a,b,w;
cin>>a>>b>>w;
edge[i]={a,b,w};
}
bellman_ford();
if(dist[n]>=inf/2) cout<<"impossible"<<endl;
else cout<<dist[n]<<endl;
}
3.spfa已死算法
//松弛操作
int n,m;
int h[N],e[N],ne[N],w[N],idx;
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool st[N];
int dist[N];
int spfa(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
queue<int> q;
q.push(1);
st[1]=true;
while(q.size()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dist[j]>dist[t]+w[i]){
dist[j]=dist[t]+w[i];
if(!st[j]){
q.push(j);
st[j]=true;
}
}
}
}
return dist[n];
}
void solve(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b,w;
cin>>a>>b>>w;
add(a,b,w);
}
int t=spfa();
if(t==inf) puts("impossible");
else cout<<t<<endl;
}
spfa算法判断负环
//刚开始把所有的点都放入队列,然后记录每个点的最短路上有多少条边。如果边数大于等于n,那么说明存在负环。
int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
queue<int> q;
for (int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);
}
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
4.Floyd算法
作用:
求多源最短路 \(O(n^3)\)
传递闭包 \(O(n^3)\)
求最小环
恰好经过k条边的最短路
//k,i,j d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
int d[550][550];
int n,m,q;
void floyed(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
void solve(){
cin>>n>>m>>q;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j) d[i][j]=0;
else d[i][j]=inf;
}
}
while(m--){
int a,b,c;
cin>>a>>b>>c;
d[a][b]=min(d[a][b],c);
}
floyed();
while(q--){
int a,b;
cin>>a>>b;
if(d[a][b]>=inf/2) cout<<"impossible"<<endl;
else cout<<d[a][b]<<endl;
}
}
//恰好经过K条边的最短路 O(n^3*logn)
int g[N][N];
int res[N][N];
void mul(int c[][N],int a[][N],int b[][N]){
static int tmp[N][N];
memset(tmp,0x3f,sizeof tmp);
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
tmp[i][j]=min(tmp[i][j],a[i][k]+b[k][j]);
}
}
}
memcpy(c,tmp,sizeof tmp);
}
void qmi(int k){
memset(res,0x3f,sizeof res);
for(int i=1;i<=n;i++) res[i][i]=0;
while(k){
if(k&1) mul(res,res,g);
mul(g,g,g);
k>>=1;
}
}
6.最小生成树
1.Prim算法
//和朴素dijkstra()类似,注意第一层循环是[1,n],不是[1,n)。
int n,m;
int g[550][550];
bool st[N];
int dist[N];
int prim(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
int res=0;
for(int i=1;i<=n;i++){
int t=-1;
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j;
}
if(dist[t]==inf) return inf;
res+=dist[t];
st[t]=true;
for(int j=1;j<=n;j++){
dist[j]=min(dist[j],g[t][j]);
}
}
return res;
}
void solve(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--){
int a,b,c;
cin>>a>>b>>c;
g[a][b]=g[b][a]=min(g[a][b],c);
}
int t=prim();
if(t==inf) puts("impossible");
else cout<<t<<endl;
}
2.Kruskal算法
根据贪心的性质,可以求解"最小生成森林",以及最大边最小生成树
//边权有负数的情况
//并查集
//如果求最大生成树,那么就修改小于号的重载
struct node{
int a,b,w;
bool operator<(node b){
return w<b.w;
}
}e[M];
int n,m;
int p[N];
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int kruskal(){
int res=0,cnt=0;
sort(e,e+m);
for(int i=1;i<=n;i++) p[i]=i;
for(int i=0;i<m;i++){
int a=e[i].a,b=e[i].b,w=e[i].w;
a=find(a),b=find(b);
if(a!=b){
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt<n-1) return inf;
return res;
}
7.二分图
1.染色法判断二分图
//二分图:一定不含有奇数环,可能包含长度为偶数的环, 不一定是连通图
int h[N],e[2*N],ne[2*N],idx;
int n,m;
int color[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool dfs(int u,int c){
color[u]=c;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!color[j]){
if(!dfs(j,3-c)) return false;
}
else if(color[j]==c) return false;
}
return true;
}
void solve(){
cin>>n>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
bool flg=true;
for(int i=1;i<=n;i++){
if(!color[i]){
if(!dfs(i,1)){
flg=false;
break;
}
}
}
if(flg) puts("Yes");
else puts("No");
}
2.匈牙利算法--二分图的最大匹配
int h[N],e[N],ne[N],idx;
int n1,n2,m;
int match[N];
int st[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool find(int x){
for(int i=h[x];~i;i=ne[i]){
int j=e[i];
if(!st[j]){
st[j]=true;
if(!match[j]||find(match[j])){
match[j]=x;
return true;
}
}
}
return false;
}
void solve(){
cin>>n1>>n2>>m;
memset(h,-1,sizeof h);
while(m--){
int a,b;
cin>>a>>b;
add(a,b);
}
int cnt=0;
for(int i=1;i<=n1;i++){
memset(st,0,sizeof st);
if(find(i)) cnt++;
}
cout<<cnt<<endl;
}
8.Tarjan
1.强联通分量 缩点
stack<int> stk;
vector<int> e[N];
int dfn[N],low[N],tot;
int instk[N],scc[N],siz[N],cnt;
//强连通分量
void tarjan(int u){
dfn[u]=low[u]=++tot;
stk.push(u);instk[u]=1;
for(auto v:e[u]){
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(instk[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;++cnt;
do{
v=stk.top();
stk.pop();
instk[v]=0;
scc[v]=cnt;
++siz[cnt];
}while(v!=u);
}
}
//缩点
for(int i=1; i<=n; i++){
if(!dfn[i]) tarjan(i);
}
for(int u=1;u<=n;u++){
for(auto v:e[u]){
if(scc[u]!=scc[v]){
din[scc[v]]++;
dout[scc[u]]++;
}
}
}
#include<bits/stdc++.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define all(u) u.begin(), u.end()
#define endl '\n'
#define debug(x) cout<<#x<<":"<<x<<endl;
typedef pair<int, int> PII;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 5e5 + 10, M = 105;
const int mod = 1e9 + 7;
const int cases = 0;
stack<int> stk;
vector<int> e[N];
vector<PII> ne[N];
int dfn[N],low[N],tot;
int instk[N],scc[N],siz[N],cnt;
int w[N],sum[N];
int din[N];
int n,m,s,p;
int st[N],dist[N],bar[N];
void tarjan(int u){
dfn[u]=low[u]=++tot;
stk.push(u);instk[u]=1;
for(auto v:e[u]){
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(instk[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
int v;++cnt;
do{
v=stk.top();
stk.pop();
instk[v]=0;
scc[v]=cnt;
siz[cnt]++;
sum[cnt]+=w[v];
}while(v!=u);
}
}
void spfa(int s){
queue<int> q;
memset(dist,0x3f,sizeof dist);
dist[s]=-sum[s];
st[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
st[u]=0;
for(auto [v,w]:ne[u]){
if(dist[v]>dist[u]+w){
dist[v]=dist[u]+w;
if(!st[v]){
st[v]=1;
q.push(v);
}
}
}
}
}
void Showball(){
cin>>n>>m;
while(m--){
int u,v;
cin>>u>>v;
e[u].pb(v);
}
for(int i=1;i<=n;i++) cin>>w[i];
cin>>s>>p;
for(int i=1;i<=p;i++) cin>>bar[i];
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++){
for(auto j:e[i]){
int u=scc[i],v=scc[j];
if(u==v) continue;
ne[u].pb({u+cnt,0});
ne[u].pb({v,-sum[v]});
ne[v].pb({v+cnt,0});
}
}
spfa(scc[s]);
int ans=0;
for(int i=1;i<=p;i++) ans=max(ans,-dist[scc[bar[i]]+cnt]);
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T=1;
if(cases) cin>>T;
while(T--)
Showball();
return 0;
}
四、数学知识
1.质数
//1.试除法判断质数
bool is_prime(int n){
if(n<2) return false;
for(int i=2;i<=n/i;i++){
if(n%i==0) return false;
}
return true;
}
//2.分解质因数
//将n分解成a1^p1+a2^p2+....的形式,其中ai都是质数
//1.暴力分解 时间复杂度:O(sqrt(N))
void devide(int n){
for(int i=2;i<=n/i;i++){
if(n%i==0){
int s=0;
while(n%i==0){
n/=i;
s++;
}
cout<<i<<" "<<s<<"\n";
}
}
if(n>1) cout<<n<<" "<<1<<"\n";
cout<<"\n";
}
//优化,因为我们只需要质因子,所以枚举的时候可以直接枚举处理好的质因子。
//前N个数中的有N/ln(N)个质因子。
for(int p:prime){
if(!n) break;
if(n%p==0){
while(n%p==0){
}
}
}
//时间复杂度O(NlogN)
//如果N为10^5/10^6/10^7,可以利用筛法求出minp[i],即i的最小的一个质因子。
//线性筛
for(int i=2;i<N;i++) minp[i]=i;
for(int i=2;i<N;i++){
if(minp[i]!=i) continue;
for(int j=2*i;j<N;j+=i){
if(minp[j]==j) minp[j]=i;
}
}
//分解质因数时就可以
while(num!=1){
int p=minp[num];
//work
num/=p;
}
//3.筛质数
阶乘分解质因数
先预处理一下质数,然后n!对于每一个质数p的次数为\(n/p+n/p^2+n/p^3.....\)
int get(int n, int p)
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
GCD:
基本性质:\(gcd(a,b)=gcd(a,b-a)\)
推广:\(gcd(a_1,a_2,...,a_n)=gcd(a1,a2-a1,...,a_n-a_{n-1})\)
若\(gcd(a,b)=k\),则有\(gcd(a/k,b/k)=1\)
2.约数
//1.试除法求约数
vector<int> get_divisors(int n){
vector<int> res;
for(int i=1;i<=n/i;i++){
if(n%i==0){
res.push_back(i);
if(i!=(n/i)) res.push_back(n/i);
}
}
sort(res.begin(),res.end());
return res;
}
//2.约数个数
N=p1^α1∗p2^α2∗⋯∗pk^αk
ans=(α1+1)(α2+1)…(αk+1)
code:
for(int i=2;i<=n/i;i++){
while(n%i==0){
n/=i;
mp[i]++;
}
}
if(n>1) mp[n]++;
int ans=1;
for(auto p:mp) ans*=(p.second+1);
//3.约数之和
code:
for(int i=2;i<=n/i;i++){
while(n%i==0){
n/=i;
mp[i]++;
}
}
if(n>1) mp[n]++;
int res=1;
for(auto p:mp){
int a=p.first,b=p.second;
int t=1;
while(b--) t=(t*a+1);
res*=t;
}
//4.最大公约数and最小公倍数
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
int lcm(int a,int b){
return a*b/gcd(a,b);
}
3.欧拉函数
\(\phi(N)\)表示:1-N中与N互质的个数
先对N分解质因数:\(N=p_1^{a_1}*p_2^{a_2}....p_k^{a_k}\)
则\(\phi(N)=N*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*....*(1-\frac{1}{p_k})\)
公式有容斥定理得来。
先分解质因数,然后套公式即可,为了防止溢出,所以\(N*(1-1/p)\)写成\(N/p*(p-1)\)
时间复杂度:\(O(\sqrt{N})\)
int phi(int x){
int res=x;
for(int i=2;i<=x/i;i++){
if(x%i==0){
res=res/i*(i-1);
while(x%i==0) x/=i;
}
}
if(x>1) res=res/x*(x-1);
return res;
}
2.线性筛的同时求出1-N的所有欧拉函数值
时间复杂度:\(O(N)\)
对于素数p:那么1-p中和p互素的数有p-1个,所以\(\phi(p)=p-1\)
对于\(i\%primes_j=0\) ,说明\(prime_j\)是\(i\)的一个因子,那么\(i*primes_j\)的质因子和\(primes_j\)一致,
所以\(\phi(primes_j*i)=primes_j*i*(1-\frac{1}{p1})*(1-\frac{1}{p2})*...(1-\frac{1}{pk})\)
\(=primes_j*[i*(1-\frac{1}{p1})*(1-\frac{1}{p2})*...(1-\frac{1}{pk})]\)
\(=primes_j*\phi(i)\)
对于\(i\%primes_j\neq0\),那么i就是\(i*primes_j\)的质因子。
所以\(\phi(primes_j*i)=primes_j*i*(1-\frac{1}{p1})*(1-\frac{1}{p2})*...(1-\frac{1}{pk})*(1-\frac{1}{primes_j})\)
\(=primes_j*[i*(1-\frac{1}{p1})*(1-\frac{1}{p2})*...(1-\frac{1}{pk})]*(1-\frac{1}{primes_j})\)
\(=(primes_j-1)*\phi(i)\)
int primes[N],cnt;
int phi[N];
bool st[N];
void get_phi(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!st[i]) {
primes[cnt++]=i;
phi[i]=i-1;
}
for(int j=0;primes[j]<=n/i;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0) {
phi[primes[j]*i]=phi[i]*primes[j];
break;
}
phi[primes[j]*i]=phi[i]*(primes[j]-1);
}
}
}
欧拉定理:
若a与n互质,则有\(a^{\phi(n)}\equiv1 (mod\ n)\)
费尔马小定理:若a与p互质,且p是质数,则\(a^{p-1}\equiv 1(mod\ p)\)
4.快速幂
LL qmi(LL a, LL k, LL p)
{
LL res = 1 % p;
while (k)
{
if (k&1) res = res * a % p;
a = a * a % p;
k >>= 1;
}
return res;
}
求逆元时,如果模数为素数,则可以直接用快速幂来求,比如求a mod p的逆元,\(a^{-1}(mod \ p)=qmi(a,p-2,p)\)
否则就需要用拓展欧几里得求逆元:exgcd(a, p, x, y)。x即为逆元。
对于一些概率题,会让我们输出分数的模数,那么就可以用逆元,比如\(\frac{b}{a}(mod\ p)=a*b^{-1}(mod\ p)\)
对于指数取模,则要模上mod-1。
5.拓展欧几里得算法
裴蜀定理:对于正整数a和b,一定存在整数x和y使得:\(ax+by=gcd(a,b)\)
不定方程通解:\(x=x_0+n*\frac{b}{d},y=y_0+n*\frac{a}{d}\)
\(x \equiv y \pmod{n}\)等价于 $m | (x-y) $
拓展欧几里得就是用来求出x和y的算法
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
求解线性同余方程:
对于\(ax\equiv b(mod \ p)\),求解x,那么一定有\(ax=yp+b\),不妨,令\(y'=-y\),则有\(ax+y'p=b\),就可以套用拓展欧几里得求解了
所以如果\(gcd(a,p)|b\),则此方程一定有解。
int d=exgcd(a,m,x,y);
int res=x*b/d%m;
递推求逆元
inv[1] = 1;
for (int i = 2; i <= N; i++)
{
inv[i] = inv[mod % i] * (mod - mod / i) % mod;
}
6.中国剩余定理
对于线性同余方程组
其中\(m_1,m_2,...m_k\)两两互质,我们令\(M=m_1*m_2*...*m_k\)
\(M_i=M/m_i, \forall i\in\{1,k\}\),\(M_i^{-1}\)表示\(M_i (mod\ m_i)\)的逆元。
则有\(x=a_1*M_1*M_1^{-1}+a_2*M_2*M_2^{-1}+...+a_k*M_k*M_k^{-1}\)
LL CRT(int k, LL* a, LL* r) {
LL n = 1, ans = 0;
for (int i = 1; i <= k; i++) n = n * r[i];
for (int i = 1; i <= k; i++) {
LL m = n / r[i], b, y;
exgcd(m, r[i], b, y); // b * m mod r[i] = 1
ans = (ans + a[i] * m * b % n) % n;
}
return (ans % n + n) % n;
}
7.高斯消元
求解多元线性方程组的经典算法。时间复杂度:\(O(n^3)\)
- 增广矩阵行初等行变换为行最简形;
- 还原线性方程组;
- 求解第一个变量;
- 补充自由未知量;
- 列表示方程组通解。
int gauss() // 高斯消元,答案存于a[i][n]中,0 <= i < n
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ ) // 找绝对值最大的行
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1
for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2; // 无解
return 1; // 有无穷多组解
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[i][j] * a[j][n];
return 0; // 有唯一解
}
//输出时
if (fabs(a[i][n]) < eps) a[i][n] = 0; // 去掉输出 -0.00 的情况
异或线性方程组
int n;
int a[N][N];
int gauss()
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ )
if (a[i][c])
t = i;
if (!a[t][c]) continue;
for (int i = c; i <= n; i ++ ) swap(a[r][i], a[t][i]);
for (int i = r + 1; i < n; i ++ )
if (a[i][c])
for (int j = n; j >= c; j -- )
a[i][j] ^= a[r][j];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (a[i][n])
return 2;
return 1;
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] ^= a[i][j] * a[j][n];
return 0;
}
8.组合数
1.范围为2000的组合数,递推即可 \(O(n^2)\)
int c[N][N];
void init(){
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){
if(!j) C[i][j]=1;
else C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
}
}
2.范围为\(10^5\)的组合数,预处理 \(O(nlogn)\)
\(C_a^{b}=\frac{a!}{(a-b)!b!}\%mod=a!*(a-b)!^{-1}*b!^{-1}\%mod\)
所以我们可以预处理出阶乘和阶乘的逆元,因为mod是质数,所以可以快速幂求逆元。
\(C_a^b=(LL)fact[a]*infact[b]\%mod*infact[a-b]\%mod\)
int fact[N],infact[N];
int qmi(int a,int k,int p){
int res=1%p;
while(k){
if(k&1) res=(LL)res*a%p;
a=(LL)a*a%p;
k>>=1;
}
return res;
}
void init(int n){
fact[0]=infact[0]=1;
for(int i=1;i<=n;i++){
fact[i]=(LL)fact[i-1]*i%mod;
infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;
}
}
int C(int a,int b){
return (LL)fact[a]*infact[b]%mod*infact[a-b]%mod;
}
3.范围为\(10^{18}\),卢卡斯定理。\(O(p*log_p(N)*logp)\)
\(C_a^b\equiv (C_{a\ mod\ p}^{b\ mod\ p}*C_{a/p}^{b/p})\ mod\ p\)
引理1:p是素数,则\(C_p^i \equiv 0(mod\ p)\ (0<i<p)\)
引理2:整数x和素数p,则\((1+x)^p\equiv (1+x^p)\ mod\ p\)
证明:https://zhuanlan.zhihu.com/p/452976974
int p;
LL C(LL a, LL b, int p)
{
if(b > a) return 0;
if(b > a - b) b = a - b;
LL x = 1, y = 1;
for(int i = 0; i < b; i++)
{
x = x * (a - i) % p;
y = y * (i + 1) % p;
}
return x * qmi(y, p - 2, p) % p;
}
LL lucas(LL a,LL b,LL p){
if(a<p&&b<p) return C(a,b,p);
else return (LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
}
4.没有模数,高精度求组合数
范围:5000
分解质因数+高精度乘法
1.筛质数
2.阶乘分解
3.高精度
#include<bits/stdc++.h>
using namespace std;
const int N =5500;
int primes[N],cnt;
bool st[N];
int sum[N];
void get_primes(int n){
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++){
st[primes[j]*i]=true;
if(i%primes[j]==0) break;
}
}
}
int get(int n,int p){
int res=0;
while(n){
res+=n/p;
n/=p;
}
return res;
}
vector<int> mul(vector<int> a,int b){
vector<int> c;
int t=0;
for(int i=0;i<a.size();i++){
t+=a[i]*b;
c.push_back(t%10);
t/=10;
}
while(t){
c.push_back(t%10);
t/=10;
}
return c;
}
int main(){
int a,b;
cin>>a>>b;
get_primes(a);
for(int i=0;i<cnt;i++){
int p=primes[i];
sum[i]=get(a,p)-get(a-b,p)-get(b,p);
}
vector<int> res;
res.push_back(1);
for(int i=0;i<cnt;i++){
int p=primes[i];
for(int j=0;j<sum[i];j++){
res=mul(res,p);
}
}
for(int i=res.size()-1;i>=0;i--) cout<<res[i];
}
彩蛋: #define printf system("shutdown") printf
卡特兰数
前几项:1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786
递推式:\(f(n)=\sum\limits_{i=0}^{n-1}f(i)*f(n-i-1)\)
\(=C_{2n}^{n}-C_{2n}^{n-1}=\frac{C_{2n}^{n}}{n+1}\)
单次询问,大数据
void solve(){
int n;
cin>>n;
int a=2*n,b=n;
int res=1;
for(int i=a;i>a-b;i--) res=(LL)res*i%mod;
for(int i=1;i<=b;i++) res=(LL)res*qmi(i,mod-2,mod)%mod;
res=(LL)res*qmi(n+1,mod-2,mod)%mod;
cout<<res<<endl;
}
多次询问小数据
f[0] = 1;
cin >> n;
for (int i = 1; i <= n; i++) f[i] = f[i - 1] * (4 * i - 2) / (i + 1);
// 这里用的是常见公式2
cout << f[n] << endl;
哈代-拉马努金拆分数列
1, 1, 2, 3, 5, 7, 11, 15, 22, 30, 42, 56, 77, 101, 135, 176, 231, 297, 385, 490, 627, 792, 1002, 1255, 1575, 1958, 2436,
1.有根树的数目:n+1个节点且高度小于等于2的有根树数目
2.方程非负解的数目:b+2c+3d+4e+…=n的非负解的数目,以及2c+3d+4e+…<=n的非负解的数目
3.整数分拆分方案
4.给你n个点,问你最多可以生成多少个不同构的树。这个树的深度最多为2.
代码见计数类DP 整数划分
斐波那契数列
通项公式:
性质:
1.求和公式
1.奇数项求和:\(f_1+f_3+...+f_{2n+1}=f_{2n}-f_2+f_1\)
2.偶数项求和:\(f_0+f_2+f_4+...+f_{2n}=f_{2n+1}-f_1\)
3.求和:\(f_0+f_1+f_2+...+f_n=f_{2n+2}-1\)
4.加减求和:\(f_0-f_1+f_2-...+(-1)^n*f_n=(-1)^n*(f_{n+1}-f_n)-1\)
5.平方求和:\(f_{1}^2+f_{2}^2+f_{3}^2+...+f_{n}^2=f_n*f_{n+1}\)
2.gcd 性质
1.\(gcd(f_m,f_n)=f_{gcd(m,n)}\)
2.\(gcd(f_{n+1},f_n)=1\)
3.隔项性质
1.和项数公式:\(f_{n+m}=f_{n+1}*f_m+f_n*f{m-1}\)
2.奇数项与某两项的平方:\(f_{2n+1}=f_{n+1}^2+f_n^2\)
3.偶数项与某两项的平方:\(f_{2n}=f_{n+1}^2-f_{n-1}^2\)
4.隔项关系:\(3*f_n=f_{n+2}+f_{n-2}\)
5.卡西尼性质:\(f_{n-1}*f_{n+1}-f_n^2=(-1)^n\)
9.容斥原理
奇数+,偶数-。
10.博弈论
公平组合游戏(Impartial Game)的定义如下:
- 游戏有两个人参与,二者轮流做出决策,双方均知道游戏的完整信息;
- 任意一个游戏者在某一确定状态可以作出的决策集合只与当前的状态有关,而与游戏者无关;
- 游戏中的同一个状态不可能多次抵达,游戏以玩家无法行动为结束,且游戏一定会在有限步后以非平局结束。
Nim 游戏
异或和为0,则先手必败,否则,先手必胜。
台阶Nim游戏
奇数层异或和为0,则先手必败,否则,先手必胜。
mex运算:设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:
mex(S) = min{x}, x属于自然数,且x不属于S
SG函数:
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:
SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。
\(SG(x)=0\)必败,\(SG(x)\neq 0\)必胜。
集合Nim游戏
每种情况对于一个SG值,那么我们将所有的SG值异或起来,如果异或值为0,那么必败。反之,必胜。
可以使用记忆化搜索来求SG值
int n,m;
int s[M],f[N];
int sg(int x){
if(f[x]!=-1) return f[x];
unordered_set<int> S;
for(int i=0;i<m;i++) if(x>=s[i]) S.insert(sg(x-s[i]));
for(int i=0;;i++){
if(!S.count(i)) return f[x]=i;
}
}
void Showball(){
cin>>m;
memset(f,-1,sizeof f);
for(int i=0;i<m;i++) cin>>s[i];
cin>>n;
int res=0;
while(n--){
int x;
cin>>x;
res^=sg(x);
}
if(res) puts("Yes");
else puts("No");
}
拆分Nim游戏
相比如集合Nim游戏,我们将一个局面拆分成两个,那么更新时,就需要更新两个局面SG值的异或。
int f[M];
int n;
int sg(int x){
if(f[x]!=-1) return f[x];
unordered_set<int> S;
for(int i=0;i<x;i++){
for(int j=0;j<=i;j++){
S.insert(sg(i)^sg(j));
}
}
for(int i=0;;i++){
if(!S.count(i)) return f[x]=i;
}
}
void Showball(){
cin>>n;
memset(f,-1,sizeof f);
int res=0;
while(n--){
int x;
cin>>x;
res^=sg(x);
}
if(res) puts("Yes");
else puts("No");
}
威佐夫博弈
问题描述:有两堆若干个物品,两个人轮流从任意一堆中取出至少一个或者同时从两堆中取出同样多的商品,规定每次至少取一个,至多不限,最后取光者胜利。
必败态:(0,0),(1,2)(3,5)(4,7)(6,10)
我们称这种局势为“‘奇异局势”。他们的差值是逐渐递增的。
结论:我们用\(a[k],b[k]\)来表示当前的状态。则\(a[k]=(int)(b[k]-a[k])*0.618\)
为了更加精确,\(0.618=(sqrt(5.0)+1)/2\)
11.常见性质:
如果\(a,b\)均为正整数且互质,那么由\(ax+by,x\geq0,y\geq0\)不能凑出的最大数是:\(ab-a-b\)。
五、动态规划(DP)
1.背包问题
01背包:每件物品最多只能用一次
状态表示:\(f[i][j]\)表示前i个物品中体积不超过j的最大价值
状态转移:\(f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])\)
滚动数组优化,二维变一维,第二层循环倒着写
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
完全背包:每件物品有无限多个
状态表示:\(f[i][j]\)表示前i个物品中体积不超过j的最大价值
状态转移:\(f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2*v[i]]+2*w[i],...,f[i-1][j-k*v[i]]+k*w[i])\)
我们发现\(f[i][j-v[i]]=max(f[i-1][j-2*v[i]]+w[i],f[i-1][j-3*v[i]]+2*w[i],...f[i-1][j-k*v[i]]+(k-1)*w[i])\)
所以\(f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])\)
我们发现和01背包特别相似,所以也可以用滚动数组优化,第二层循环正着写
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
多重背包:每件物品的数量是\(S_i\)个
状态表示:\(f[i][j]\)表示前i个物品中体积不超过j的最大价值
状态转移:\(f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i],k=[0,s_i]\)
二进制优化:把k打包成cnt个,每包只能取一次。那么问题就转化成了一个01背包。
vector<int> v,w,f(m+1);
for(int i=1;i<=n;i++){
int a,b,s;
cin>>a>>b>>s;
int k=1;
while(k<=s){
v.push_back(a*k);
w.push_back(b*k);
s-=k;
k*=2;
}
if(s>0){
v.push_back(a*s);
w.push_back(b*s);
}
}
//01背包
}
分组背包:每一组的物品不同,同组物品最多只能选一个
状态表示:\(f[i][j]\)表示前i组中选体积不超过j的最大价值
状态转移:\(f[i][j]=max(f[i-1][j],f[i-1][j-v[i][k]]+w[i][k])\)
同样可以滚动数组优化
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
for(int k=0;k<s[i];k++){
if(v[i][k]<=j){
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}
}
2.线性DP
数字三角形
状态表示:\(f[i][j]\)表示从起点到\((i,j)\)点的路径最大价值
状态转移:\(f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j])\)
for(int i=1;i<=n;i++){
for(int j=0;j<=n+1;j++){
f[i][j]=-inf;
}
}
f[1][1]=a[1][1];
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
}
}
int res=-inf;
for(int i=1;i<=n;i++) res=max(res,f[n][i]);
最长上升子序列
数据范围1000 复杂度\(O(n^2)\)
状态表示:\(f[i]\)表示以第i个数结尾的最长上升子序列的长度
状态转移:\(f[i]=max(f[j]+1),j\in[1,i-1]\&\&a[j]<a[i]\)
数据范围:100000 复杂度\(O(nlogn)\)
贪心+二分
int w[N];
int f[N];
int cnt=0;
void Showball(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>w[i];
}
f[cnt++]=w[0];
for(int i=1;i<n;i++){
if(w[i]>f[cnt-1]) f[cnt++]=w[i];
else{
int pos=lower_bound(f,f+cnt,w[i])-f;
f[pos]=w[i];
}
}
cout<<cnt<<endl;
}
最长公共子序列
状态表示:\(f[i][j]\)表示所有在第一个序列的前i个字母出现,且在第二个序列的前j个字母出现的子序列的最大长度
状态转移:\(f[i][j]=max(f[i-1][j],f[i][j-1],f[i-1][j-1]+1)\)
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
}
最长公共上升子序列
int n;
int a[3010],b[3010];
int f[3010][3010];
void Showball(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++){
int maxn=1;
for(int j=1;j<=n;j++){
f[i][j]=f[i-1][j];
if(a[i]==b[j]) f[i][j]=max(f[i][j],maxn);
if(a[i]>b[j]) maxn=max(maxn,f[i-1][j]+1);
}
}
int res=0;
for(int i=1;i<=n;i++) res=max(res,f[n][i]);
cout<<res<<endl;
}
编辑距离
将字符串A通过增,删,改三种操作变成字符串B的最小操作数
状态表示:\(f[i][j]\)表示A的前i个字母和B的前j个字母匹配的最小操作数
状态转移:\(f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1,f[i-1][j-1]+(a[i]\neq b[j]))\)
for(int i=1;i<=n;i++) f[i][0]=i;
for(int i=1;i<=m;i++) f[0][i]=i;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
}
}
3.区间DP
石子合并
状态表示:\(f[i][j]\)表示将第i堆到第j堆石子合并的最小代价
状态转移:\(f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1])\)
枚举区间长度(从小到大)
枚举左端点,得到右端点
\(ans=f[1][n]\)
vector<int> s(n+1);
vector f(n+1,vector<int>(n+1));
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++) s[i]+=s[i-1];
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int l=i,r=i+len-1;
f[l][r]=1e8;
for(int k=l;k<r;k++){
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
4.计数类DP
整数划分,即哈代-拉马努金拆分数列
完全背包解法:
状态表示:\(f[i][j]\)表示从前i个数中选体积等于j的方案数
状态转移:\(f[i][j]=f[i-1][j]+f[i-1][j-i]+f[i-1][j-2i]+...+f[i-1][j-si]\)
因为\(f[i][j-i]=f[i-1][j-i]+f[i-1][j-2i]+...f[i-1][j-si]\)
所以\(f[i][j]=f[i-1][j]+f[i][j-1]\)
ans=\(f[n]\)
同理也可以优化
void Showball(){
int n;
cin>>n;
vector<int> f(n+1);
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
f[j]=(f[j]+f[j-i])%mod;
}
}
cout<<f[n]<<endl;
}
另外一种方法:
状态表示:\(f[i][j]\)表示总和为i,并且数量个数为j的方案数
状态转移:\(f[i][j]=f[i-1][j-1]+f[i-j][j]\)
分成最小值等于1和最小值大于1两种情况
ans=\(f[n][1]+f[n][2]+...+f[n][n]\)
void Showball(){
int n;
cin>>n;
vector f(n+1,vector<int>(n+1));
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
f[i][j]=(f[i-1][j-1]+f[i-j][j])%mod;
}
}
int res=0;
for(int i=1;i<=n;i++) res+=f[n][i];
cout<<res<<endl;
}
5.数位统计DP
目前只学习了按照数位来进行统计
统计a到b之间每个数字(0-9)出现的次数
int dgt(int n){
int res=0;
while(n){
res++;
n/=10;
}
return res;
}
int calc(int n,int x){
int res=0,d=dgt(n);
for(int i=1;i<=d;i++){
int p=pow(10,i-1),l=n/p/10,r=n%p,di=n/p%10;
//第一种情况:左边的整数小于l
if(x) res+=l*p;
if(!x&&l) res+=(l-1)*p;
//第二种情况:左边的整数等于l
if((di>x)&&(x||l)) res+=p;
if(di==x&&(x||l)) res+=r+1;
}
return res;
}
void Showball(){
int a,b;
while(cin>>a>>b,a){
if(a>b) swap(a,b);
for(int i=0;i<10;i++) cout<<calc(b,i)-calc(a-1,i)<<" ";
cout<<endl;
}
}
6.状态压缩DP
利用二进制来表达和枚举所有状态,达到状态压缩的目的。
蒙德里安的梦想
状态表示:\(f[i][j]\)表示前i-1列状态是j的操作方案数
状态转移:\(f[i][j]=f[i-1][k]之和\)
我们需要去预处理出所有符合条件的k。需要满足两列之间不可以相交,并且连续的空白数必须是偶数
const int N = 12, M = 1<<N;
bool st[M];
vector<int> state[M];
LL f[N][M];
void Showball(){
int n,m;
while(cin>>n>>m,n||m){
//预处理连续空白格为偶数的情况
for(int i=0;i<1<<n;i++){
bool flg=true;
int cnt=0;
for(int j=0;j<n;j++){
if(i>>j&1) {
if(cnt&1){
flg=false;
break;
}
}
else cnt++;
}
if(cnt&1) flg=false;
st[i]=flg;
}
//预处理所有满足情况的状态
for(int i=0;i<1<<n;i++){
state[i].clear();
for(int j=0;j<1<<n;j++){
if((i&j)==0&&st[i|j]) state[i].push_back(j);
}
}
memset(f,0,sizeof f);
for(int i=0;i<=m;i++){
for(int j=0;j<1<<n;j++){
for(auto k:state[j]){
f[i][j]+=f[i-1][k];
}
}
}
cout<<f[m][0]<<endl;
}
}
最短哈密顿回路
给你每个点到其他点的边权,计算最短哈密顿回路的最短距离。哈密顿回路就是所有点必须都经过,并且只经过一次。
状态表示:\(f[i][j]\)表示从0到j经过状态是i的路径的最小权值。
我们用i这个二进制数来表示每个节点是经过还是未经过。
状态转移:我们通过枚举倒数第二个点k,来更新当前点。从1到k,再k到j。
\(f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j])\)
再通过经典二进制枚举去进行状态压缩枚举即可
通过集合的定义:\(ans=f[(1<<n)-1][n-1]\)
const int N=20,M=1<<N;
int f[M][N];
int w[N][N];
void Showball(){
int n;
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>w[i][j];
}
}
memset(f,0x3f,sizeof f);
f[1][0]=0;
for(int i=0;i<1<<n;i++){
for(int j=0;j<n;j++){
if(i>>j&1){
for(int k=0;k<n;k++){
if((i-(1<<j))>>k&1){
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
}
}
}
}
}
cout<<f[(1<<n)-1][n-1]<<endl;
}
7.树形DP
顾名思义,就是在树上进行DP,一般都要涉及递归
没有上司的舞会
状态表示:\(f[u][0/1]\)表示在u的所有子树里去计算(包含/不包含)u这个结点。
状态计算:\(f[u][0]+=max(f[s_i][0],f[s_i][1])\)
\(f[u][1]+=f[s_i][0]\)
int hp[N];
int f[N][2];
bool has_fa[N];
int n;
int h[N],e[N],ne[N],idx;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u){
f[u][1]=hp[u];
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
dfs(j);
f[u][0]+=max(f[j][0],f[j][1]);
f[u][1]+=f[j][0];
}
}
void Showball(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>hp[i];
memset(h,-1,sizeof h);
for(int i=0;i<n-1;i++){
int a,b;
cin>>a>>b;
has_fa[a]=true;
add(b,a);
}
int root=1;
while(has_fa[root]) root++;
dfs(root);
cout<<max(f[root][0],f[root][1])<<endl;
}
8.记忆化搜索
当dp的状态不好枚举时,用dfs搜索更新值,更加好写方便。
六、贪心
区间问题
区间选点&最大不相交区间数量
将区间按照右端点排序,从左到右遍历区间,每次选左端点,被覆盖的区间就pass
struct node{
int l,r;
bool operator<(const node &a)const{
return r<a.r;
}
}a[N];
void Showball(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
a[i]={l,r};
}
sort(a,a+n);
int res=0,ed=-2e9;
for(int i=0;i<n;i++){
if(a[i].l>ed){
res++;
ed=a[i].r;
}
}
cout<<res<<endl;
}
区间分组
问题描述:将相交的区间分成不同组,使得组数最小
将区间按照左端点排序,维护每个组的max_r。从左到右枚举所有区间,如果区间的左端点和目前所有组的某个组有交集,那么就新开一个组,否则就随便放进一个组。用小根堆来维护当前所有区间的max_r
struct node{
int l,r;
bool operator<(const node &a)const{
return l<a.l;
}
}a[N];
void Showball(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int l,r;
cin>>l>>r;
a[i]={l,r};
}
sort(a,a+n);
priority_queue<int,vector<int>,greater<int>> heap;
for(int i=0;i<n;i++){
if(heap.size()&&heap.top()<a[i].l) heap.pop();
heap.push(a[i].r);
}
cout<<heap.size()<<endl;
}
区间覆盖
问题描述:给定一些区间和一个目标区间,让我们使用最少的给定区间去覆盖目标区间
将区间按照左端点从小到大排序
枚举每个区间在所有能够覆盖start的区间中,选择右端点最大的,并更新start
int res=0;
bool ok=false;
for(int i=0;i<n;i++){
int j=i,r=-2e9;
while(j<n&&a[j].l<=st){
r=max(r,a[j].r);
j++;
}
if(r<st)break;
res++;
if(r>=ed){
ok=true;
break;
}
st=r;
j=i-1;
}
货仓选址
问题描述:坐标上有n个点,选取一个点,让n个点到这个点距离之和最小
思路:排序选中位数的点作为这个点求距离即可
耍杂技的牛
问题描述:牛牛叠罗汉,每个牛牛有体重和承受力。风险等于自己上面的所有体重之和减去自己的承受力。
安排牛牛的顺序,使得牛牛的风险的最大值最小。
思路:将牛牛按照体重+承受力之和从小到大排序
七.字符串
1.循环同构最小表示法
//最小表示法模板
int minStr(string s){
int i = 0, j = 1, k = 0, n = s.size();
while(i < n && j < n && k < n) {
int a = s[(i + k) % n], b = s[(j + k) % n];
if(a == b) k ++;
else {
if(a > b) i += k + 1; else j += k + 1;
if(i == j) i ++;
k = 0;
}
}
return min(i, j);
}
八.计算几何
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cassert>
#include <utility>
namespace Geometry {
using namespace std;
typedef double real; // 需要时可以转为long double
const real EPS = 1e-6; // 精度控制
const real PI = acos((real)-1);
// 浮点数比较
// @param x: 任意实数
// @return 0: x==0, 1: x>0, -1: x<0
int dcmp(real x) {
return (x>EPS) - (x<-EPS);
}
int dcmp(real x, real y) {
return dcmp(x - y);
}
struct Point {
real x, y, z;
int id;
Point(real x = 0, real y = 0, real z = 0) :x(x), y(y), z(z) {}
// 强制转换为real,表示点到原点距离
operator real() const {
return sqrt(x*x + y * y + z * z);
}
// 平面向量与x轴正向夹角
// @return 方向角,(-pi, pi]
real direction() const {
return atan2(y, x);
}
};
typedef Point Vector;
Vector operator+ (Vector a, Vector b) {
return Vector(a.x + b.x, a.y + b.y, a.z + b.z);
}
Vector operator- (Vector a, Vector b) {
return Vector(a.x - b.x, a.y - b.y, a.z - b.z);
}
Vector operator- (Vector a) {
return Vector(-a.x, -a.y, -a.z);
}
Vector operator* (Vector a, real b) {
return Vector(a.x*b, a.y*b, a.z*b);
}
Vector operator* (real a, Vector b) {
return Vector(a*b.x, a*b.y, a*b.z);
}
// 向量点积
real operator* (Vector a, Vector b) {
return a.x*b.x + a.y*b.y + a.z*b.z;
}
Vector operator/ (Vector a, real b) {
return Vector(a.x / b, a.y / b, a.z / b);
}
// 向量叉积
Vector cross(Vector a, Vector b) {
return Vector(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x);
}
// 平面向量叉积的值
real Cross(Vector a, Vector b) {
return a.x*b.y - a.y*b.x;
}
// 向量混合积=axb*c
real mix(Vector a, Vector b, Vector c) {
return cross(a, b)*c;
}
// 绕z轴旋转向量
Vector rotate(Vector v, real theta) {
return Vector(v.x*cos(theta) - v.y*sin(theta), v.x*sin(theta) + v.y*cos(theta), v.z);
}
real distance(Point a, Point b) {
return (real)(a - b);
}
// 平面向量v1->v2的旋转角
// @return (-pi, pi]
real rotAngle(Vector v1, Vector v2) {
real rst = v2.direction() - v1.direction();
if (rst>PI) rst -= PI * 2;
if (rst <= -PI) rst += PI * 2;
return rst;
}
// 向量夹角
// @return [0, pi]
real angle(Vector v1, Vector v2) {
real costheta = v1 * v2 / (real)v1 / (real)v2;
return acos(costheta);
}
// 方向和长度生成向量
Vector zoom(Vector v, real length) {
return v * length / (real)v;
}
// 有向直线,方向为a->b
struct Line {
Point a, b;
Line(Point a = Point(), Point b = Point()) :a(a), b(b) {}
// 方向向量
Vector direction() const {
return b - a;
}
};
// 点到直线距离
real distance(Point p, Line l) {
return abs((real)cross(l.b - l.a, p - l.a)) / real(l.b - l.a);
}
// 两直线位置关系
// @return 0:平行 1:相交 2:异面
int position(Line l1, Line l2) {
Vector v1 = l1.direction(), v2 = l2.direction();
if (dcmp((real)cross(v1, v2)) == 0) return 0;
if (dcmp(mix(l2.a - l1.a, v1, v2)) == 0) return 1;
return 2;
}
// 两直线交点
Point intersect(Line l1, Line l2) {
assert(position(l1, l2) == 1);
Vector c1 = cross(l1.direction(), l2.a - l1.a), c2 = cross(l1.direction(), l2.direction());
real sgn = dcmp(c1*c2)>0 ? -1 : 1;
return l2.a + l2.direction() * (real)c1 / (real)c2 * sgn;
}
// 判断三点共线
bool colinear(Point a, Point b, Point c) {
return dcmp((real)cross(b - a, c - a)) == 0;
}
// 平面点与直线的位置关系
// 0: 在直线上, 1: 点在直线左侧, -1: 点在直线右侧
int position(Point p, Line l) {
return dcmp(Cross(l.b - l.a, p - l.a));
}
// 常用坐标比较函数
bool cmpxyz(Point a, Point b) {
if (dcmp(a.x - b.x) != 0) return a.x<b.x;
else if (dcmp(a.y - b.y) != 0) return a.y<b.y;
else return dcmp(a.z - b.z)<0;
}
// 有向线段
struct Segment {
Point a, b;
Segment(Point a = Point(), Point b = Point()) : a(a), b(b) {}
// 方向向量
Vector direction() const {
return b - a;
}
// 线段长度
operator real() const {
return (real)direction();
}
};
// 点到线段距离
real distance(Point p, Segment seg) {
Vector ap = p - seg.a, bp = p - seg.b;
if (ap*(seg.b - seg.a) <= 0) return (real)ap;
if (bp*(seg.a - seg.b) <= 0) return (real)bp;
return distance(p, Line(seg.a, seg.b));
}
// 点和线段的位置关系
// @return true:在线段上 false:不在
bool position(Point p, Segment seg) {
return dcmp(distance(p, seg)) == 0;
}
// 线段相交,端点处相交也算
bool intersected(Segment a, Segment b) {
if (position(Line(a.a, a.b), Line(b.a, b.b)) != 1) return false;
return dcmp(cross(a.direction(), b.a - a.a)*cross(a.direction(), b.b - a.a)) <= 0 &&
dcmp(cross(b.direction(), a.a - b.a)*cross(b.direction(), a.b - b.a)) <= 0;
}
// 平面简单多边形
struct Polygon2D {
vector<Point> vtx;
// 按逆时针顺序给出顶点
Polygon2D(vector<Point> vertex = vector<Point>()) :vtx(vertex) {}
// 第i条边
// @param i: 0~n-1
Segment side(int i) const {
if (i == vtx.size() - 1) return Segment(vtx[vtx.size() - 1], vtx[0]);
return Segment(vtx[i], vtx[i + 1]);
}
// 面积
real area() const {
real rst = 0;
int sz = vtx.size();
for (int i = 0; i<sz; i++) {
rst += Cross(vtx[i], vtx[(i + 1) % sz]);
}
return rst / 2;
}
// 重心
Point cofg() const {
Point rst;
real ar = 0;
int sz = vtx.size();
for (int i = 0; i<sz; i++) {
real temp = Cross(vtx[i], vtx[(i + 1) % sz]);
rst = rst + (vtx[i] + vtx[(i + 1) % sz])*temp;
ar += temp;
}
return rst / ar / 3.;
}
// 周长
real circumference() const {
real rst = 0;
int sz = vtx.size();
for (int i = 0; i<sz; i++) {
rst += (real)side(i);
}
return rst;
}
// 凸包算法将点按逆时针排序
void arrange() {
sort(vtx.begin(), vtx.end(), cmpxyz);
vector<Point> p;
for (const Point& it : vtx) {
while (p.size() >= 2) {
Line last(p[p.size() - 2], p[p.size() - 1]);
if (position(it, last) == -1) {
p.pop_back();
}
else break;
}
p.push_back(it);
}
for (vector<Point>::const_reverse_iterator it = ++vtx.rbegin(); it != vtx.rend(); ++it) {
while (p.size() >= 2) {
Line last(p[p.size() - 2], p[p.size() - 1]);
if (position(*it, last) == -1) {
p.pop_back();
}
else break;
}
p.push_back(*it);
}
p.pop_back();
vtx = move(p);
}
};
// 点与多边形的位置关系
// @return -1:内 0:上 1:外
int position(Point p, Polygon2D c) {
int n = c.vtx.size();
int cnt = 0;
for (int i = 0; i < n; i++) {
Segment seg = c.side(i);
if (position(p, seg)) return 0;
int k = dcmp(Cross(seg.direction(), p - seg.a));
int d1 = dcmp(seg.a.y, p.y);
int d2 = dcmp(seg.b.y, p.y);
if (k>0 && d1 <= 0 && d2>0) cnt++;
if (k<0 && d2 <= 0 && d1>0) cnt--;
}
if (cnt) return -1;
else return 1;
}
// 平面圆
struct Circle2D {
Point ct;
real r;
Circle2D(Point center = Point(), real radius = 0) :ct(center), r(radius) {}
// 过三点的圆
Circle2D(Point a, Point b, Point c) {
real x1 = a.x, y1 = a.y, x2 = b.x, y2 = b.y, x3 = c.x, y3 = c.y;
real a11 = 2 * (x3 - x2);
real a12 = 2 * (y3 - y2);
real a21 = 2 * (x2 - x1);
real a22 = 2 * (y2 - y1);
real b1 = x3 * x3 - x2 * x2 + y3 * y3 - y2 * y2;
real b2 = x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1;
real d = a11 * a22 - a12 * a21;
real d1 = b1 * a22 - a12 * b2;
real d2 = a11 * b2 - b1 * a21;
ct = Point(d1 / d, d2 / d);
r = distance(a, ct);
}
// 面积
real area() const {
return PI * r*r;
}
// 周长
real circumference() const {
return 2 * PI*r;
}
};
// 点与圆的位置关系
// @return -1:点在圆内 0:点在圆上 1:点在圆外
int position(Point p, Circle2D c) {
return dcmp((real)(p - c.ct), c.r);
}
// 直线与圆的位置关系
// @return -1:相交 0:相切 1:相离
int position(Line l, Circle2D c) {
return dcmp(distance(c.ct, l), c.r);
}
// 两圆的位置关系
// @return 0:内含 1:内切 2:相交 3:外切 4:相离
int position(Circle2D a, Circle2D b) {
real d = distance(a.ct, b.ct);
int cmp1 = dcmp(d, a.r + b.r), cmp2 = dcmp(d, abs(a.r - b.r));
if (cmp1 >= 0) return cmp1 + 3;
else return cmp2 + 1;
}
// 圆与直线交点
pair<Point, Point> intersect(Line l, Circle2D c) {
real x0 = c.ct.x, y0 = c.ct.y, r = c.r;
real x1 = l.a.x, y1 = l.a.y;
real x2 = l.b.x, y2 = l.b.y;
real dx = x2 - x1, dy = y2 - y1;
real A = dx * dx + dy * dy;
real B = 2 * dx*(x1 - x0) + 2 * dy*(y1 - y0);
real C = (x1 - x0)*(x1 - x0) + (y1 - y0)*(y1 - y0) - r * r;
real delta = B * B - 4 * A*C;
delta = max((real)0, delta); // 更好地处理相切情况
real t1 = (-B - sqrt(delta)) / 2 / A;
real t2 = (-B + sqrt(delta)) / 2 / A;
return make_pair(Point(x1 + t1 * dx, y1 + t1 * dy), Point(x1 + t2 * dx, y1 + t2 * dy));
}
// @deprecated
// 平面上凸包
// @return 返回上凸包上的点,从左至右,不包含共线点和重合点
vector<Point> hull2DTop(vector<Point> points) {
sort(points.begin(), points.end(), cmpxyz);
vector<Point> rst;
for (const Point& it : points) {
while (rst.size() >= 2) {
if (position(it, Line(rst[rst.size() - 2], rst[rst.size() - 1])) >= 0) {
rst.pop_back();
}
else
break;
}
rst.push_back(it);
}
return rst;
}
// @deprecated
// 平面下凸包
// @return 返回下凸包上的点,从左至右,不包含共线点和重合点
vector<Point> hull2DBottom(vector<Point> points) {
sort(points.begin(), points.end(), cmpxyz);
vector<Point> rst;
for (const Point& it : points) {
while (rst.size() >= 2) {
if (position(it, Line(rst[rst.size() - 2], rst[rst.size() - 1])) <= 0) {
rst.pop_back();
}
else
break;
}
rst.push_back(it);
}
return rst;
}
// 平面凸包
Polygon2D hull2D(vector<Point> points) {
Polygon2D rst(points);
rst.arrange();
return rst;
}
// 平面,由平面上一点和法向量确定
struct Plane {
Point on;
Vector norm;
Plane(Point on = Point(), Vector normal = Vector()) :on(on), norm(normal) {}
// 三点确定平面
Plane(Point a, Point b, Point c) :on(a), norm(cross(b - a, c - a)) {}
};
// 点到平面距离,注意有正负号
real distance(Point p, Plane alpha) {
return (p - alpha.on)*alpha.norm / (real)alpha.norm;
}
// 点与平面位置关系
// @return 0:在平面内 1:在平面正侧 -1:在平面背侧
int position(Point p, Plane alpha) {
return dcmp((p - alpha.on)*alpha.norm);
}
// 直线与平面位置关系
// @return 0:共面 1:平行 2:相交
int position(Line l, Plane alpha) {
if (dcmp(l.direction()*alpha.norm) == 0) {
if (dcmp((l.a - alpha.on)*alpha.norm) == 0)
return 0;
else
return 1;
}
else
return 2;
}
// 点在平面上的投影
Point projection(Point p, Plane alpha) {
return p - zoom(alpha.norm, distance(p, alpha));
}
// 点在直线上的投影
Point projection(Point p, Line l) {
if (colinear(p, l.a, l.b)) return p;
Vector temp = cross(l.direction(), p - l.a);
Point c = l.a + temp;
Plane A(l.a, l.b, c);
return projection(p, A);
}
// 直线左侧表示半平面
typedef Line HalfPlane;
// 半平面交构成凸多边形
Polygon2D intersect(vector<HalfPlane> hps) {
/* 不保证半平面封闭时添加这些
const real INF = 1e50;
hps.emplace_back(Point(-INF, -INF), Point(INF, -INF));
hps.emplace_back(Point(INF, -INF), Point(INF, INF));
hps.emplace_back(Point(INF, INF), Point(-INF, INF));
hps.emplace_back(Point(-INF, INF), Point(-INF, -INF));
*/
// 极角排序
sort(hps.begin(), hps.end(), [](const HalfPlane& a, const HalfPlane& b) {
if (dcmp(a.direction().direction(), b.direction().direction()) != 0)
return a.direction().direction()<b.direction().direction();
else
return position(a.a, b) == 1;
});
// 极角相同保留左侧那一个
auto uend = unique(hps.begin(), hps.end(), [](const HalfPlane& a, const HalfPlane& b) {
return dcmp(a.direction().direction(), b.direction().direction()) == 0;
});
HalfPlane* que = new HalfPlane[uend - hps.begin()];
HalfPlane* frt = que, *bak = que;
for (auto it = hps.begin(); it != uend; ++it) {
while (bak - frt >= 2) {
if (position(intersect(*(bak - 1), *(bak - 2)), *it) == 1)
break;
else
--bak;
}
while (bak - frt >= 2) {
if (position(intersect(*frt, *(frt + 1)), *it) == 1)
break;
else
++frt;
}
*bak++ = *it;
if (bak - frt >= 2 && position(*(bak - 1), *(bak - 2)) != 1) { delete[] que; return Polygon2D(); }
}
// 最后用队首检查队尾
while (bak - frt >= 2) {
if (position(intersect(*(bak - 1), *(bak - 2)), *frt) == 1)
break;
else
--bak;
}
if (bak - frt <= 2) { delete[] que; return Polygon2D(); }
Polygon2D rst;
int n = bak - frt;
rst.vtx.resize(n);
for (int i = 0; i < n; i++) {
rst.vtx[i] = intersect(frt[i], frt[(i + 1) % n]);
}
delete[] que;
return rst;
}
// *凸*多边形交
Polygon2D intersect(Polygon2D a, Polygon2D b) {
int n1 = a.vtx.size(), n2 = b.vtx.size();
vector<HalfPlane> hps(n1 + n2);
for (int i = 0; i < n1; i++) {
hps[i] = HalfPlane(a.vtx[i], a.vtx[(i + 1) % n1]);
}
for (int i = 0; i < n2; i++) {
hps[n1 + i] = HalfPlane(b.vtx[i], b.vtx[(i + 1) % n2]);
}
return intersect(hps);
}
// 圆的切线
pair<Line, Line> tangent(Point p, Circle2D c) {
real sina = c.r / distance(p, c.ct);
sina = min((real)1, sina); // 更好地处理点在圆上的情况
Vector v = c.ct - p;
Vector v1 = rotate(v, asin(sina));
Vector v2 = rotate(v, -asin(sina));
return make_pair(Line(p, p + v1), Line(p, p + v2));
}
// 圆与顶点在圆心的三角形交的面积
real intersect(Point a, Point b, Circle2D c) {
if (position(a, c) <= 0 && position(b, c) <= 0) {
Vector ca = a - c.ct, cb = b - c.ct;
return abs(Cross(ca, cb)) / 2;
}
else if (position(a, c) <= 0 || position(b, c) <= 0) {
if (position(b, c) <= 0) swap(a, b); // a在内部,b在外部
Vector ca = a - c.ct, cb = b - c.ct;
pair<Point, Point> inter = intersect(Line(a, b), c);
Point d = distance(inter.first, b)<distance(inter.second, b) ? inter.first : inter.second; // 取靠近b的交点
return (abs(Cross(ca, d - c.ct)) + angle(cb, d - c.ct)*c.r*c.r) / 2;
}
else {
if (position(Line(a, b), c) < 0) {
pair<Point, Point> inter = intersect(Line(a, b), c);
Point aa = inter.first, bb = inter.second;
if (distance(a, aa)>distance(a, bb)) swap(aa, bb);
Vector ca = a - c.ct, cb = b - c.ct;
Vector caa = aa - c.ct, cbb = bb - c.ct;
return (abs(Cross(caa, cbb)) + (angle(ca, caa) + angle(cb, cbb))*c.r*c.r) / 2;
}
else {
Vector ca = a - c.ct, cb = b - c.ct;
return angle(ca, cb)*c.r*c.r / 2;
}
}
}
}
using namespace Geometry;
九.杂项
1.时间复杂度
2.匿名函数
//不用递归
auto 函数名 = [&](参数列表){}
//递归b
function<返回类型(参数列表)> 函数名 = [&](参数列表){}
3.二进制枚举
对于一些题目,是有关状态的枚举,那么我们可以用0,1来表示不同状态,不同状态的集合一共有\(2^n\)种,所以用2进制枚举可以更好的表示状态。
//比如n个物品有选或者不选两种状态
for(int i=0;i<(i<<n);i++){
for(int j=0;j<n;j++){
if(i&(1<<j)){
//work
}
}
}
4.距离
1.欧几里得距离:\(d=\sqrt{(x1-x2)^2+(y1-y2)^2}\)
2.曼哈顿距离:\(d=|x1-x2|+|y1-y2|\)
3.切雪比夫距离:\(d=max(|x1-x2|,|y1-y2|)\)
5.自动取模类
在一些组合计数问题中,由于答案很大,一般都会将结果对于一个质数取模。自动取模类,可以更加方便的进行mod条件下的基本运算
template<const int T>
struct ModInt {
const static int mod = T;
int x;
ModInt(int x = 0) : x(x % mod) {}
ModInt(long long x) : x(int(x % mod)) {}
int val() { return x; }
ModInt operator + (const ModInt &a) const { int x0 = x + a.x; return ModInt(x0 < mod ? x0 : x0 - mod); }
ModInt operator - (const ModInt &a) const { int x0 = x - a.x; return ModInt(x0 < 0 ? x0 + mod : x0); }
ModInt operator * (const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
ModInt operator / (const ModInt &a) const { return *this * a.inv(); }
bool operator == (const ModInt &a) const { return x == a.x; };
bool operator != (const ModInt &a) const { return x != a.x; };
void operator += (const ModInt &a) { x += a.x; if (x >= mod) x -= mod; }
void operator -= (const ModInt &a) { x -= a.x; if (x < 0) x += mod; }
void operator *= (const ModInt &a) { x = 1LL * x * a.x % mod; }
void operator /= (const ModInt &a) { *this = *this / a; }
friend ModInt operator + (int y, const ModInt &a){ int x0 = y + a.x; return ModInt(x0 < mod ? x0 : x0 - mod); }
friend ModInt operator - (int y, const ModInt &a){ int x0 = y - a.x; return ModInt(x0 < 0 ? x0 + mod : x0); }
friend ModInt operator * (int y, const ModInt &a){ return ModInt(1LL * y * a.x % mod);}
friend ModInt operator / (int y, const ModInt &a){ return ModInt(y) / a;}
friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x;}
friend istream &operator>>(istream &is, ModInt &t){return is >> t.x;}
ModInt pow(int64_t n) const {
ModInt res(1), mul(x);
while(n){
if (n & 1) res *= mul;
mul *= mul;
n >>= 1;
}
return res;
}
ModInt inv() const {
int a = x, b = mod, u = 1, v = 0;
while (b) {
int t = a / b;
a -= t * b; swap(a, b);
u -= t * v; swap(u, v);
}
if (u < 0) u += mod;
return u;
}
};
typedef ModInt<mod> mint;
6.矩阵类
const int SZ=105;
const LL M=1000000007;
typedef long long mytype;
LL quickpow(LL a, LL b)
{
if(b < 0) return 0;
LL ret = 1;
a %= M;
for (; b; b >>= 1, a = (a * a) % M)
if (b & 1)
ret = (ret * a) % M;
return ret;
}
LL inv(LL a)
{
return quickpow(a,M-2);
}
struct mat
{
int n,m;
mytype a[SZ][SZ];
void init()
{
memset(a,0,sizeof(a));
}
mat(int n=SZ,int m=SZ):n(n),m(m) {}
mat unit()
{
mat t(n,n);
t.init();
for (int i=0; i<n; i++)
t.a[i][i]=1;
return t;
}
mytype *operator [](int n)
{
return *(a+n);
}
mat operator +(const mat &b)
{
mat t(n,m);
for (int i=0; i<n; i++)
for (int j=0; j<m; j++)
t.a[i][j]=(a[i][j]+b.a[i][j]+M)%M;
return t;
}
mat operator -(const mat &b)
{
mat t(n,m);
for (int i=0; i<n; i++)
for (int j=0; j<m; j++)
t.a[i][j]=(a[i][j]-b.a[i][j]+M)%M;
return t;
}
mat operator *(const mat &b)
{
mat t(n,b.m);
for(int i=0; i<n; i++)
for(int j=0; j<b.m; j++)
{
t.a[i][j]=0;
for(int k=0; k<m; k++)
t.a[i][j]=(t.a[i][j]+(a[i][k]*b.a[k][j])%M)%M;
}
return t;
}
mat operator *(const mytype &b)
{
mat t(n,m);
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
t.a[i][j]=a[i][j]*b%M;
return t;
}
mat operator /(const mytype &b)
{
mat t(n,m);
for(int i=0; i<n; i++)
for(int j=0; j<m; j++)
t.a[i][j]=a[i][j]*inv(b)%M;
return t;
}
mat operator !()
{
mat t(m,n);
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
t.a[i][j]=a[j][i];
return t;
}
mytype det()
{
}
mat invm(mat &a)
{
}
friend mat quickpow(mat a, mytype b)
{
if(b<0) return a.unit();
mat ret=a.unit();
for (; b; b>>=1,a=a*a)
if (b&1)
ret=ret*a;
return ret;
}
void in()
{
for (int i=0; i<n; i++)
for (int j=0; j<m; j++)
scanf("%lld",&a[i][j]);
}
void out()
{
for (int i=0; i<n; i++)
for (int j=0; j<m; j++)
printf("%lld%c",a[i][j]," \n"[j==m-1]);
}
};
7.开根号
精度!!!
LL n;
cin>>n;
LL x=sqrt(n);
while(x*x<n) x++;
while((x-1)*(x-1)>=n) x--;
取对数
int f[N];//2为底__lg()更快
for(int i = b; i < N; i++)
f[i] = f[i / b] + 1;//预处理以b为底i的对数下取整
8.Trick:
针对一些我们需要排序后的结果,还需要原数组时,这样写可以不用去复制一个新数组。
vector<int> a(n),id(n);
for(int i=0;i<n;i++) cin>>a[i],id[i]=i;
sort(all(id),[&](int x,int y){
return a[x]<a[y];
});
最大子段和
int n;
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int ans=-1e9,sum=0;
for(int i=0;i<n;i++){
if(sum<=0) sum=0;
sum+=a[i];
ans=max(ans,sum);
}
cout<<ans<<endl;
形如这样的式子求最大值,直接取两个端点值即可。
以某个点为左端点的区间中 区间与的值只会在O(logn)个
9.随机数
random_device rd; // 用于获得随机数种子
mt19937 gen(rd()); // 使用Mersenne Twister随机数引擎
uniform_int_distribution<int> dis(1, n); // 生成1到100之间的均匀分布的整数
int cnt=0;
int x=dis(gen);
10.构造
正整数可以分成奇数和偶数。
偶数有两种,一种模4余0,一种模4余2。注意模4余2这种还有一个性质就是/2是奇数。
一组觉得有趣的排列构造
1
1 2
2 1 3
3 1 2 4
2 4 1 3 5
3 4 1 5 2 6
struct node{
int l,r;
bool operator<(const node&u) const{
int len=r-l+1,len2=u.r-u.l+1;
if(len==len2) return l>u.l;
else return len<len2;
}
};
void Showball(){
int n;
cin>>n;
vector<int> a(n+1);
priority_queue<node> q;
q.push({1,n});
for(int i=1;i<=n;i++){
auto t=q.top();
q.pop();
int mid=t.l+t.r>>1;
a[mid]=i;
if(t.l<=mid-1) q.push({t.l,mid-1});
if(mid+1<=t.r) q.push({mid+1,t.r});
}
for(int i=1;i<=n;i++) cout<<a[i]<<" \n"[i==n];
}
11.vector的定义
//一维
vector<int> a;
vector<int> a(n,val);
//二维
vector<int> a[n];
vector<vector<int> > a;
vector<vector<int> > a(n,vector<int>(m,val));
vector a(n,vector<int>(m));
12.数论分块
公式:\(\lceil \frac{a}{b} \rceil= \lfloor \frac{a-1}{b} \rfloor+1\)
\(a\ mod\ b=a-\lfloor\frac{a}{b}\rfloor*b\)
根号下取整求和
Code
LL getSum(int x) {
int sq = sqrt(x), sqt = sq - 1;
return 1LL*sqt * (sqt + 1) * (sqt * 2 + 1) / 3 + 1LL*sqt * (sqt + 1) / 2 + 1LL*sq * (x - 1LL*sq * sq + 1);
}
// for(int i = st;i<=ed;i++)ans+=num/i
int block(int st, int ed, int num) {
//sum(num/i i in [st,ed])
int L = 0;
int _ans = 0;
ed = min(ed, num);
for (int i = st; i <= ed; i = L + 1) {
L = min(ed,num / (num / i)); //该区间的最后一个数
_ans += (L - i + 1)*(num / i);//区间[i,L]的num/i 都是一个值
}
return _ans;
}
双指针写法
for(int i=0,j=0;i<n;){
while(j<n&&nums[j]==nums[i]) j++;
nums[cnt++]=nums[i];
i=j;
}
//j-i是连续满足条件元素数量