1.基础
二分
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
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;
}
快速幂
// a^b%c
ll qpow(ll a, ll b, ll c)
{
ll res = 1;
while(b) {
if(b & 1) res = res * a % c;
a = a * a % c;
b >>= 1;
}
return res;
}
离散化
// a为原数组,b存储a的排序去重,c存储a的离散值
int a[maxn],b[maxn],c[maxn];
int n,m;
// 将a离散
void lisan(){
for(int i=1;i<=n;i++) b[i]=a[i];
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-(b+1);
for(int i=1;i<=n;i++) c[i]=lower_bound(b+1,b+1+m,a[i])-(b+1);
}
树上倍增查找
// fa[i][j]:i结点的第2^j级祖先
// 其中a[i]为结点i的权值,a数组递增
int fa[maxn][20];
for (int j=1;j<=18;j++)
for (int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
// x:自给定的x结点起,查找链上权值小于等于r的最大结点
for (int i=18;i>=0;i--) if (a[fa[x][i]]<=r) x=fa[x][i];
2.字符串
字符串hash
//自然溢出法
//统计前缀出现次数
#define base 131
#define ull unsigned long long
unordered_map<ull,int> mp;
string a;
void hash(char a[]){
int len=strlen(a);
ull t=0;
for(int i=0;i<len;i++){
t=t*base+a[i]-'a'+1;
mp[t]++;
}
}
//统计后缀出现次数
#define base 131
#define ull unsigned long long
unordered_map<ull,int> mp;
string a;
void hash(char a[]){
int len=strlen(a);
ull t=0,p=1;
for(int i=len-1;i>=0;i--){
t=t+p*(a[i]-'a'+1);
p*=base;
mp[t]++;
}
}
KMP
// 最小循环节长度公式:minlen=len-ne[len-1]
//ne[i]:第i个前缀的最长公共前后缀长度
int ne[maxn];
void getne(char* a){
ne[0]=0;
int len=strlen(a);
for(int i=1,j=0;i<len;i++){
while(a[j]!=a[i]&&j) j=ne[j-1];
if(a[i]==a[j]) j++;
ne[i]=j;
}
}
//a:主串,b:模式串
void find(char* a,char* b){
getne(b);
int lena=strlen(a),lenb=strlen(b);
for(int i=0,j=0;i<lena;i++){
while(b[j]!=a[i]&&j) j=ne[j-1];
if(a[i]==b[j]) j++;
if(j==lenb) printf("%lld\n",i-lenb+2);
}
}
Trie(前缀树、字典树)
struct trie{
int ch[maxn][30]; //ch[i][j]:结点i的第j个字符对应结点号
int val[maxn];
int tot;
int idx(char c){
return c-'a';
}
void insert(char *s,int w){
int u=0,n=strlen(s);
for(int i=0;i<n;i++){
int c=idx(s[i]);
if(!ch[u][c]) ch[u][c]=++tot;
u=ch[u][c];
}
val[u]+=w;
}
int query(char *s){
int u=0,n=strlen(s);
for(int i=0;i<n;i++){
int c=idx(s[i]);
u=ch[u][c];
if(u==0) return 0;
}
return val[u];
}
};
AC自动机(Trie+KMP)
将多个模式串压缩到Trie树中,并用KMP建立失配连接
得到AC自动机
再用主串去匹配AC自动机,可知所有模式串在主串中出现的信息
struct AC_automaton{
int ch[maxn][30],val[maxn],tot;
int ne[maxn];
int idx(char c) {return c-'a';}
void insert(char *s,int w){
int u=0,n=strlen(s);
for(int i=0;i<n;i++){
int c=idx(s[i]);
if(!ch[u][c]) ch[u][c]=++tot;
u=ch[u][c];
}
val[u]+=w;
}
void getne(){
queue<int> q;
for(int i=0;i<26;i++) if(ch[0][i]) q.push(ch[0][i]);
while(q.size()){
int u=q.front();
q.pop();
for(int i=0;i<26;i++){
if(ch[u][i]) ne[ch[u][i]]=ch[ne[u]][i], q.push(ch[u][i]);
else ch[u][i]=ch[ne[u]][i];
}
}
}
int query(char *s){
int u=0,n=strlen(s),ans=0;
for(int i=0;i<n;i++){
int c=idx(s[i]);
u=ch[u][c];
// 访问所有的失配点(同一个结点可能会在主串出现多次)
// 1.统计多少个模式串在主串s中出现过
for(int j=u;j&&val[j]!=-1;j=ne[j]) ans+=val[j],val[j]=-1;
// 2.统计以结点j结尾的模式串在主串s中出现过多少次
// for(int j=u;j;j=ne[j]) if(val[j]) ans[j]++;
}
return ans;
}
}AC;
后缀数组(待定)
将一个字符串进行压缩,高效处理所有子串信息
学习参考:Link
//未使用基数排序
//时间复杂度:O(nlognlogn)
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
char a[maxn];
//sa[i]:所有后缀在排完序后,排名为i的后缀在原串中的位置
//rk[i]:所有后缀在排序完后,原字符串中第i名现在的排名
//height[i]:suff[sa[i]]和suff[sa[i-1]]的最大公共前缀
int len,sa[maxn],rk[maxn],height[maxn],oldrk[maxn],w;
int cmp(int a,int b)
{
if(rk[a]==rk[b]) return rk[a+w]<rk[b+w];
return rk[a]<rk[b];
}
int main()
{
scanf("%s",a+1);
len=strlen(a+1);
//预处理
for(int i=1;i<=len;i++) sa[i]=i,rk[i]=a[i];
//倍增
for(w=1;w<=len;w=(w<<1)){
//排序(等于计算sa) 计算出当前排名的位
sort(sa+1,sa+1+len,cmp);
//依据sa计算rk 计算当前位的排名
memcpy(oldrk,rk,sizeof(rk));
for(int i=1,cnt=0;i<=len;i++){
//rk[sa[i]]=i;
//若相邻排名转移而来大小相同则rk一致
if(oldrk[sa[i]]==oldrk[sa[i-1]]&&oldrk[sa[i]+w]==oldrk[sa[i-1]+w]){
rk[sa[i]]=cnt;
}
else rk[sa[i]]=++cnt; //不一致则排名增加
}
}
//求height
int k=0;
for(int i=1;i<=len;i++) rk[sa[i]]=i;
for(int i=1;i<=len;i++){
if(rk[i]==1) continue;
int j=sa[rk[i]-1];
k=(k>0?k-1:k);
while(i+k<=len&&j+k<=len&&a[i+k]==a[j+k]) k++;
height[rk[i]]=k;
}
for(int i=1;i<=len;i++) printf("%d ",sa[i]);
return 0;
}
//使用基数排序
//时间复杂度:O(nlogn)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e6+5;
char a[maxn];
//sa[i]:所有后缀在排完序后,排名为i的后缀在原串中的位置
//rk[i]:所有后缀在排序完后,原字符串中第i名现在的排名
//height[i]:suff[sa[i]]和suff[sa[i-1]]的最大公共前缀
int len,sa[maxn],rk[maxn],height[maxn],x[maxn],y[maxn],c[maxn];
int m=128;
signed main()
{
scanf("%s",a+1);
len=strlen(a+1);
//求sa
for(int i=1;i<=len;i++){
x[i]=a[i];c[x[i]]++;
}
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=len;i>0;i--) sa[c[x[i]]--]=i;
for(int w=1;w<=len;w<<=1){
int cur=0;
for(int i=len-w+1;i<=len;i++) y[++cur]=i;
for(int i=1;i<=len;i++){
if(sa[i]>w) y[++cur]=sa[i]-w;
}
for(int i=1;i<=m;i++) c[i]=0;
for(int i=1;i<=len;i++) c[x[i]]++;
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=len;i>0;i--){
sa[c[x[y[i]]]--]=y[i];
y[i]=0;
}
swap(x,y);
x[sa[1]]=cur=1;
for(int i=2;i<=len;i++){
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+w]==y[sa[i-1]+w]) x[sa[i]]=cur;
else x[sa[i]]=++cur;
}
if(cur==len) break;
m=cur;
}
//求rk
for(int i=1;i<=len;i++) rk[sa[i]]=i;
//求height
int k=0;
for(int i=1;i<=len;i++) rk[sa[i]]=i;
for(int i=1;i<=len;i++){
if(rk[i]==1) continue;
int j=sa[rk[i]-1];
k=(k>0?k-1:k);
while(i+k<=len&&j+k<=len&&a[i+k]==a[j+k]) k++;
height[rk[i]]=k;
}
for(int i=1;i<=len;i++) printf("%lld ",sa[i]);
return 0;
}
manacher
求串中最长回文串的长度
//核心:求解len数组(le[i]:以i为回文中心的最长回文长度(直径))
char str[maxn]; //原字符串
char tmp[maxn<<1]; //转换字符串
int le[maxn<<1]; //回文长度数组
int ans;
//转换字符串
void init()
{
int len=strlen(str),cnt=0;
for(int i=0;i<len;i++){
tmp[cnt++]='$';
tmp[cnt++]=str[i];
}
tmp[cnt]='$';
}
void getle()
{
int len=strlen(tmp),m=0,r=0; //r:回文串最大右边界 m:对应的回文中心
//枚举i逐次求解le[i]
for(int i=0;i<len;i++){
int j=2*m-i; //j为i关于m的对称
if(i<r) le[i]=min(r-i,le[j]); //i未越界直接解值
else le[i]=1;
while(i-le[i]>=0&&tmp[i-le[i]]==tmp[i+le[i]]) le[i]++; //回文枚举
//更新r和m
int r_new=i+le[i],m_new=i;
if(r_new>r) r=r_new,m=m_new;
ans=max(ans,le[i]-1); //le[i]-1为原字符串回文长度
}
}
最小表示法
不断将字符串a[1~n]的最后一位放至开头,得到n个字符串,其中字典序最小的称为a的最小表示
char a[maxn]; // 从a[1]开始输入
// 返回原串中最小表示的起点位置
int sol(){
int n=strlen(a+1);
for(int i=1;i<=n;i++) a[n+i]=a[i];
int i=1,j=2,k;
while(i<=n&&j<=n){
for(k=0;k<n&&a[i+k]==a[j+k];k++);
if(k==n) break;
if(a[i+k]>a[j+k]){
i=i+k+1;
if(i==j) i++;
}
else{
j=j+k+1;
if(i==j) j++;
}
}
int ans=min(i,j);
return ans;
}
最长上升子序列
bool operator > (string a,string b){
if(a.compare(b)<=0) return false;
return true;
}
//dp[i]:以第i个串为结尾的最长上升子序列长度
for(int i=1;i<cnt;i++){
string now=a[i];
if(!tot||now>tmp[tot]) tmp[++tot]=now,dp[i]=tot;
else{
int l=1,r=tot;
//第一个比now大的
while(l<r){
int m=(l+r)>>1;
if(tmp[m]<=now) l=m+1;
else r=m;
}
tmp[l]=now;
dp[i]=l;
}
}
求所有后缀的最长公共前缀
// lcp[i][j]:第i个后缀和第j个后缀的最长公共前缀长度
char a[maxn];
int lcp[5005][5005];
void getlcp(){
lcp[len+1][len+1]=0;
for (int i=len;i>=1;i--) {
for (int j=len;j>=1;j--) {
if (a[i]==a[j])
lcp[i][j]=lcp[i+1][j+1]+1;
else
lcp[i][j]=0;
}
}
}
3.数据结构
并查集
int f[maxn];
int find(int a){
if(f[a]==a||!f[a]) return a;
return f[a]=find(f[a]);
}
void merge(int a,int b){
f[find(a)]=find(b);
}
ST表
静态RMQ(只查询)
O(nlogn)预处理,O(1)回答
int n,a[maxn]; // a:原始序列
struct ST{
int f[maxn][30]; // f[i][j]:在区间[i,i+2^j-1]的最大值
void init(){
for(int i=1;i<=n;i++) f[i][0]=a[i];
int t=log(n)/log(2)+1;
for(int j=1;j<t;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
// 查询区间[l,r]最大值
int query(int l,int r){
int k=log(r-l+1)/log(2);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
}st;
树状数组
点修改、区间查询
输入a数组后,调用build函数建立树状数组
int n,a[maxn];
struct Tree{
int tree[maxn]; // 维护的是a数组
int lowbit(int x){
return x&-x;
}
// 将第x数加上val
void add(int x,int val){
for(;x<=n;x+=lowbit(x)) tree[x]+=val;
}
void build(){
for(int i=1;i<=n;i++) add(i,a[i]);
}
// 前缀和查询[1,x]
int ask(int x){
int ans=0;
for(;x;x-=lowbit(x)) ans+=tree[x];
return ans;
}
// 查询区间[l,r]
int query(int l,int r){
return ask(r)-ask(l-1);
}
}tree;
树状数组
区间修改、单点查询
输入a数组后直接使用
int n,m,a[maxn];
struct Tree{
int tree[maxn]; // 维护a的差分数组
int lowbit(int x){
return x&-x;
}
// 将第x数加上val
void add(int x,int val){
for(;x<=n;x+=lowbit(x)) tree[x]+=val;
}
// 差分前缀和
int ask(int x){
int ans=0;
for(;x;x-=lowbit(x)) ans+=tree[x];
return ans;
}
// 区间修改:差分
void update(int l,int r,int val){
add(l,val);
add(r+1,-val);
}
// 单点查询
int query(int x){
return a[x]+ask(x);
}
}tree;
树状数组
区间修改(update)、区间查询(query)
输入a数组以后调用init函数初始化
int n,a[maxn]; // a:维护序列
struct Tree{
int c[2][maxn]; // 2颗树状数组
int sum[maxn];
// 注意使用前调用初始化函数
void init(){
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
}
int lowbit(int x){
return x&-x;
}
int ask(int k,int x){
int ans=0;
for(;x;x-=lowbit(x)) ans+=c[k][x];
return ans;
}
void add(int k,int x,int y){
for(;x<=n;x+=lowbit(x)) c[k][x]+=y;
}
// 区间修改
void update(int l,int r,int val){
add(0,l,val);
add(0,r+1,-val);
add(1,l,l*val);
add(1,r+1,-(r+1)*val);
}
// 区间查询
int query(int l,int r){
int ans=sum[r]+(r+1)*ask(0,r)-ask(1,r);
ans-=sum[l-1]+l*ask(0,l-1)-ask(1,l-1);
return ans;
}
}tree;
线段树
区间修改([l,r]+val)、区间查询([l,r]的和)
使用build函数输入,整个区间为:[1,n]
struct Tree{
int tree[maxn*4],lazy[maxn*4];
void push_up(int u){
int v1=u*2,v2=u*2+1;
tree[u]=tree[v1]+tree[v2];
}
void push_down(int l,int r,int u){
int v1=u*2,v2=u*2+1,m=(l+r)/2;
tree[v1]+=lazy[u]*(m-l+1);
tree[v2]+=lazy[u]*(r-m);
lazy[v1]+=lazy[u];
lazy[v2]+=lazy[u];
lazy[u]=0;
}
// 建树:整个区间,根节点
void build(int l,int r,int u){
if(l==r){
scanf("%lld",&tree[u]);
return;
}
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
build(l,m,v1);
build(m+1,r,v2);
push_up(u);
}
// 区间修改:整个区间,询问区间,权值,根节点
void update(int l,int r,int L,int R,int val,int u){
if(l>=L&&r<=R){
lazy[u]+=val;
tree[u]+=(r-l+1)*val;
return;
}
push_down(l,r,u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
if(L<=m) update(l,m,L,R,val,v1);
if(R>m) update(m+1,r,L,R,val,v2);
push_up(u);
}
// 区间查询:整个区间,询问区间,根节点
int query(int l,int r,int L,int R,int u){
if(l>=L&&r<=R) return tree[u];
push_down(l,r,u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
int tmp=0;
if(L<=m) tmp+=query(l,m,L,R,v1);
if(R>m) tmp+=query(m+1,r,L,R,v2);
return tmp;
}
}tree;
线段树
区间加修改,区间乘修改,区间和查询
int p; // 模数
struct Tree{
// lazy:加法懒标记 lazy2:乘法懒标记
int tree[maxn*4],lazy[maxn*4],lazy2[maxn*4];
void push_up(int u){
int v1=u*2,v2=u*2+1;
tree[u]=(tree[v1]+tree[v2])%p;
}
void push_down(int l,int r,int u){
int v1=u*2,v2=u*2+1,m=(l+r)/2;
tree[v1]=(tree[v1]*lazy2[u]+lazy[u]*(m-l+1))%p;
tree[v2]=(tree[v2]*lazy2[u]+lazy[u]*(r-m))%p;
lazy[v1]=(lazy[v1]*lazy2[u]+lazy[u])%p;
lazy[v2]=(lazy[v2]*lazy2[u]+lazy[u])%p;
lazy2[v1]=(lazy2[v1]*lazy2[u])%p;
lazy2[v2]=(lazy2[v2]*lazy2[u])%p;
lazy2[u]=1;
lazy[u]=0;
}
// 建树:整个区间,根节点
void build(int l,int r,int u){
lazy2[u]=1;
lazy[u]=0;
if(l==r){
scanf("%lld",&tree[u]);
tree[u]=tree[u]%p;
return;
}
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
build(l,m,v1);
build(m+1,r,v2);
push_up(u);
}
// 加法区间修改:整个区间,询问区间,权值,根节点
void update(int l,int r,int L,int R,int val,int u){
if(l>=L&&r<=R){
lazy[u]=(lazy[u]+val)%p;
tree[u]=(tree[u]+(r-l+1)*val)%p;
return;
}
push_down(l,r,u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
if(L<=m) update(l,m,L,R,val,v1);
if(R>m) update(m+1,r,L,R,val,v2);
push_up(u);
}
// 乘法区间修改
void update2(int l,int r,int L,int R,int val,int u){
if(l>=L&&r<=R){
lazy2[u]=(lazy2[u]*val)%p;
lazy[u]=(lazy[u]*val)%p;
tree[u]=(tree[u]*val)%p;
return;
}
push_down(l,r,u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
if(L<=m) update2(l,m,L,R,val,v1);
if(R>m) update2(m+1,r,L,R,val,v2);
push_up(u);
}
// 区间和查询:整个区间,询问区间,根节点
int query(int l,int r,int L,int R,int u){
if(l>=L&&r<=R) return tree[u];
push_down(l,r,u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
int tmp=0;
if(L<=m) tmp=(tmp+query(l,m,L,R,v1))%p;
if(R>m) tmp=(tmp+query(m+1,r,L,R,v2))%p;
return tmp;
}
}tree;
线段树
动态RMQ(修改+查询)
struct Tree{
int tree[maxn*4],lazy[maxn*4];
void push_up(int u){
int v1=u*2,v2=u*2+1;
tree[u]=max(tree[v1],tree[v2]);
}
void push_down(int u){
int v1=u*2,v2=u*2+1;
tree[v1]+=lazy[u];
tree[v2]+=lazy[u];
lazy[v1]+=lazy[u];
lazy[v2]+=lazy[u];
lazy[u]=0;
}
// 建树:整个区间,根节点
void build(int l,int r,int u){
if(l==r){
scanf("%lld",&tree[u]);
return;
}
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
build(l,m,v1);
build(m+1,r,v2);
push_up(u);
}
// 区间修改:整个区间,询问区间,权值,根节点
void update(int l,int r,int L,int R,int val,int u){
if(l>=L&&r<=R){
lazy[u]+=val;
tree[u]+=val;
return;
}
push_down(u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
if(L<=m) update(l,m,L,R,val,v1);
if(R>m) update(m+1,r,L,R,val,v2);
push_up(u);
}
// 区间查询:整个区间,询问区间,根节点
int query(int l,int r,int L,int R,int u){
if(l>=L&&r<=R) return tree[u];
push_down(u);
int m=(l+r)/2;
int v1=u*2,v2=u*2+1;
int tmp=0;
if(L<=m) tmp=max(tmp,query(l,m,L,R,v1));
if(R>m) tmp=max(tmp,query(m+1,r,L,R,v2));
return tmp;
}
}tree;
静态主席树(可持久化权值线段树)
查询数组区间第k大
输入a数组以后,使用build创建主席树,使用query查询区间第k大
如需实现不同功能,修改其query函数即可
// 支持a范围:[-1e9,1e9],需离散
// n:a(原)数组长度,m:b数组长度
int n,m,a[maxn],b[maxn];
struct Tree{
// T[i]:原数组下标为i的数的根结点
// val[i]:主席树结点为i的权值
// L[i]:主席树结点为i的左儿子,R类似
int T[maxn],val[maxn*40],L[maxn*40],R[maxn*40]; // 主席树开40倍空间
int tot;
// 上颗树节点,区间,x:插入的值a[i]
int insert(int pre,int l,int r,int x){
int rt=++tot;
int m=(l+r)/2;
L[rt]=L[pre],R[rt]=R[pre],val[rt]=val[pre]+1;
if(l<r){
if(x<=m) L[rt]=insert(L[pre],l,m,x);
else R[rt]=insert(R[pre],m+1,r,x);
}
return rt;
}
// 据a数组创建主席树
void build(){
// 将a离散:a变原数组对应离散值,b变原数组的排序去重
for(int i=1;i<=n;i++) b[i]=a[i];
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-(b+1);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+1+m,a[i])-b;
T[i]=insert(T[i-1],1,m,a[i]);
}
}
// 返回区间第k大的下标值
// 查询区间对应的根节点,总区间,第k大
int query(int u,int v,int l,int r,int k){
if(l==r) return l;
int x=val[L[v]]-val[L[u]];
int m=(l+r)/2;
if(x>=k) return query(L[u],L[v],l,m,k);
else return query(R[u],R[v],m+1,r,k-x);
}
}tree;
/*
查询区间第k大的值
scanf("%lld%lld%lld",&l,&r,&k);
int pos=tree.query(tree.T[l-1],tree.T[r],1,m,k);
printf("%lld\n",b[pos]);
*/
静态主席树
a为结点值。对树使用主席树,按dfs序建主席树
题目参考
// 支持a范围:[1,1e9],无需离散
int n,a[maxn];
struct Tree{
// T[i]:原数组下标为i的数的根结点
// val[i]:主席树结点为i的权值
// L[i]:主席树结点为i的左儿子,R类似
int T[maxn],val[maxn*40],L[maxn*40],R[maxn*40]; // 主席树开40倍空间
int tot;
// 上颗树节点,区间,x:插入的值a[i]
int insert(int pre,int l,int r,int x){
int rt=++tot;
int m=(l+r)/2;
L[rt]=L[pre],R[rt]=R[pre],val[rt]=val[pre]+1;
if(l<r){
if(x<=m) L[rt]=insert(L[pre],l,m,x);
else R[rt]=insert(R[pre],m+1,r,x);
}
return rt;
}
// 查询左边界树的根,查询右边界的根,总区间,查询大于等于k的数量
int ask(int u,int v,int l,int r,int k){
int x=val[v]-val[u];
if(l>=k) return x;
if(r<k) return 0;
int m=(l+r)/2;
return ask(L[u],L[v],l,m,k)+ask(R[u],R[v],m+1,r,k);
}
}tree;
// 求解dfs序(dfn1,dfn2),并按dfs序建树
void dfs(int u,int fa){
dfn1[u]=++all;
tree.T[all]=tree.insert(tree.T[all-1],1,N,a[u]);
for(int i=head[u];i;i=edge[i].ne){
int v=edge[i].to;
if(v==fa) continue;
dfs(v,u);
}
dfn2[u]=all;
}
// tree.ask(tree.T[dfn1[x]-1],tree.T[dfn2[x]],1,N,l)
求树的重心
// 树的重心为f[i]最小的点
// 树的重心:最大子树的结点数最小
// f[i]:以i为根的最大子树的结点数
// size[i]:以i为根的子树的结点数
int f[maxn],size[maxn],all;
vector<int> ve[maxn];
void dfs(int u,int fa){
size[u]=1;
for(int v:ve[u]){
if(v==fa) continue;
dfs(v,u);
size[u]+=size[v];
f[u]=max(f[u],size[v]);
}
f[u]=max(f[u],all-size[u]);
}
最近公共祖先LCA(倍增)
int n,m,s;
// fa[i][j]:i结点的第2^j级祖先
// dep[i]:结点i的深度
int fa[maxn][25],dep[maxn];
vector<int> ve[maxn];
// 求dep[i],并初始化f[u][0]
void dfs(int u,int fa,int d){
dep[u]=d;
::fa[u][0]=fa;
for(int v:ve[u]){
if(!dep[v]) dfs(v,u,d+1);
}
}
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y); // 默认x更深
for(int i=20;i>=0;i--)
if(dep[y]<=dep[x]-(1<<i)) x=fa[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
}
return fa[x][0];
}
signed main()
{
cin>>n>>m>>s;
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%lld%lld",&u,&v);
ve[u].push_back(v);
ve[v].push_back(u);
}
// dfs初始化后求所有的fa[i][j]
dfs(s,0,1);
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
while(m--){
int u,v;
scanf("%lld%lld",&u,&v);
printf("%lld\n",LCA(u,v));
}
return 0;
}
点分治
const int maxn = 1e6+5;
vector<int> edge[maxn];
int rt,size[maxn],son[maxn];
int all;
int vis[maxn];
int ans;
void getroot(int u, int fa)
{
size[u] = 1;
son[u] = 0;
int tmp = 0;
for(auto v : edge[u]){
if(v == fa) continue;
getroot(v, u);
size[u] += size[v];
son[u] = max(son[u], size[v]); //son[u]:u结点最大的儿子块
}
son[u] = max(son[u], all - size[u]);
if(son[u] < son[rt]) rt = u; //最大儿子最小的为重心
}
void dfs(int u)
{
vis[u] = 1;
ans += cal(u);
for(auto v : edge[u]){
if(vis[v]) continue;
ans -= cal(v);
rt = 0; //son[0]为inf,保证第一次rt能被置换
all = size[v];
getroot(v, 0);
dfs(rt);
}
}
树上启发式合并
dsu on tree
1.轻重链剖分,找出重儿子。(预处理)
2.先处理轻儿子,不记录贡献。
3.处理重儿子,记录贡献。
4.将当前节点和轻儿子的贡献和重儿子合并。
5.如果当前节点是轻儿子的话,消除影响。
模板题(给出一个树,求出每个节点的子树中出现次数最多的颜色的编号和)
参考博客
// 结点的大小,重儿子结点号,结点颜色,颜色数,最大颜色数,重儿子
int size[maxn],son[maxn],col[maxn],cnt[maxn],Mx,Son;
int sum,ans[maxn];
vector<int> ve[maxn];
// 预处理求son[u]
void dfs(int u,int fa){
size[u]=1;
for(int v:ve[u]){
if(v==fa) continue;
dfs(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void add(int u,int fa,int val){
cnt[col[u]]+=val;//这里可能会因题目而异
if(cnt[col[u]]>Mx) Mx=cnt[col[u]],sum=col[u];
else if(cnt[col[u]]==Mx) sum+=col[u];
for(int v:ve[u]){
if(v==fa||v==Son) continue;
add(v,u,val);
}
}
// 求ans[u]
void dfs2(int u,int fa,int op){
for(int v:ve[u]){
if(v==fa) continue;
if(v!=son[u]) dfs2(v,u,0);//暴力统计轻边的贡献,opt = 0表示递归完成后消除对该点的影响
}
if(son[u]) dfs2(son[u],u,1),Son=son[u];//统计重儿子的贡献,不消除影响
add(u,fa,1); Son=0;//暴力统计所有轻儿子的贡献
ans[u]=sum;//更新答案
if(!op) add(u,fa,-1),sum=0,Mx=0;//如果需要删除贡献的话就删掉
}
int n;
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) scanf("%lld",&col[i]);
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%lld%lld",&u,&v);
ve[u].push_back(v);
ve[v].push_back(u);
}
dfs(1,0);
dfs2(1,0,0);
for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
return 0;
}
4.数学
求组合数
double c[105][105];
void init(){
for(int i=0;i<=100;i++){
for(int j=0;j<=i;j++){
if(j==0) c[i][j]=1;
else c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
}
可重复排列组合
多重集全排列个数
题目参考
辗转相除法
gcd(a,b)=gcd(a%b,b)
当题目给出a%b=1时注意考虑(互质)
差分与gcd
g c d ( a , b , c , d . . . ) = g c d ( a , b − a , c − b , d − c ) gcd(a,b,c,d...)=gcd(a,b-a,c-b,d-c) gcd(a,b,c,d...)=gcd(a,b−a,c−b,d−c)
二项式定理
二项分布
几何分布
在 n n n 次伯努利实验中,第 k k k 次实验才得到第一次成功的概率分布,令该事件为 A A A,可以理解为前 k − 1 k-1 k−1 次皆失败,第 k k k 次成功的概率。
P ( k ) = ( 1 − p ) k − 1 ∗ p P ( k ) = ( 1 − p )^{ k − 1} ∗ p P(k)=(1−p)k−1∗p
E = 1 P ( k ) E=\frac1{P(k)} E=P(k)1( A A A发生一次期望的实验次数)
期望
期望= ∑ \sum\limits_{} ∑概率x权值
容斥原理
容斥原理
奇加偶减
A ∪ B ∪ C = A + B + C − A ∩ B − A ∩ C − B ∩ C + A ∩ B ∩ C A∪B∪C=A+B+C-A∩B-A∩C-B∩C+A∩B∩C A∪B∪C=A+B+C−A∩B−A∩C−B∩C+A∩B∩C
逆元
即求关于x的同余方程 ax ≡ 1 (mod m) 的最小正整数解
即解方程a*inv + my = 1(已知a和m,求一组特殊解使得inv最小)
// 函数返回值是a和b的最大公约数
int exgcd(int a,int b,int &x,int &y){
if(b==0){
x=1;
y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
int tmp=x;
x=y;
y=tmp-a/b*y;
return ans;
}
int inv(int a,int m){
int x,y,gcd=exgcd(a,m,x,y);
if(gcd!=1) return -1; // 不存在逆元(需a和m互质)
return (x%m+m)%m;
}
矩阵快速幂
struct mat {
int r,c;
int m[105][105]; //经测试最大开成590*590的 ll 型矩阵
mat() {}
mat(int r,int c):r(r),c(c) {}
void clear() {
memset(m,0,sizeof(m));
}
mat operator+(mat a)const {
mat ans(r,c);
for(int i=1; i<=r; i++) {
for(int j=1; j<=c; j++) {
ans.m[i][j]=(m[i][j]+a.m[i][j])%mod;
}
}
return ans;
}
mat operator*(mat a)const {
mat tmp(r,a.c);
int i,j,k;
for(i=1; i<=tmp.r; i++) {
for(j=1; j<=tmp.c; j++) {
tmp.m[i][j]=0;
for(k=1; k<=c; k++) {
tmp.m[i][j]=(tmp.m[i][j]+(m[i][k]*a.m[k][j])%mod)%mod;
}
}
}
return tmp;
}
// 算矩阵幂:a^n
mat operator^(int n)const { //需要时可以用 ll n,注意运算符优先级比较低,多用括号;
mat ans(r,r),tmp(r,r);
memcpy(tmp.m,m,sizeof(tmp.m));
ans.clear();
for(int i=1; i<=ans.r; i++) {
ans.m[i][i]=1;
}
while(n) {
if(n&1)ans=ans*tmp;
n>>=1;
tmp=tmp*tmp;
}
return ans;
}
void print()const {
for(int i=1; i<=r; i++) {
for(int j=1; j<=c; j++) {
printf("%lld",m[i][j]);
if(j==c)printf("\n");
else printf(" ");
}
}
}
};
高斯消元
// a为方程组
int n;
double a[105][105];
// 高斯-约旦消元法
void Gauss(){
for(int i=1;i<=n;i++){
int now=i;
for(int j=i+1;j<=n;j++){
if(fabs(a[j][i])>fabs(a[now][i])) now=j;
}
for(int j=1;j<=n+1;j++) swap(a[i][j],a[now][j]);
if(!a[i][i]){
puts("No Solution");
return;
}
for(int j=1;j<=n;j++){
if(j!=i){
double tmp=a[j][i]/a[i][i];
for(int k=i+1;k<=n+1;k++) a[j][k]-=a[i][k]*tmp;
}
}
}
// 上述操作结束后,矩阵会变成这样
/*
a1*x1=b1
a2*x2=b2
a3*x3=b3
a4*x4=b4
*/
// 用b/a消除系数
for(int i=1;i<=n;i++)
printf("%.2lf\n",a[i][n+1]/a[i][i]);
}
5.图论
链式前向星
int head[maxn],tot;
struct edge{
int to,val,ne;
}edge[maxn];
void add_edge(int now,int to,int val){
tot++;
edge[tot].ne=head[now];
head[now]=tot;
edge[tot].to=to;
edge[tot].val=val;
}
void dfs(int u,int fa){
for(int i=head[u];i;i=edge[i].ne){
int v=edge[i].to;
if(v==fa) continue;
dfs(v,u);
}
}
vector建图
// 适用于无边权
vector<int> ve[maxn];
void add_edge(int u,int v){
ve[u].push_back(v);
}
void dfs(int u,int fa){
for(int v:ve[u]){
if(v==fa) continue;
dfs(v,u);
}
}
堆优化dijkstra
单源最短路径,求一个点到其他所有点的最短路径
// 点数,边数,源点到各点最短距离,起点,访问标记,链式head
int n,m,dist[maxn],s,vis[maxn],head[maxn];
struct edge{
int to,val,ne;
}edge[maxn];
struct node{
int val,pos;
friend bool operator <(struct node a,struct node b){
return a.val>b.val;
}
};
priority_queue<node> q; // 堆优化
void add_edge(int now,int to,int val,int tot){
edge[tot].ne=head[now];
head[now]=tot;
edge[tot].to=to;
edge[tot].val=val;
}
void dij(){
for(int i=1;i<=n;i++) if(i!=s) dist[i]=inf;
q.push({0,s});
while(!q.empty()){
struct node top=q.top();
q.pop();
if(vis[top.pos]) continue;
vis[top.pos]=1;
for(int i=head[top.pos];i;i=edge[i].ne){
int to_val=top.val+edge[i].val;
int to_pos=edge[i].to;
if(dist[to_pos]>to_val){
dist[to_pos]=to_val;
q.push({to_val,to_pos});
}
}
}
}
Floyd(n^3)
求每两点之间的最短路径(不必调用n次dijkstra)
void floyd(){
// 初始化
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j) f[i][j]=inf;
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
}
}
}
}
6.DP
背包
// 01背包
for (int i=1;i<=n;i++)
for (int j=V;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+v[i]);
// 完全背包
for (int i=1;i<=n;i++)
for (int j=w[i];j<=V;j++)
f[j]=max(f[j],f[j-w[i]]+v[i]);
悬线法
数位dp
int a,b,dig[15],pos;
int dp[15][15][2][2];
//dp[pos][cnt]:前pos位数字x出现了cnt次对应的答案
int dfs(int pos,int cnt,int zero,int lim,int now){
if(!pos) return cnt;
if(dp[pos][cnt][zero][lim]!=-1) return dp[pos][cnt][zero][lim];
int up=(lim?dig[pos]:9);
int tmp=0;
for(int i=0;i<=up;i++){
if(zero&&i==0) tmp+=dfs(pos-1,cnt,zero&&i==0,lim&&i==up,now);
else tmp+=dfs(pos-1,cnt+(i==now),zero&&i==0,lim&&i==up,now);
}
return dp[pos][cnt][zero][lim]=tmp;
}
int sol(int x,int i){
memset(dp,-1,sizeof(dp));
pos=0;
while(x){
dig[++pos]=x%10;
x/=10;
}
return dfs(pos,0,1,1,i);
}
signed main()
{
scanf("%lld%lld",&a,&b);
for(int i=0;i<=9;i++) printf("%lld ",sol(b,i)-sol(a-1,i));
return 0;
}
int t,x,y,a[maxn],b[maxn],pos1,pos2,dp[maxn][2][2],ans;
int dfs(int pos,int lim1,int lim2,int ok){
if(pos==0) return 1;
if(dp[pos][lim1][lim2]!=-1) return dp[pos][lim1][lim2];
int up1=(lim1?a[pos]:1),up2=(lim2?b[pos]:1);
int res=0,val=0;
for(int i=0;i<=up1;i++){
for(int j=0;j<=up2;j++){
if(i==1&&j==1) continue;
int tmp=dfs(pos-1,lim1&&i==up1,lim2&&j==up2,ok||i||j);
res=(res+tmp)%mod;
if(!ok&&(i||j)) val=(val+tmp)%mod;
}
}
ans=(ans+pos*val)%mod;
return dp[pos][lim1][lim2]=res;
}
void sol(int x,int y){
while(x){
a[++pos1]=x%2;
x/=2;
}
while(y){
b[++pos2]=y%2;
y/=2;
}
dfs(max(pos1,pos2),1,1,0);
}
signed main()
{
cin>>t;
while(t--){
ans=pos1=pos2=0;
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(dp,-1,sizeof(dp));
scanf("%lld%lld",&x,&y);
sol(x,y);
printf("%lld\n",ans);
}
return 0;
}
概率dp
int t,n,m,k;
double c[maxn][maxn],p[maxn],dp[maxn][maxn],ans;
void init(){
p[0]=1;
for(int i=1;i<=100;i++) p[i]=p[i-1]*0.5;
c[0][0]=1;
for(int i=1;i<=100;i++){
for(int j=0;j<=100;j++){
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
}
signed main()
{
init();
cin>>t;
while(t--){
ans=0;
memset(dp,0,sizeof(dp));
dp[0][0]=1;
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
for(int x=0;x<=k;x++){
if(n-j>=k)
dp[i+1][j+x]+=dp[i][j]*p[k]*c[k][x];
else
dp[i+1][j+x-(k-(n-j))]+=dp[i][j]*p[k]*c[k][x];
}
}
}
for(int i=0;i<=n;i++) ans+=dp[m][i]*i;
printf("%.3lf\n",ans);
}
return 0;
}
7.计算几何
const double eps=1e-8;
int sgn(double x){
if(fabs(x)<eps) return 0;
if(x>0) return 1;
return -1;
}
// 点类
struct point{
double x,y;
point() {}
point(double x,double y): x(x),y(y) {}
point operator - (point b){
return point{x-b.x,y-b.y};
}
// 点积
double operator * (point b){
return x*b.x+y*b.y;
}
// 叉积
double operator ^ (point b){
return x*b.y-y*b.x;
}
// 绕原点旋转A弧度(注意A是弧度,非角度)
void rotate(double A){
double tx=x,ty=y;
x=tx*cos(A)-ty*sin(A);
y=tx*sin(A)+ty*cos(A);
}
};
// 线类
struct line{
point s,e;
line() {}
line(point s,point e): s(s),e(e) {}
//两直线求交点
pair<int,point> operator & (line b){
point ans=s;
if (sgn((s - e) ^ (b.s - b.e)) == 0){
if (sgn((s - b.e) ^ (b.s - b.e)) == 0)
return make_pair(0,ans); //重合
else
return make_pair(1,ans); //平行
}
double t = ((s - b.s) ^ (b.s - b.e)) / ((s - e) ^ (b.s - b.e));
ans.x += (e.x - s.x) * t;
ans.y += (e.y - s.y) * t;
return make_pair(2, ans);
}
};
//求两点间距离
double dist(point a,point b){
return sqrt((a-b)*(a-b));
}
//判点在线段上
bool PointOnSegment(point p,line l){
return sgn( (p-l.s) ^ (l.e-l.s) )==0 && sgn( (p-l.s) * (p-l.e) )<=0;
}
//判线段相交
bool inter(line l1, line l2){
return max(l1.s.x, l1.e.x) >= min(l2.s.x, l2.e.x) &&
max(l2.s.x, l2.e.x) >= min(l1.s.x, l1.e.x) &&
max(l1.s.y, l1.e.y) >= min(l2.s.y, l2.e.y) &&
max(l2.s.y, l2.e.y) >= min(l1.s.y, l1.e.y) &&
sgn((l2.s - l1.e) ^ (l1.s - l1.e)) * sgn((l2.e - l1.e) ^ (l1.s - l1.e)) <= 0 &&
sgn((l1.s - l2.e) ^ (l2.s - l2.e)) * sgn((l1.e - l2.e) ^ (l2.s - l2.e)) <= 0;
}
//判直线与线段相交,l2为线段
bool seg_inter_line(line l1, line l2){
return sgn((l2.s - l1.e) ^ (l1.s - l1.e)) * sgn((l2.e - l1.e) ^ (l1.s - l1.e)) <= 0;
}
//点到直线的距离,返回点到直线最近的点
point PointToLine(point P,line L){
point ans;
double t = ((P - L.s) * (L.e - L.s)) / ((L.e - L.s) * (L.e - L.s));
ans.x = L.s.x + (L.e.x - L.s.x) * t;
ans.y = L.s.y + (L.e.y - L.s.y) * t;
return ans;
}
//点到线段的距离
point NearestPointToLineSeg(point P, line L){
point ans;
double t = ((P - L.s) * (L.e - L.s)) / ((L.e - L.s) * (L.e - L.s));
if (t >= 0 && t <= 1){
ans.x = L.s.x + (L.e.x - L.s.x) * t;
ans.y = L.s.y + (L.e.y - L.s.y) * t;
}
else{
if (dist(P, L.s) < dist(P, L.e))
ans = L.s;
else
ans = L.e;
}
return ans;
}
// 多边形类
struct polygon{
int n;
point a[maxn];
polygon() {}
// 计算面积
double area(){
double ans=0;
a[n]=a[0];
for(int i=0;i<n;i++) ans+=(a[i+1]^a[i]);
return ans/2;
}
// 判断点是否在多边形内部(0外,1内,2边界)
int point_in(point p){
int num=0;
a[n]=a[0];
for(int i=0;i<n;i++){
if(PointOnSegment(p,{a[i],a[i+1]})) return 2;
int k=sgn( (a[i+1]-a[i]) ^ (p-a[i]) );
int d1=sgn(a[i].y-p.y);
int d2=sgn(a[i+1].y-p.y);
if(k>0&&d1<=0&&d2>0) num++;
if(k<0&&d2<=0&&d1>0) num--;
}
return num!=0;
}
};
其他
关闭cin cout同步
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
输入输出重定向
freopen("D:\\in.txt","r",stdin);
freopen("D:\\out.txt","w",stdout);
一行读入
int a[maxn],n;
while(~scanf("%lld",&a[n++]));
n--;
string a;
getline(cin,a);
char a[maxn];
scanf("%[^\n]",a);
string 转换(string转数字,数字转string)
全局long long
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
const int mod=1e9+7;
const int inf=1e18;
signed main()
{
return 0;
}
程序计时
cin>>n; /*用户初始输入部分*/
double t1=clock(); //一定要置于初始输入后面,忽略输入时间
/*
代码主体部分
*/
double t2=clock();
cout<<"\nTime: "<<t2-t1<<"ms"<<endl;
技巧
有环图DP(无论有向图或无向图)
在图进行DP时,因为有环的情况
需要保证状态只能从父结点向子结点转移,而不能由子结点向父结点转移
由于要保证父结点向子结点转移,故需设计好状态,将问题进行一定的转化,使用DFS和BFS均有可能
题目参考
题目参考
取模输出答案
若题目要求取模输出答案,算完后需执行以下操作
因为计算时可能有减操作,由于取模的性质算出负数
ans=(ans+mod)%mod;
计算空间复杂度
字节数 / 1024 = xx KB
例子:
题目空间限制为262144K
定义数组int a[6e7],则占用空间6e7*4 /1024 = 234375 K,未炸空间