目录
一切的开始
初始模板
// o2 o3 优化防止卡常
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
#define endl "\n"
#define LF(x) fixed<<setprecision(x)// c++ 保留小数
// typedef 家族
typedef long long LL;
typedef unsigned long long ULL;
typedef tuple<int,int,int> TUP;
typedef pair<int, int> PII;
// const 家族
const int N=1000010,M=1010,INF=0x3f3f3f3f,pp=13331,mod=1e9+7;
const double pai=acos(-1.0);// pai
int t,n,m;
// 随机化
mt19937_64 rnd(time(NULL));
void solve(){
return ;
}
int main (){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int t; cin>>t;
while(t--){
solve();
}
return 0;
}
奇技淫巧
// 随机化数组
random_shuffle(x+1,x+1+n);
//卡时间限制随机化
srand(time(NULL));
while((double)clock()/CLOCKS_PER_SEC < 1.9) get();
//(1.9这个时间依照题变化)
简单基础算法
1.高精度 | int128
1.高精度加法
时间o(n) 空间o(n)
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);
reverse(C.begin(),C.end());
return C;
}
2.高精度减法
时间o(n) 空间o(n)
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;
int t = 0;
for(int i=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 and C.back()==0) C.pop_back();
reverse(C.begin(),C.end());
return C;
}
3.高精度乘法 高x低
时间: o(n) 时间o(n) 空间o(n)
vector<int> A;
vector<int> mul(vector<int>&A,int b){
vector<int> C;
int t = 0;
for(int i=0;i<A.size() or t;i++){
if(i<A.size()) t += A[i]*b;
C.push_back(t%10);
t /= 10;
}
while(C.size()>1 and C.back()==0) C.pop_back();
reverse(C.begin(),C.end());
return C;
}
4.高精度乘法 高x高
时间: o(n*m) 空间(n+m)
vector<int> A,B;
vector<int> mul(vector<int>&A,vector<int>&B){
vector<int> C(A.size()+B.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++){
t += C[i];
C[i] = t%10;
t /=10;
}
while(C.size()>1 and C.back()==0) C.pop_back();
reverse(C.begin(),C.end());
return C;
}
5.int128
为了放置爆LL,对于128位内的数字使用int128来操作
template<class T> inline void read(T &k) {
T x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') {
f = -1;
}
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
k = x * f;
}
template<class T> inline void write(T x) {
if (x < 0) {
putchar('-'), x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
}
using i128 = __int128_t;
2.双指针
1.简单双指针
如果这个区间不满足那么对于包含他的区间一定也是不满足当区间的右端点增加时,左区间的移动趋势一定是增加
可以随着区间的左右端点的变化依旧维护性质,可以转移
可以用于求解满足某些要求的区间的数量
注意有些题目明显的就是具有二分性质,但是他可能也是具有双指针性质,降低时间复杂度
判断依据:左指针只会随着右指针的变大而变大,当前区间不行大的一定不行
void solve(){
cin>>n;
vector<int> cnt(N);
vector<int> a(n+1);
int ans = 0;
for(int i=1,j=1;i<=n;i++){
cin>>a[i];
cnt[a[i]]++;
while(cnt[a[i]]>1){
cnt[a[j]]--;
j++;
}
ans = max(ans,i-j+1);
}
cout << ans << endl;
return ;
}
2.双指针+前缀和
考虑对于区间中满足某些要求的区间的数量
如果我们对于右端点得到了满足要求的最大的左端点,我们可以发现任意选择区间中的点得到的结果就是题目要求的<=要求的区间数量
如下 求解恰好等于数的种类恰好等于3的区间数量
恰好等于数的种类恰好等于3的区间数量 == 数的种类恰<=3的区间数量 –数的种类恰<=2的区间数量
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
auto cal = [&](int x) -> ll {
map<int, int> m;
ll ans = 0;
for (int i = 1, l = 1, c = 0; i <= n; i++) {
if (++m[a[i]] == 1) c++;
while (c > x) {
if (--m[a[l++]] == 0) c--;
}
ans += i - l + 1;
}
return ans;
};
cout << cal(3) - cal(2) << endl;
}
3.序列自动机
(序列长度较小)
题意一般是要在字符串中需要出现某种序列,或者同时维护不出现某种序列的数量
我们可以发现同时要的就是对当前区间来随着指针的移动来维护区间的信息
注意到我们可以考虑使用序列自动机,对每一个点去维护离他最近的后一个字母所在的位置
for(int i=n-1;i>=0;i--){
for(int j=0;j<26;j++) ne[i][j]=ne[i+1][j];
ne[i][s[i+1]-'A']=i+1;
}
我们由此可以固定i为左指针,用j当做快指针向后去移动类似kmp的匹配方式向后跳跃最后判断要求出字符串的位置和不出现字符串的位置
3.二分
具有二分性质:
当区间[l,r]的子区间的点mid符合要求的时候[l,mid]都符合要求或者[mid,r]都符合要求
基础二分注意保留的边界,核心是判断是否具有二分性质 + check函数的书写
时间 o(logn)
int l = 1,r = 1e9;
while(l<r){
int mid = l+r+1>>1; // 注意右边界为2e9的时候开LL
if(check(mid)) l = mid;
else r = mid-1;
}
int l = 1,r = 1e9;
while(l<r){
int mid = l+r>>1;
if(check(mid)) r = mid;
else l = mid+1;
}
注意小数二分写法1:
double l = 0, r = 1e9;
while(r-l>1e-6){
double mid = (l+r)/2;
if(check(mid)) l = mid;
else r = mid;
}
小数二分写法2:
这样不会由于二分精度导致超时
double l = 0, r = 1e9;
for(int i=1;i<=50;i++){
double mid = (l+r)/2;
if(check(mid)) l = mid;
else r = mid;
}
4.三分
具有单峰函数的性质,中间位置为最大值朝着左右递减
时间:o(logn)
int l = 0, r = 1e9+10;
while(l<=r){
int lmid = l + (r-l)/3;
int rmid = r - (r-l)/3;
if(check(lmid) <= check(rmid)) r = rmid - 1;
else l = lmid + 1;
}
小数做法
while(r-l>1e-6){
double midl=l+(r-l)/3.0;
double midr=r-(r-l)/3.0;
if(check(midl)<check(midr)) l=midl;
else r=midr;
}
5.并查集
并查集是线性的用来维护一些关系的数据结构
1.首先是具有传递性的数据结构
2.可以维护某些集体中的关系
3.可以判断关系是否具有矛盾
1.用于维护关系,区间合并
int find(int x){
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
并查集可以运用区间的合并操作表示一个区间的一种信息的合并、改变、扩散
cin>>l>>r;// 作用于一整个信息在这个区间的合并
int color=w[find(r)];
for(int i=find(l);i<=r;i=find(i+1)){// 把当前节点向后合并
color=max(color,w[p[i]]);
p[i]=find(r);
}
w[find(r)]=color;
}
2.对立关系拓展域并查集
for(int i=1;i<=2*n;i++) p[i] = i;
for(int i=1;i<=n;i++){
if(find(i)==find(i+n)){
cout << 0 << endl;
return ;
}
}
3.权重合并,带权并查集
int find(int x){
if(x!=p[x]){
int t=find(p[x]);
d[x]+=d[p[x]];// 如果只有几个的话可以注意加上取模来保证范围
p[x]=t;
}
return p[x];
}
6.树状数组
使用 1.单点修改,区间查询 2.区间修改单点查询 3.离线处理问题
时间: o(nlogn)
struct BIT{
int tr[N];
int n;
void add(int k,int x){
for(int i=k;i<=n;i+=lowbit(i)) tr[i]+=x;
}
int query(int k){
int res=0;
for(int i=k;i;i-=lowbit(i)) res+=tr[i];
return res;
}
int ask(int l,int r){// 先左再右
return query(r)-query(l-1);
}
void clear(){
for(int i=0;i<=n+2;i++) tr[i]=0;
}
}T;
7.ST表 | 倍增
st表
st表就是通过一种倍增的思想可以多而不可以少的覆盖一段区间,也就是需要满足自身和自身操作不会有变化的时候可以使用st表
常见的情况有gcd,|,&,max,min
时间 o(nlogn) 单次查询o(1)
for(int j=1;j<=log2(n);j++)
for(int i=0;i<=n-(1<<j)+1;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
int l,r; cin>>l>>r;
int len=log2(r-l+1);
max(f[l][len],f[r-(1<<len)+1][len])
倍增
倍增可以处理当需要抵达某一个情况才可以成功时候直接使用从大到小去倍增,
同时可以对每一个点的位置开始跳跃到指定条件
可以解决远处可行性问题
8.kmp
字符串匹配类型的问题
时间o(n) 空间o(n)
for(int i=2,j=0;i<=n;i++){
while(p[i]!=p[j+1]&&j) j=ne[j];
if(p[i]==p[j+1]) j++;
ne[i]=j;
}
for(int i=1,j=0;i<=m;i++){
while(j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==n){
cout<<i-n<<" ";
j=ne[j];
}
}
9.哈希
哈希处理字符串,o(1)判断是否是相同串
1.字符哈希
时间o(n) 查询o(1) 空间o(n)
ULL p[N],h[N];
ULL query(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
void init(){
p[0]=1;
for(int i=1;i<=n;i++){
p[i]=p[i-1]*P;
h[i]=h[i-1]*P+a[i];
}
}
完整的封装双模哈希防止冲突
const LL base1 = 114514, base2 = 54321, mod = 19260817;
template <class T>
class stringhash {
public:
std::vector<T> h1, h2, w1, w2;
void init(std::string s)
{
int n = s.size();
s = ' ' + s;
h1.resize(n + 1), w1.resize(n + 1);
h2.resize(n + 1), w2.resize(n + 1);
h1[0] = h2[0] = 0;
w1[0] = w2[0] = 1;
for (int i = 1; i <= n; i++) {
h1[i] = (h1[i - 1] * base1 + s[i]) % mod;
w1[i] = w1[i - 1] * base1 % mod;
h2[i] = (h2[i - 1] * base2 + s[i]) % mod;
w2[i] = w2[i - 1] * base2 % mod;
}
}
std::pair<T, T> get(int l, int r)
{
return { (h1[r] % mod - h1[l - 1] * w1[r - l + 1] % mod + mod) % mod,
(h2[r] % mod - h2[l - 1] * w2[r - l + 1] % mod + mod) % mod };
}
};
2.异或哈希
用随机数来表示当前数的值,然后用哈希来维护出现次数
例题1,区间中每一个数出现k次的区间数量
mt19937_64 rnd(time(NULL));
int a[N];
LL p[N];
void solve(){
cin>>n>>k;
map<LL,LL> x;
for(int i=1;i<=n;i++){
cin>>a[i];
x[a[i]] = rnd();
}
map<int,int> cnt;
for(int i=1;i<=n;i++){
p[i] = p[i-1];
p[i] -= 1ll*cnt[a[i]]*x[a[i]];
cnt[a[i]] = (cnt[a[i]]+1)%k;
p[i] += 1ll*cnt[a[i]]*x[a[i]];
}
cnt.clear();
map<LL,int> mp;
mp[0] = 1;
LL ans = 0;
for(int i=1,j=1;i<=n;i++){
cnt[a[i]]++;
while(cnt[a[i]]>k){
mp[p[j-1]]--;
cnt[a[j++]]--;
}
ans += mp[p[i]];
++mp[p[i]];
}
cout << ans << endl;
return ;
}
例题2,区间中是否出现排列
mt19937_64 rnd(time(NULL));
LL res = 0;
for(int i=1;i<=m;i++){
w[i] = rnd();
res ^= w[i];
}
auto check = [&](int i){
return (S[i]^S[i-m])== res;
};
for(int i=1;i<=n;i++){
S[i] = S[i-1] ^ w[a[i]];
if(i>=m and check(i)) {
pos[i-m+1] = true;
}
}
10.trie树
1.字符trie树
查询是否存在
int son[N][26],cnt[N],idx;
int n,m;
int insert(string x){
int p=0;
for(int i=0;x[i];i++){
int u=x[i]-'a';
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;
}
int query(string x){
int p=0;
for(int i=0;x[i];i++){
int u=x[i]-'a';
if(!son[p][u]) return 0;
p=son[p][u];
}
return cnt[p];
}
2.01trie数
求 里面的数^x>y之类的
const int N=100010*32;
int tr[N][2],n,h,idx=1;
int cnt[N][2];
// idx可以不从1开始但是要注意截止的位置
void insert(int x,int num){
int p=1;
for(int i=30;i>=0;i--){
int u=x>>i&1;
if(!tr[p][u]) tr[p][u]=++idx;
cnt[p][u]+=num;
p=tr[p][u];
}
}
int query(int x,int y){
int ans=0,p=1;
for(int i=30;i>=0;i--){
int ux=x>>i&1,uy=y>>i&1;
if(!uy){// 表示这个位置是0
ans+=cnt[p][ux^1];// 表示这个结尾的就是完全大于的答案了
p=tr[p][ux];// 接着做不大于的答案
}
else{// 表示这个位置是1 那么只能是不一样的答案
p=tr[p][ux^1];
}
}
return ans;
}
数学知识
线性筛
bitset<N> st;
vector<int> primes;
int cnt;
void init(){
for(int i=2;i<N;i++){
if(!st[i]) primes.push_back(i);
for(int j=0;primes[j]<=N/i;j++){
st[i*primes[j]] = true;
if(i%primes[j]==0) break;
}
}
}
exgcd
LL exgcd(LL a,LL b,LL&x, LL&y){
if(!b){
x=1,y=0;
return a;
}
LL d = exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
欧拉函数
线性求欧拉函数
vector<int> primes;
int cnt;
int phi[N];
bitset<N> st;
int n;
void get_phi(int n){
LL ans=0;
phi[1]=1;
st[1]=false;
for(int i=2;i<=n;i++){
if(!st[i]){
primes.push_back(i);
phi[i]=i-1;
}
for(int j=0;primes[j]<=n/i;j++){
st[i*primes[j]]=true;
if(i%primes[j]==0){
phi[i*primes[j]]=phi[i]*primes[j];
break;
}
phi[i*primes[j]]=phi[i]*(primes[j]-1);
}
}
}
单个数求欧拉函数
LL ans=x;
// 算术基本定理
for(int i=2;i<=x/i;i++){
if(x%i==0){
ans=ans/i*(i-1);
while(x%i==0) x/=i;
}
}
if(x>1) ans=ans/x*(x-1);
组合数
int C[N][N];
void intn(){
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-1]+C[i-1][j])%mod;
}
}
组合数+快速幂
int fn[N],fnm[N],inv[N];
LL qmi(LL a,LL b,LL p){// 快速幂
LL res=1;
while(b){
if(b&1) res=res*a%p;
b>>=1;
a=a*a%p;
}
return res;
}
void init(){
inv[1]=1;
for(int i=2;i<N;i++) inv[i]=(LL)(mod-mod/i)*inv[mod%i]%mod;
fn[0]=fnm[0]=1;
for(int i=1;i<N;i++) fn[i]=(LL)fn[i-1]*i%mod;
fnm[N-1]=qmi(fn[N-1],mod-2,mod);
for(int i=N-2;i>=1;i--) fnm[i]=(LL)fnm[i+1]*(i+1)%mod;
}
LL C(int a,int b){ // -> C a中选b
//if(a<b) return 0;
return (LL)fn[a]*fnm[b]%mod*fnm[a-b]%mod;
}
LL A(int a,int b){ // A a中b
//if(a<b) return 0;
return (LL)fn[a]*fnm[a-b]%mod;
}
LL get_inv(LL x){
if(x<N) return inv[x];
return qmi(x,mod-2,mod);
}
容斥原理
二进制枚举选择,然后奇加偶减
博弈论
小猜一手结论,直接暴力打表查看
sg定理
int sg(int x){
if(f[x]!=-1) return f[x];// 表示这个状态已经被使用过了
// 记忆化搜索
unordered_set<int>S;// 记录这个数的转移中的sg值
for(int i=1;i<=n;i++){
int y=s[i];
if(x>=y) S.insert(sg(x-y));//
}
for(int i=0;;i++)
if(!S.count(i))// 这个值没有出现过就是答案
return f[x]=i;
}
模仿刀
特判特殊情况,然后发现某一位玩家是否可以通过模仿对面的操作一直处于必胜状态
图论
prim
通过当前最小值取更新其他的明显的贪心思路
时间复杂度是
auto prim = [&](){
d[1] = 0;
for(int i=1;i<=n;i++){
int t = -1;
for(int j=1;j<=n;j++)
if(!st[j] and (t==-1 or d[j]<d[t]))
t = j;
st[t] = true;
for(int i=1;i<=n;i++) d[i] = min(d[i],d[t] + g[t][i]);
}
return d[n] > 1e9 ? -1 : d[n];
};
dijkstra
auto dijkstra = [&](){
priority_queue<PII,vector<PII>,greater<PII>> q;
d[1] = 0,q.push({0,1});
while(!q.empty()){
auto [cost,u] = q.top(); q.pop();
if(st[u]) continue;
st[u] = true;
if(u==n) return cost;
for(auto&[v,w]:g[u]){
if(d[v]>d[u]+w){
d[v] = d[u] + w;
q.push({d[v],v});
}
}
}
return -1;
};
bellman-ford
限制边数注意不能用当前更新的状态去更新
auto bellman_ford = [&](){
vector<int> last(n+1,2e9);
last[1] = 0;
while(k--){
vector<int> dp(last); // 直接使用上一个一模一样的
for(int i=1;i<=m;i++){
auto [a,b,c] = e[i];
dp[b] = min(dp[b],last[a]+c);
}
swap(dp,last);
}
if(last[n]>1e9) cout << "impossible" << endl;
else cout << last[n] << endl;
return ;
};
spfa
auto spfa = [&](){
for(int i=1;i<=n;i++) d[i] = 2e9;
queue<int> q;
d[1] = 0,q.push(1);
while(!q.empty()){
auto u = q.front(); q.pop();
st[u] = false;
for(auto&[v,w]:g[u])
if(d[v]>d[u]+w){
d[v] = d[u] + w;
if(!st[v]){
st[v] = true;
q.push(v);
}
}
}
if(d[n]>1e9) cout << "impossible" << endl;
else cout << d[n] << endl;
return ;
};
floyd
auto floyd = [&](){
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j] = min(g[i][j],g[i][k]+g[k][j]);
};
floyd();
prim求最小生成树
边权代替点权
auto prim = [&](){
int ans = 0;
for(int i=1;i<=n;i++) d[i] = 1e9;
d[1] = 0;
for(int i=1;i<=n;i++){
int t = -1;
for(int j=1;j<=n;j++)
if(!st[j] and (t==-1 or d[j]<d[t]))
t = j;
st[t] = true;
if(d[t]>=1e9/2){
cout << "impossible" << endl;
return ;
}
ans += d[t];
for(int i=1;i<=n;i++)
d[i] = min(d[i],g[t][i]);
}
cout << ans << endl;
return ;
};
prim();
Kruskal
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int p[N];
vector<array<int,3>> v;
int n,m;
int find(int x){
if(x!=p[x]) p[x] = find(p[x]);
return p[x];
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) p[i] = i;
while(m--){
int a,b,c; cin>>a>>b>>c;
v.push_back({c,a,b});
}
sort(v.begin(),v.end());
int ans = 0,cnt = 0;
for(auto&[c,a,b]:v){
int fa = find(a),fb = find(b);
if(fa!=fb){
p[fa] = fb;
ans += c;
cnt++;
}
}
if(cnt!=n-1) cout << "impossible" << endl;
else cout << ans << endl;
return 0;
}
染色法判断二分图
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
vector<int> g[N];
int c[N];
int n,m;
int main(){
cin>>n>>m;
while(m--){
int a,b; cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
function<bool(int,int)> dfs = [&](int u,int x){
c[u] = x;
for(auto&v:g[u]){
if(!c[v]){
if(!dfs(v,3-x)) return false;
}
else if(c[v]==c[u]) return false;
}
return true;
};
int ok = 0;
for(int i=1;i<=n;i++)
if(!c[i]){
if(!dfs(i,1)) {ok++; break;}
}
cout << (!ok ? "Yes" : "No") << endl;
return 0;
}
匈牙利
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=510;
int match[N];
vector<int> g[N];
bool st[N];
int n1,n2,m;
// 如果我要的人的对象可以换人就让他换人‘
// 表示现在这个人是一定有对象了
// 所以就不会出现重复搜索的
bool find(int u){
for(auto &v:g[u]){
if(!st[v]){
st[v]=true;
if(match[v]==0||find(match[v])){
match[v]=u;
return true;
}
}
}
return false;
}
int main (){
cin>>n1>>n2>>m;
while(m--){
int a,b; cin>>a>>b;
g[a].push_back(b);
}
int res=0;
for(int i=1;i<=n1;i++){
memset(st,false,sizeof st);// 表示每一个点都可以接着探索
if(find(i)) res++;// 表示这个点匹配成功了
}
cout<<res<<endl;
return 0;
}
lca
void dfs(int u,int fa){
d[u]=d[fa]+1;
for(auto&v:g[u]){
if(v==fa) continue;
f[v][0]=u;
for(int j=1;j<20;j++)
f[v][j]=f[f[v][j-1]][j-1];
dfs(v,u);
}
}
int lca(int a,int b){
if(d[a]<d[b]) swap(a,b);
while(d[a]>d[b])
a = f[a][(int)log2(d[a]-d[b])];
if(a==b) return a;
for(int j=(int)log2(d[a]);j>=0;j--)
if(f[a][j]!=f[b][j])
a=f[a][j],b=f[b][j];
return f[a][0];
}
tarjan有向图
vector<int> g[N];
int low[N],dfn[N],id[N],scc_cnt,timestamp;
bool is_stk[N];
int stk[N],top;
int dout[N],siz[N];
void tarjan(int u){
dfn[u]=low[u]=++timestamp;
stk[++top]=u,is_stk[u]=true;
for(auto&v:g[u]){
if (!dfn[v]){
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (is_stk[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u]==dfn[u]){
++scc_cnt;
int y;
do{
y=stk[top--];
is_stk[y]=false;
id[y]=scc_cnt;
siz[scc_cnt]++;
}while(y!=u);
}
}
tarjan无向图找桥
vector<PII> g[N];
int dfn[N],low[N],is_bridge[N];
int stk[N],id[N],sz[N];
int timtamp,top,dcc;
void tarjan(int u,int fa){
dfn[u] = low[u] = ++ timtamp;
stk[++top] = u;
for(auto&[v,id]:g[u]){
if(!dfn[v]){
tarjan(v,u);
low[u] = min(low[u],low[v]);
if(dfn[u]<low[v])
is_bridge[id] = true;
}
else if(v!=fa) low[u] = min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++dcc;
int y;
do{
y = stk[top--];
id[y] = dcc;
sz[dcc]++;
}while(y!=u);
}
}
topsort
//这里填你的代码^^
#include <bits/stdc++.h>
using namespace std;
const int N = 10010,M = 1000010;
int h[N],w[M],ne[M],e[M],idx;
int d[N],dp[N],q[N];
bool st[N];
int n,m;
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
d[b]++;
}
void topsort(){
int hh=0,tt=-1;
for(int i=1;i<=n+m;i++)
if(!d[i]) q[++tt]=i;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(--d[j]==0) q[++tt]=j;
}
}
}
int main(){
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<=m;i++){
memset(st,0,sizeof st);
int start=n,end=1;
int cnt; cin>>cnt;
while(cnt--){
int x; cin>>x;
st[x]=true;
start=min(start,x);
end=max(end,x);
}
int ver=i+n;// 表示建立在中间过渡边建立虚拟源点来减少边的数量满足时间复杂度
for(int j=start;j<=end;j++)
if(st[j]) add(ver,j,1);
else add(j,ver,0);
}
topsort();
for(int i=1;i<=n;i++) dp[i]=1;
int ans=0;
for(int i=0;i<n+m;i++)
for(int j=h[q[i]];~j;j=ne[j]){
int k=e[j];
dp[k]=max(dp[k],dp[q[i]]+w[j]);
}
for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
cout<<ans<<endl;
return 0;
}
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
Kruskal重构树
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
#define endl "\n"
#define LF(x) fixed<<setprecision(x)// c++ 保留小数
#define deg(a) cout << #a << " = " << a << " ";
typedef long long LL;
typedef unsigned long long ULL;
typedef tuple<int,int,int> TUP;
typedef pair<int, int> PII;
const int N=50010,M=1010,INF=0x3f3f3f3f,pp=13331,mod=1e9+7;
const double pai=acos(-1.0);// pai
int t,n,m;
array<int,3> a[N];
int p[N];
vector<PII> g[N];
int d[N],f[N][20],w[N][20];
bool vis[N];
int find(int x){
if(x!=p[x]) p[x] = find(p[x]);
return p[x];
}
void kurskal(){
sort(a+1,a+1+m,[&](auto a,auto b){
return a[2]>b[2];
});
for(int i=1;i<=n;i++) p[i] = i;
for(int i=1;i<=m;i++){
auto [u,v,w]=a[i];
int fu = find(u),fv = find(v);
if(fu!=fv){
p[fu]=fv;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
}
}
void dfs(int u,int fa){
d[u] = d[fa] + 1;
vis[u] = true;
for(auto&[v,t]:g[u]){
if(v==fa) continue;
f[v][0] = u;
w[v][0] = t;
dfs(v,u);
}
}
int lca(int a,int b){
if(find(a)!=find(b)) return -1; // 不在一颗树
int ans = 2e9;
if(d[a]<d[b]) swap(a,b);
while(d[a]>d[b]){
int t = log2(d[a]-d[b]);
ans = min(ans,w[a][t]);
a = f[a][t];
}
if(a==b) return ans;
for(int i=(int)log2(d[a]);i>=0;i--){
if(f[a][i]!=f[b][i]){
ans = min({ans,w[a][i],w[b][i]});
a = f[a][i];
b = f[b][i];
}
}
ans = min({ans,w[a][0],w[b][0]});
return ans;
}
void solve(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v,w; cin>>u>>v>>w;
a[i]={u,v,w};
}
kurskal();
for(int i=1;i<=n;i++){
if(!vis[i]){
dfs(i,i);
w[i][0] = 2e9;
}
}
for(int j=1;j<20;j++)
for(int i=1;i<=n;i++){
f[i][j] = f[f[i][j-1]][j-1];
w[i][j] = min(w[i][j-1],w[f[i][j-1]][j-1]);
}
int q; cin>>q;
while(q--){
int a,b; cin>>a>>b;
cout << lca(a,b) << endl;
}
return ;
}
signed main (){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int _;
_ = 1;
while(_--){
solve();
}
}
三元环计数
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++){
d[i] = 0;
g[i].clear();
st[i] = {0,0};
}
for(int i=1;i<=m;i++) p[i] = i;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
d[a[i]]++;
d[b[i]]++;
}
for(int i=1;i<=m;i++){
int x = a[i],y = b[i];
if(d[x]>d[y]) swap(x,y);
else if(d[x] == d[y] and x>y) swap(x,y);
g[x].push_back({y,i});
}
auto merge = [&](int x,int y){
int fx = find(x),fy = find(y);
p[fx] = fy;
};
for(int i=1;i<=n;i++){
for(auto&[u,j]:g[i]) st[u] = {i,j};
for(auto&[u,j]:g[i]){
for(auto&[v,k]:g[u]){
if(st[v].first == i){
int p1 = st[v].second;
merge(p1,j);
merge(p1,k);
merge(j,k);
}
}
}
}
int fa = find(1);
for(int i=1;i<=m;i++){
if(find(i) != fa){
cout << "No" << endl;
return ;
}
}
cout << "Yes" << endl;
return ;
}
高级数据结构
莫队算法
如果[l,r]的答案可以在o(1)或者o(log)的时间拓展到[l-1,r],[l+1,r],[l,r+1],[l,r-1]的答案,那么就可以在时间复杂度求出所有询问的答案
int unit;
struct code{
int l,r,id;
bool operator<(const code &t) const {
if (l / unit != t.l / unit) return l < t.l;
if ((l / unit) & 1) return r < t.r;
return r > t.r;
}
}Q[N];
void add(int x){
}
void del(int x){
}
void solve(){
unit = sqrt(n);
for(int i=1;i<=m;i++){
int l,r; cin>>l>>r;
Q[i]={l,r,i};
}
sort(Q+1,Q+1+m);
int L = 1, R = 0;
for(int i=1;i<=m;i++){
auto [l,r,id]=Q[i];
while(L<l) del(L++);
while(L>l) add(--L);
while(R<r) add(++R);
while(R>r) del(R--);
}
}
AC自动机
#include <bits/stdc++.h>
using namespace std;
const int N = 10010,M=55;
int tr[N*M][26],idx;
string s;
int cnt[N*M];
int ne[N*M];
int q[N*M];
int n;
void insert()
{
int p=0;
for(int i=0;s[i];i++)
{
int u=s[i]-'a';
if(!tr[p][u]) tr[p][u]=++idx;
p=tr[p][u];
}
cnt[p]++;
}
void build(){
int hh=0,tt=-1;
for(int i=0;i<26;i++)
if(tr[0][i]) q[++tt]=tr[0][i];// 表示第一层中有这个数
while(hh<=tt){
int t=q[hh++];
for(int i=0;i<26;i++){
int p=tr[t][i];// 看一下这个数后面是不是可以接这个字母
if(!p) tr[t][i]=tr[ne[t]][i];// 表示这个数后面不可以接 也就是回溯到这个点本身的位置
else{
ne[p]=tr[ne[t]][i];// 表示这个点后面有这个点// 找到前面出现这个点的位置
q[++tt]=p;
}
}
}
}
void solve(){
memset(ne,0,sizeof ne);
memset(cnt,0,sizeof cnt);
memset(tr,0,sizeof tr);
cin>>n;
while(n--){
cin>>s;
insert();// 表示加入其中
}
build();// 建立ne指针
cin>>s;
int res=0;
for(int i=0,j=0;s[i];i++){
int u=s[i]-'a';
j=tr[j][u];// 表示这个位置后面是否有字母
// 也就是trie树中的p是否有接着走的节点
int p=j;
while(p){// 也就是在trie树中有这个点、
res+=cnt[p];
cnt[p]=0;
p=ne[p];// 接着向上面走
}
}
cout<<res<<endl;
return ;
}
int main(){
int T; cin>> T;
while(T--) solve();
return 0;
}
矩阵快速幂
void mul(i64 A[],i64 B[][N]){
i64 ans[N]={0};
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
ans[i]=(ans[i]+A[j]*B[i][j])%m;
for(int i=0;i<N;i++)
A[i]=ans[i]%m;
}
void mul(i64 A[][N],i64 B[][N]){
i64 ans[N][N]={0};
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
ans[i][j]=(ans[i][j]+A[i][k]*B[k][j]%m)%m;
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
A[i][j]=ans[i][j]%m;
}
线段树
维护区间操作,区间合并
int w[N];
struct code{
int l,r;
}tr[4*N];
void pushup(code&u,code&l,code&r){
}
void pushdown(code&u,code&l,code&r){
}
void pushup(int u){
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void pushdown(int u){
pushdown(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r){
if(l==r){
tr[u]={};
return ;
}
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 x){
if(tr[u].l>= l && tr[u].r<=r){
return ;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,l,r,x);
if(r>mid) modify(u<<1|1,l,r,x);
pushup(u);
}
code query(int u,int l,int r){
if(tr[u].l>=l && tr[u].r<=r){
return tr[u];
}
pushdown(u);
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{
code res;
code ll=query(u<<1,l,r);
code rr=query(u<<1|1,l,r);
res.l=ll.l,res.r=rr.r;
pushup(res,ll,rr);
return res;
}
}
主席树
维护不同版本,同时支持回退操作,维护区间信息
空间: o(nlogn)
时间: o(logn)
struct code{
int l,r; LL v;
}tr[N*32];
void pushup(int p){
tr[p].v = tr[tr[p].l].v + tr[tr[p].r].v;
}
int build(int l,int r){
int p = ++idx;
if(l==r){
tr[p].v = w[l];
return p;
}
int mid=l+r>>1;
tr[p].l=build(l,mid);
tr[p].r=build(mid+1,r);
pushup(p);
return p;
}
int insert(int last,int l,int r,int pos,int v){
int now=++idx;
tr[now]=tr[last];
if(l==r){
tr[now].v=v;
return now;
}
int mid=l+r>>1;
if(pos<=mid) tr[now].l=insert(tr[now].l,l,mid,pos,v);
else tr[now].r=insert(tr[now].r,mid+1,r,pos,v);
pushup(now);
return now;
}
LL query(int now,int l,int r,int L,int R){
if(L <= l and r <= R) return tr[now].v;
int mid = l+r>>1;
LL res = 0;
if(L<=mid) res+=query(tr[now].l,l,mid,L,R);
if(R>mid) res+=query(tr[now].r,mid+1,r,L,R);
return res;
}
权值线段树
对于权值去建立线段树
空间: o(nlogn)
时间: 单次(logn)
int t,n,m,k,idx;
struct code{
int l,r;
int val;
}tr[N*40];
int c[N],v[N],s[M];
int rc[N];
LL vc[N];
void update(int p){
tr[p].val=tr[tr[p].l].val+tr[tr[p].r].val;
}
void change(int&p,int l,int r,int pos,int val){
if(!p) p=++idx;
if(l==r){
tr[p].val+=val;
return ;
}
int mid=l+r>>1;
if(pos<=mid) change(tr[p].l,l,mid,pos,val);
else change(tr[p].r,mid+1,r,pos,val);
update(p);
return ;
}
int query(int p,int l,int r,int L,int R){
if(!p) return 0;
if(L<=l and r<=R) return tr[p].val;
int mid=l+r>>1;
int res = 0;
if(L<=mid) res+=query(tr[p].l,l,mid,L,R);
if(R>mid) res+=query(tr[p].r,mid+1,r,L,R);
return res;
}
void clear(){
for(int i=1;i<=n;i++) rc[i]=vc[i]=0;
for(int i=1;i<=idx;i++) tr[i].l=tr[i].r=tr[i].val=0;
idx=0;
}
树上主席树
维护对子树的查询操作
空间: o(nlogn)
时间: o(logn)
int idx,tot;
int L[N],R[N],rt[N];
struct code{
int l,r;
LL cnt;
}tr[40*N];
int insert(int last,int l,int r,int pos,int v){
int now=++tot;
tr[now]=tr[last];
if(l==r){
tr[now].cnt+=v;
return now;
}
int mid=l+r>>1;
if(pos<=mid) tr[now].l=insert(tr[last].l,l,mid,pos,v);
else tr[now].r=insert(tr[last].r,mid+1,r,pos,v);
tr[now].cnt=tr[tr[now].l].cnt+tr[tr[now].r].cnt;
return now;
}
LL query(int last,int now,int l,int r,int L,int R){
if(L<=l and r<=R) return tr[now].cnt-tr[last].cnt;
int mid=l+r>>1;
LL res = 0;
if(L<=mid) res+=query(tr[last].l,tr[now].l,l,mid,L,R);
if(R>mid) res+=query(tr[last].r,tr[now].r,mid+1,r,L,R);
return res;
}
int w[N],d[N];
vector<int> g[N];
void dfs(int u,int fa){
L[u]=++idx;
d[u]=d[fa]+1;
rt[idx]=insert(rt[idx-1],1,n,d[u],w[u]);
for(auto&v:g[u]){
if(v==fa) continue;
dfs(v,u);
}
R[u]=idx;
}
线段树合并
把儿子节点信息带到合并到父节点
空间: o(nlogn)
时间: o(logn)
int t,n,m,idx;
struct code{
int l,r;
int cnt;
}tr[N*40];
vector<int> v;
int a[N],ans[N],root[N];
vector<int> g[N];
void pushup(int p){
tr[p].cnt = tr[tr[p].l].cnt + tr[tr[p].r].cnt;
}
void change(int& p,int l,int r,int v,int val){
if(!p) p = ++idx;
if(l==r){
tr[p].cnt += val;
return ;
}
int mid = l+r>>1;
if(v<=mid) change(tr[p].l,l,mid,v,val);
else change(tr[p].r,mid+1,r,v,val);
pushup(p);
}
int query(int& p,int l,int r,int L,int R){
if(!p) return 0;
if(L <= l and r <= R) return tr[p].cnt;
int mid = l+r>>1;
int res = 0;
if(L<=mid) res+=query(tr[p].l,l,mid,L,R);
if(R>mid) res+=query(tr[p].r,mid+1,r,L,R);
return res;
}
int merge(int p,int q,int l,int r){
if(!p or !q) return p+q;
if(l==r){
tr[p].cnt += tr[q].cnt;
return p;
}
int mid = l+r>>1;
tr[p].l=merge(tr[p].l,tr[q].l,l,mid);
tr[p].r=merge(tr[p].r,tr[q].r,mid+1,r);
pushup(p);
return p;
}
void dfs(int u,int fa){
for(auto&v:g[u]){
if(v==fa) continue;
dfs(v,u);
root[u]=merge(root[u],root[v],1,m);
}
ans[u]=query(root[u],1,m,a[u]+1,m);
change(root[u],1,m,a[u],1);
}
树套树
空间: o(nlognlogn)
时间: o(nlognlogn)
int t,n,m,vn;
struct code{
int op;
int l,r,x;
}Q[N];
int a[N];
struct tree{
int l,r,v;
}tr[410*N];
int rt[N],idx;
void change(int&p,int l,int r,int pos,int val){
if(!p) p = ++ idx;
tr[p].v += val;
if(l==r) return ;
int mid = l+r>>1;
if(pos<=mid) change(tr[p].l,l,mid,pos,val);
else change(tr[p].r,mid+1,r,pos,val);
}
void add(int pos,int x,int val){
while(pos<=n){
change(rt[pos],1,vn,x,val);
pos += lowbit(pos);
}
}
int n1,n2;
int t1[N],t2[N];
int kth(int l,int r,int k){
if(l==r) return l;
int mid = l+r>>1;
int sum = 0;
for(int i=1;i<=n2;i++)
sum += tr[tr[t2[i]].l].v;
for(int i=1;i<=n1;i++)
sum -= tr[tr[t1[i]].l].v;
if(sum>=k){
for(int i=1;i<=n1;i++)
t1[i] = tr[t1[i]].l;
for(int i=1;i<=n2;i++)
t2[i] = tr[t2[i]].l;
return kth(l,mid,k);
}
else{
for(int i=1;i<=n1;i++)
t1[i] = tr[t1[i]].r;
for(int i=1;i<=n2;i++)
t2[i] = tr[t2[i]].r;
return kth(mid+1,r,k-sum);
}
}
int anskth(int l,int r,int k){
n1 = n2 = 0;
for(int i=l-1;i>=1;i-=lowbit(i))
t1[++n1] = rt[i];
for(int i=r;i>=1;i-=lowbit(i))
t2[++n2] = rt[i];
return kth(1,vn,k);
}