xcpc自用板子
可持久化线段树
/*
可持久化线段树:区间取min
每次update之后返回新版本的线段树的根结点。
查询操作传入相应版本的线段树的根结点即可。
因为要可持久化,所以必须标记永久化,不能pushdown。
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 110000;
const int inf = 1 << 30;
struct node {
int l, r, lc, rc;
int minv, tag;
} t[maxn * 20];
int cur = 0, mn;
void init() {
cur = 0;
}
void maintain(int p) {
int lc = t[p].lc, rc = t[p].rc;
t[p].minv = t[p].tag;
if (t[p].r > t[p].l) {
t[p].minv = min({ t[lc].minv, t[rc].minv, t[p].tag });
}
}
int build(int L, int R) { //只要计算mid的方式是(L + R) >> 1而不是(L + R) / 2,就可以建立负坐标线段树。
int p = ++cur;
t[p].l = L;
t[p].r = R;
t[p].tag = inf; //重置结点标记
if (L < R) {
int mid = (L + R) >> 1;
t[p].lc = build(L, mid);
t[p].rc = build(mid + 1, R);
}
maintain(p);
return p;
}
int update(int i, int L, int R, int v) { //区间取min
int p = ++cur; t[p] = t[i];
if (L <= t[p].l && R >= t[p].r) {
t[p].tag = min(t[p].tag, v);
}
else {
int mid = (t[p].l + t[p].r) >> 1;
if (L <= mid)
t[p].lc = update(t[p].lc, L, R, v);
if (R > mid)
t[p].rc = update(t[p].rc, L, R, v);
}
maintain(p);
return p;
}
void query(int p, int L, int R) { //调用之前设置mn = inf,查询区间最小值
mn = min(mn, t[p].tag);
if (L <= t[p].l && R >= t[p].r) {
mn = min(mn, t[p].minv);
}
else {
int mid = (t[p].l + t[p].r) >> 1;
if (L <= mid)
query(t[p].lc, L, R);
if (R > mid)
query(t[p].rc, L, R);
}
}
int main() {
init();
int root[10], n = 10;
root[0] = build(1, n);
root[1] = update(root[0], 1, 7, 10);
root[2] = update(root[1], 5, 8, 0);
mn = inf; query(root[2], 1, 5);
printf("%d\n", mn);
return 0;
}
线段树
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <numeric>
#include <algorithm>
#include <random>
using namespace std;
const int maxn = 210000, offset = 210000;
const int inf = 1 << 30;
struct tmp {
int data[maxn * 5];
int& operator[] (int idx) {
return data[idx + offset];
}
}A;
#define lc t[p].lchild
#define rc t[p].rchild
int cur = 0, tot, mn, mx;
struct segment {
int l, r, lchild, rchild;
int sum, min, max, set, add;
}t[maxn * 4];
void init() {
cur = 0;
}
inline void maintain(int p) {
t[p].sum = t[lc].sum + t[rc].sum;
t[p].min = min(t[lc].min, t[rc].min);
t[p].max = max(t[lc].max, t[rc].max);
}
inline void mark(int p, int setv, int addv) { //给结点打标记
if (setv >= 0) {
t[p].set = setv; t[p].add = 0;
t[p].min = t[p].max = setv;
t[p].sum = setv * (t[p].r - t[p].l + 1);
}
if (addv) {
t[p].add += addv;
t[p].min += addv;
t[p].max += addv;
t[p].sum += addv * (t[p].r - t[p].l + 1);
}
}
inline void pushdown(int p) { //pushdown将标记传递给子结点,不影响当前结点的信息。
mark(lc, t[p].set, t[p].add);
mark(rc, t[p].set, t[p].add);
t[p].set = -1;
t[p].add = 0;
}
int build(int L, int R) { //只要计算mid的方式是(L + R) >> 1而不是(L + R) / 2,就可以建立负坐标线段树。
int p = ++cur;
t[p].l = L;
t[p].r = R;
t[p].add = 0; t[p].set = -1; //清空结点标记
if (t[p].l == t[p].r) {
mark(p, 0, A[L]);
}
else {
int mid = (t[p].l + t[p].r) >> 1;
lc = build(L, mid);
rc = build(mid + 1, R);
maintain(p);
}
return p;
}
void update(int p, int L, int R, int op, int v) {
if (L <= t[p].l && R >= t[p].r) {
if (op == 0)
mark(p, -1, v);
else
mark(p, v, 0);
}
else {
pushdown(p); //如果没有pushdown只需要在最后调用一次maintain即可。
int mid = (t[p].l + t[p].r) >> 1;
if (L <= mid)
update(lc, L, R, op, v);
if (R > mid)
update(rc, L, R, op, v);
maintain(p);
}
}
void update(int p, int pos, int v) { //单点修改
if (t[p].l == t[p].r) {
mark(p, -1, v);
}
else {
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (pos <= mid)
update(lc, pos, v);
else
update(rc, pos, v);
maintain(p);
}
}
void query(int p, int L, int R) { //调用之前要设置:mn = inf; mx = -inf; tot = 0;
if (L <= t[p].l && R >= t[p].r) {
tot += t[p].sum;
mn = min(mn, t[p].min);
mx = max(mx, t[p].max);
}
else {
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (L <= mid)
query(lc, L, R);
if (R > mid)
query(rc, L, R);
}
}
int main() {
// default_random_engine e;
// int n = 100000, m = 100000;
// uniform_int_distribution<int> d(-n, n);
// for (int i = -n; i <= n; ++i)
// A[i] = d(e);
int n=10;
for(int i=1;i<=10;i++){
A[i]=5;
}
init();
int root = build(1, n);
mn = inf; mx = -inf; tot = 0;
query(root,1,10);
cout<<mn<<" "<<mx<<endl;
update(root,2,2,0,1);
mn = inf; mx = -inf; tot = 0;
query(root,2,2);
cout<<mn<<" "<<mx<<endl;
update(root,2,4,0,-1);
mn = inf; mx = -inf; tot = 0;
query(root,1,4);
cout<<mn<<" "<<mx<<endl;
// for (int i = 1; i <= m; ++i) {
// int op = rand() % 4, a = d(e), b = d(e), v = rand();
// int L = min(a, b), R = max(a, b);
// if (op == 0) {
// for (int i = L; i <= R; ++i)
// A[i] += v;
// update(root, L, R, op, v);
// }
// else if (op == 1) {
// for (int i = L; i <= R; ++i)
// A[i] = v;
// update(root, L, R, op, v);
// }
// else if (op == 2) {
// mn = inf; mx = -inf; tot = 0;
// query(root, L, R);
// if (mn != *min_element(A.data + offset + L, A.data + offset + R + 1))
// abort();
// if (mx != *max_element(A.data + offset + L, A.data + offset + R + 1))
// abort();
// if (tot != accumulate(A.data + offset + L, A.data + offset + R + 1, 0))
// abort();
// }
// else {
// A[L] += v;
// update(root, L, v);
// }
// }
return 0;
}
网络流
//链式前向星
struct Edge
{
int to, w, next;
}edges[MAXN];
int head[MAXN], cnt; // cnt为当前边的编号
inline void add(int from, int to, int w)
{
edges[++cnt].w = w; //新增一条编号为cnt+1的边,边权为w
edges[cnt].to = to; //该边的终点为to
edges[cnt].next = head[from]; //把下一条边,设置为当前起点的第一条边
head[from] = cnt; //该边成为当前起点新的第一条边
//反向边
edges[++cnt].w = 0;
edges[cnt].to = from;
edges[cnt].next = head[to];
head[to] = cnt;
}
int s, t, lv[MAXN], cur[MAXN]; // lv是每个点的层数,cur用于当前弧优化标记增广起点 ,s是源点,t是汇点
inline bool bfs() // BFS分层
{
memset(lv, -1, sizeof(lv));
lv[s] = 0;
memcpy(cur, head, sizeof(head)); // 当前弧优化初始化
queue<int> q;
q.push(s);
while (!q.empty())
{
int p = q.front();
q.pop();
for (int eg = head[p]; eg; eg = edges[eg].next)
{
int to = edges[eg].to, vol = edges[eg].w;
if (vol > 0 && lv[to] == -1)
lv[to] = lv[p] + 1, q.push(to);
}
}
return lv[t] != -1; // 如果汇点未访问过说明已经无法达到汇点,此时返回false
}
int dfs(int p = s, int flow = INF)
{
if (p == t)
return flow;
int rmn = flow; // 剩余的流量
for (int eg = cur[p]; eg && rmn; eg = edges[eg].next) // 如果已经没有剩余流量则退出
{
cur[p] = eg; // 当前弧优化,更新当前弧
int to = edges[eg].to, vol = edges[eg].w;
if (vol > 0 && lv[to] == lv[p] + 1) // 往层数高的方向增广
{
int c = dfs(to, min(vol, rmn)); // 尽可能多地传递流量
rmn -= c; // 剩余流量减少
edges[eg].w -= c; // 更新残余容量
edges[eg ^ 1].w += c; // 再次提醒,链式前向星的cnt需要初始化为 1 才能这样求反向边
}
}
return flow - rmn; // 返回传递出去的流量的大小
}
inline int dinic()
{
int ans = 0;
while (bfs())
ans += dfs();
return ans;
}
cnt=1;
DSU并查集
struct DSU{
vector<int>f,siz;
DSU(int n) : f(n), siz(n,1) {std::iota(f.begin(),f.end(),0);}
int find(int x){
while(x!=f[x]) x=f[x]=f[f[x]];
return x;
}
bool same(int x,int y){return find(x)==find(y);}
bool merge(int x,int y){
x=find(x);y=find(y);
if(x==y) return false;
siz[x]+=siz[y];
f[y]=x;
return true;
}
int size(int x){return siz[find(x)];}
};
//siz是节点数
//启发式合并
u=find(u);
v=find(v);
if(edge[u].size()<edge[v].size()){
swap(u,v);
}
ans=ans*siz[u]%p*siz[v]%p;
int cnt=0;
for(auto t:edge[v]){
if(find(t)==u){
cnt++;
}else{
edge[u].push_back(t);
}
}
if(cnt!=1){
ok=0;
break;
}
merge(u,v);
//维护每个点的深度
struct DSU{
vector<int> fa,deep;
DSU(int n):fa(n),deep(n){
iota(begin(fa),end(fa),0);
}
int find(int x){
if(x==fa[x]) return x;
int tmp=find(fa[x]);
deep[x]+=deep[fa[x]];
return fa[x]=tmp;
}
};
dsu.fa[v]=u;
dsu.deep[v]=1;
dsu.find(v);
f[dsu.find(u)]=max(f[dsu.find(u)],f[v]+dsu.deep[v]);
排列组合C
ll C[n+7][n+7];
for(int i=0;i<=n;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++){
C[i][j]=(C[i-1][j-1]+C[i-1][j])%q;
}
}
归并排序
void msort(int l,int r){
if(l==r){
return ;
}
int c[n+1];
int idx=l;
int mid=(l+r)/2;
int i=l,j=mid+1;
msort(l,mid);
msort(mid+1,r);
while(i<=mid&&j<=r){
if(a[i]<=a[j]){
c[idx++]=a[i++];
}else{
c[idx++]=a[j++];
// ans+=mid-i+1;
// ÄæÐòÊý
}
}
while(i<=mid){
c[idx++]=a[i++];
}
while(j<=r){
c[idx++]=a[j++];
}
for(int k=l;k<=r;k++){
a[k]=c[k];
}
}
凸包
// DEPENDS eq, lt, cross, V-V, P<P
using Points = vector<Point>;
double theta(Point p) { return p == O ? -1 / 0. : atan2(p.y, p.x); } // 求极角
void psort(Points &ps, Point c = O) // 极角排序
{
sort(ps.begin(), ps.end(), [&](auto p1, auto p2) {
return lt(theta(p1 - c), theta(p2 - c));
});
}
bool check(Point p, Point q, Point r) // 检查三个点组成的两个向量的旋转方向是否为逆时针
{
return lt(0, cross(q - p, r - q));
}
Points chull(Points &ps)
{
psort(ps, *min_element(ps.begin(), ps.end())); // 以最左下角的点为极角排序
Points H{ps[0]};
for (int i = 1; i < ps.size(); i++)
{
while (H.size() > 1 && !check(H[H.size() - 2], H.back(), ps[i]))
H.pop_back();
H.push_back(ps[i]);
}
return H;
}
树状数组
#define lowbit(x) ((x) & (-x))
//单点修改
int tree[MAXN];
inline void update(int i, int x)
{
for (int pos = i; pos < MAXN; pos += lowbit(pos))
tree[pos] += x;
}
//求前n项和
inline int query(int n)
{
int ans = 0;
for (int pos = n; pos; pos -= lowbit(pos))
ans += tree[pos];
return ans;
}
//区间查询
inline int query(int a, int b)
{
return query(b) - query(a - 1);
}
序列自动机
//查找子序列
struct node{
int next[26];
};
vector<node > G(n+7);
vector<int > nxt(26,0);
for (int i = n - 1; i >= 0; --i) {
nxt[s[i] - 'A'] = i + 2;
for (int ch = 0; ch < 26; ++ch)
G[i + 1].next[ch] = nxt[ch];
}
string ac="";
int cur=i;
for(auto c:ac){
cur=G[cur].next[c-'A'];
if(!cur){
break;
}
}
//例题:https://ac.nowcoder.com/acm/contest/78807/E
二进制前缀异或和情况种数
int pre[30][n+1][2];
int suff[30][n+2][2];
memset(pre,0,sizeof(pre));
memset(suff,0,sizeof(suff));
for(int i=1;i<=n;i++){
for(int j=0;j<30;j++){
int t=(a[i]>>j)&1;
pre[j][i][0]=pre[j][i-1][t^0]+(t==0);
pre[j][i][1]=pre[j][i-1][t^1]+(t==1);
}
}
for(int i=n;i>=1;i--){
for(int j=0;j<30;j++){
int t=(a[i]>>j)&1;
suff[j][i][0]=suff[j][i+1][t^0]+(t==0);
suff[j][i][1]=suff[j][i+1][t^1]+(t==1);
}
}
//第j位第i个数的前缀异或和为1或0的情况种数
//例题https://codeforces.com/contest/1957/problem/D
威尔逊定理
- 定义\(C(i,j)\)为从集合{\(1, 2, \ldots, i\)}中选择j个数构成圆排列,有多少个这种圆排列
- \(C(i,j) \bmod j = \frac{i(i-1)\cdots(i-j+1)}{j} \bmod j = \left( (j-1)! \times \left\lfloor \frac{i}{j} \right\rfloor \right) \bmod j\)
- (p-1)!%p(p-1的阶乘模p)
- 威尔逊定理:
- 如果p是4,结果为2
- 如果p是质数,结果为p-1
__int128
#define int __int128
inline void read(int &n){
int 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();
}
n=x*f;
}
inline void print(int n){
if(n<0){
putchar('-');
n*=-1;
}
if(n>9) print(n/10);
putchar(n % 10 + '0');
}
#undef int
快读
inline void read(int &n){
int 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();
}
n=x*f;
}
inline void print(int n){
if(n<0){
putchar('-');
n*=-1;
}
if(n>9) print(n/10);
putchar(n % 10 + '0');
}
树的直径(树形DP)
#include<bits/stdc++.h>
using namespace std;
const int Maxn=300000+10,inf=0x3f3f3f3f;
int d[Maxn],g[Maxn];
int f[Maxn],c[Maxn]; // 这里的c数组的含义不是跟上面对应的
bool vis[Maxn];
vector <int> e[Maxn];
int n,m,q,len;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
int find(int x)
{
if(f[x]==x)return x;
return f[x]=find(f[x]);
}
void dfs(int x,int fa) // 树形DP求树的直径
{
int m1=-1,m2=-1;
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i];
if(y==fa)continue;
dfs(y,x);
int tmp=d[y]+1;
d[x]=max(d[x],tmp);
if(tmp>m1)m2=m1,m1=tmp;
else if(tmp>m2)m2=tmp;
}
g[x]=max(max(0,m1+m2),max(m1,m2));
len=max(len,g[x]);
}
void calc(int x) // 寻找树的直径
{
len=0;
dfs(x,0);
c[x]=len;
}
int main()
{
// freopen("in.txt","r",stdin);
n=read(),m=read();
for(int i=1;i<=n;++i)
f[i]=i;
for(int i=1;i<=m;++i)
{
int x=read(),y=read();
f[find(x)]=find(y);
e[x].push_back(y);
e[y].push_back(x);
}
for(int i=1;i<=n;++i)
{
if(f[i]!=i || vis[i])continue;
calc(i);
vis[i]=1;
}
return 0;
}
二分图染色
//例题:https://codeforces.com/gym/104901/problem/G
//相邻的两个节点颜色不同
//一个节点有两种状态
//不能同时存在的两种状态用边相连,跑dfs进行判定能否进行二分图染色,若可以,情况种数为\(2^{连通块个数}\)
vector<int > vis(2*r+1,0);
int ok=1;
function<void (int ) > dfs=[&](int u){
for(auto t:edges[u]){
if(vis[t]==vis[u]){
ok=0;
return ;
}else if(vis[t]>0){
continue ;
}
if(vis[u]==1){
vis[t]=2;
}else{
vis[t]=1;
}
dfs(t);
if(ok==0){
return ;
}
}
};
ll ans=1;
for(int i=1;i<=2*r;i++){
if(vis[i]>0){
continue;
}
if(edges[i].size()==0){
continue;
}
vis[i]=1;
dfs(i);
if(ok==0){
break;
}
ans=(ans*2)%p;
}
线性基
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.push_back(x);
}
//第k大的数
sort(B.begin(), B.end());
ull ans = 0;
if (B.size() < n)
k--;
for (auto b : B) {
if (k & 1)
ans ^= b;
k >>= 1;
}
if (k == 0)
cout << ans << endl;
else
cout << -1 << endl;
//一个数是否存在
bool check(ull x) {
for (auto b : B)
x = min(x, b ^ x);
return x == 0;
}
//最大异或和就是B中所有数的异或和
ull queryMax(){
ull x=0;
for(auto b:B){
x^=b;
}
return x;
}
mex不存在的最小非负整数
int mex(auto v) // v可以是vector、set等容器
{
unordered_set<int> S;
for (auto e : v)
S.insert(e);
for (int i = 0;; ++i)
if (S.find(i) == S.end())
return i;
}
01-Trie
const int MAXN = 3200000, MAXBIT = 31;
int nex[MAXN][2], cnt;
int num[MAXN];
int mx[MAXN];
void init()
{
/*
memset(nex, 0, sizeof(nex));
memset(num, 0, sizeof(num));*/
for(int i=1;i<=cnt;i++){
nex[i][0]=nex[i][1]=0;
num[i]=0;
}
cnt = 1;
}
void insert(int n)
{
int cur = 1;
for (int i = MAXBIT; i >= 0; --i)
{
int bit = n >> i & 1; // 求出当前位并插入
if (!nex[cur][bit])
nex[cur][bit] = ++cnt;
cur = nex[cur][bit];
}
num[cur] = n;
}
int find_max(int x) // 找到与x异或最大的那个数
{
int cur = 1;
for (int i = MAXBIT; i >= 0; --i)
{
int bit = x >> i & 1;
if (nex[cur][bit ^ 1]) // 优先走与当前位不同的路径
cur = nex[cur][bit ^ 1];
else
cur = nex[cur][bit];
}
return x ^ num[cur];
}
bool query(int x,int k,int val){
int p=1;
int res=0;
for(int i=30;i>=0;--i){
int t=(x>>i)&1;
int res=(k>>i)&1;
if(res==1){
if(nex[p][t^1]){
p=nex[p][t^1];
}else{
return 0;
}
}else{
if(nex[p][t^1]){
if(mx[nex[p][t^1]]>=val){
return 1;
}
}
if(nex[p][t]){
p=nex[p][t];
}
}
}
return mx[p]>=val;
}
Trie字典树(前缀树)
const int MAXN = 500005;
int nex[MAXN][26], cnt; // 用类似链式前向星的方式存图,next[i][c]表示i号点所连、存储字符为c+'a'的点的编号
void init() // 初始化
{
memset(nex, 0, sizeof(nex)); // 全部重置为0,表示当前点没有存储字符
/*
for(int i=1;i<=cnt;i++){
for(int j=0;j<26;j++){
nex[i][j]=0;
}
}*/
cnt = 1;
}
void insert(const string &s) // 插入字符串
{
int cur = 1;
for (auto c : s)
{
// 尽可能重用之前的路径,如果做不到则新建节点
if (!nex[cur][c - 'a'])
nex[cur][c - 'a'] = ++cnt;
cur = nex[cur][c - 'a']; // 继续向下
}
}
bool find_prefix(const string &s) // 查找某个前缀是否出现过
{
int cur = 1;
for (auto c : s)
{
// 沿着前缀所决定的路径往下走,如果中途发现某个节点不存在,说明前缀不存在
if (!nex[cur][c - 'a'])
return false;
cur = nex[cur][c - 'a'];
}
return true;
}
lucas卢卡斯定理O(p+logp)
// 需要先预处理出fact[],即阶乘
inline ll C(ll m, ll n, ll p)
{
return m < n ? 0 : fact[m] * inv(fact[n], p) % p * inv(fact[m - n], p) % p;
}
inline ll lucas(ll m, ll n, ll p)
{
return n == 0 ? 1 % p : lucas(m / p, n / p, p) * C(m % p, n % p, p) % p;
}
区间dp
vector< vector<ll> > dp(2 * n + 1,vector<ll>(2 * n + 1, 0ll));//dp[2*n+1][2*n+1]初始化为0
for(int i=1;i<=2*n;i++){//枚举终点
for(int j=i-1;j>=1;j--){//枚举起点
if(i-j+1>n){//是否符合题意
break;
}
dp[j][i]=dp[j+1][i];//初始化或继承
for(int k=0;k<edge[j].size();k++){//枚举分界点
ll cnt;//由分界点划分出的各区间dp的和
dp[j][i]=max(dp[j][i],cnt);
}
}
}
最近公共祖先LCA
bool dfs(TreeNode* k,TreeNode* p,TreeNode* q){
if(k==NULL){
return false;
}
bool lson=dfs(k->left,p,q);
bool rson=dfs(k->right,p,q);
if(lson==1&&rson==1||(k==p||k==q)&&(lson==1||rson==1)){
ans=k;
}
return k==p||k==q||lson==1||rson==1;
}
//O(n)
//
int Log2[MAXN], fa[MAXN][20], dep[MAXN]; // fa的第二维大小不应小于log2(MAXN)
bool vis[MAXN];
void dfs(int cur, int fath = 0)
{
if (vis[cur])
return;
vis[cur] = true;
dep[cur] = dep[fath] + 1;
fa[cur][0] = fath;
for (int i = 1; i <= Log2[dep[cur]]; ++i)
fa[cur][i] = fa[fa[cur][i - 1]][i - 1];
for (int eg = head[cur]; eg != 0; eg = edges[eg].next)
dfs(edges[eg].to, cur);
}
int lca(int a, int b)
{
if (dep[a] > dep[b])
swap(a, b);
while (dep[a] != dep[b])
b = fa[b][Log2[dep[b] - dep[a]]];
if (a == b)
return a;
for (int k = Log2[dep[a]]; k >= 0; k--)
if (fa[a][k] != fa[b][k])
a = fa[a][k], b = fa[b][k];
return fa[a][0];
}
//
//预处理O(nlogn),查找O(logn)
int fa[MXN][31], cost[MXN][31], dep[MXN];
int n, m;
int a, b, c;
// dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。
void dfs(int root, int fno) {
// 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
fa[root][0] = fno;
dep[root] = dep[fa[root][0]] + 1;
// 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
// 2^(i-1) 的祖先节点。
for (int i = 1; i < 31; ++i) {
fa[root][i] = fa[fa[root][i - 1]][i - 1];
cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1];
}
// 遍历子节点来进行 dfs。
int sz = v[root].size();
for (int i = 0; i < sz; ++i) {
if (v[root][i] == fno) continue;
cost[v[root][i]][0] = w[root][i];
dfs(v[root][i], root);
}
}
// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lca(int x, int y) {
// 令 y 比 x 深。
if (dep[x] > dep[y]) swap(x, y);
// 令 y 和 x 在一个深度。
int tmp = dep[y] - dep[x], ans = 0;
for (int j = 0; tmp; ++j, tmp >>= 1)
if (tmp & 1) ans += cost[y][j], y = fa[y][j];
// 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
if (y == x) return ans;
// 不然的话,找到第一个不是它们祖先的两个点。
for (int j = 30; j >= 0 && y != x; --j) {
if (fa[x][j] != fa[y][j]) {
ans += cost[x][j] + cost[y][j];
x = fa[x][j];
y = fa[y][j];
}
}
// 返回结果。
ans += cost[x][0] + cost[y][0];
return ans;
}
完全背包dp
int t;
cin>>t;
int n;
cin>>n;
int dp[t+1];//dp[i]表示容量i最多可以装多少卡路里的食物
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
int a,b;//a为体积或重量,b为卡路里
cin>>a>>b;
for(int j=t;j>=1;j--){//j--表示一种食物只能选择一次,j++表示一种食物可以选择多次
if(j>=a){
dp[j]=max(dp[j],dp[j-a]+b);
}
}
}
单调队列
deque<int> Q; // 存储的是编号
for (int i = 0; i < n; ++i)
{
if (!Q.empty() && i - Q.front() >= m) // 毕业
Q.pop_front();
while (!Q.empty() && V[Q.back()] < V[i]) // 比新生弱的当场退役(求区间最小值把这里改成>即可)
Q.pop_back();
Q.push_back(i); // 新生入队
if (i >= m - 1)
cout << V[Q.front()] << " ";
}
最长公共子串
while (cin >> s1 >> s2)
{
memset(dp, 0, sizeof(dp));
int n1 = strlen(s1), n2 = strlen(s2), ma = 0;
for (int i = 1; i <= n1; ++i)
for (int j = 1; j <= n2; ++j)
if (s1[i - 1] == s2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = 0;
for (int i = 1; i <= n1; ++i)
for (int j = 1; j <= n2; ++j)
ma = max(ma, dp[i][j]);
cout << ma << endl;
}
最长公共子序列LCS
const int MAXN = 505;
char s1[MAXN], s2[MAXN];
int dp[MAXN][MAXN];
int lcs(int n1, int n2)
{
if (dp[n1][n2] != -1)
return dp[n1][n2];
if (n1 == 0 || n2 == 0)
dp[n1][n2] = 0;
else if (s1[n1 - 1] == s2[n2 - 1])
dp[n1][n2] = lcs(n1 - 1, n2 - 1) + 1;
else
dp[n1][n2] = max(lcs(n1, n2 - 1), lcs(n1 - 1, n2));
return dp[n1][n2];
}
//精简版
while (cin >> s1 >> s2)
{
memset(dp, 0, sizeof(dp));
int n1 = strlen(s1), n2 = strlen(s2);
for (int i = 1; i <= n1; ++i)
for (int j = 1; j <= n2; ++j)
if (s1[i - 1] == s2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
cout << dp[n1][n2] << endl;
}
欧拉函数、欧拉筛
//小于(或不大于,这里是一样的n但与n互质的正整数的数量 O(logn)
int phi(int n)
{
int res = n;
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
res = res / i * (i - 1); // 先除再乘防止溢出
while (n % i == 0) // 每个质因数只处理一次,可以把已经找到的质因数除干净
n /= i;
}
if (n > 1)
res = res / n * (n - 1); // 最后剩下的部分也是原来的n的质因数
return res;
}
//求1-n的欧拉函数 O(nloglogn)
int phi[MAXN];
void init(int n)
{
for (int i = 1; i <= n; i++)
phi[i] = i; // 除1外没有数的欧拉函数是本身,所以如果phi[i] = i则说明未被筛到
for (int i = 2; i <= n; i++)
if (phi[i] == i) // 未被筛到
for (int j = i; j <= n; j += i) // 所有含有该因子的数都进行一次操作
phi[j] = phi[j] / i * (i - 1);
}
//欧拉筛 O(n)
int phi[MAXN];
bool isnp[MAXN];
vector<int> primes; // 质数表
void init(int n)
{
phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!isnp[i])
primes.push_back(i), phi[i] = i - 1; // 性质一,指数为1的情形
for (int p : primes)
{
if (p * i > n)
break;
isnp[p * i] = 1;
if (i % p == 0)
{
phi[p * i] = phi[i] * p; // 性质二
break;
}
else
phi[p * i] = phi[p] * phi[i]; // 这时肯定互质,用性质三
}
}
}
拓扑排序O(n)
// deg是入度,在存图的时候需要录入数据
// A是排序后的数组
vector <int > edges[MAXN];
int deg[MAXN], A[MAXN];
bool toposort(int n)
{
int cnt = 0;
queue<int> q;
for (int i = 1; i <= n; ++i)
if (deg[i] == 0)
q.push(i);
while (!q.empty())
{
int t = q.front();
q.pop();
A[cnt++] = t;
for (auto to : edges[t])
{
deg[to]--;
if (deg[to] == 0) // 出现了新的入度为0的点
q.push(to);
}
}
return cnt == n;
}
最长上升子序列LIS dp O(nlogn)
int len = 0;
for (int i = 0; i < n; ++i)
{
if (dp[len] < a[i])
dp[++len] = a[i];
else
*lower_bound(dp + 1, dp + len + 1, a[i]) = a[i];
}
/*最长下降子序列
int dp[n+1];
memset(dp,0,sizeof(dp));
int len=0;
dp[len]=INF;
for(int i=0;i<n;i++){
if(a[i]<dp[len]){
dp[++len]=a[i];
}else{
*lower_bound(dp+1,dp+1+len,a[i],greater<int >())=a[i];
}
}
*/
set小用法
// 现存可用的元素
set<int> available;
// 需要大于等于的值
int x;
// 查找最小的大于等于x的元素
set<int>::iterator it = available.lower_bound(x);
if (it == available.end()) {
// 不存在这样的元素,则进行相应操作……
} else {
// 找到了这样的元素,将其从现存可用元素中移除
available.erase(it);
// 进行相应操作……
}
tarjan强连通图
stack<int> stk;
// instk:是否在栈中 scc:每个点所属的强连通分量编号 cscc:强连通分量的数量
int dfsn[MAXN], low[MAXN], instk[MAXN], scc[MAXN], cnt, cscc;
void tarjan(int p)
{
low[p] = dfsn[p] = ++cnt;
instk[p] = 1;
stk.push(p); // 进栈
for (auto q : edges[p])
{
if (!dfsn[q]) // 未访问过
{
tarjan(q); // 递归地搜索
low[p] = min(low[p], low[q]);
}
else if (instk[q]) // 访问过,且q可达p
low[p] = min(low[p], dfsn[q]);
}
if (low[p] == dfsn[p]) // 发现强连通分量的根
{
int top;
cscc++;
do
{
top = stk.top();
stk.pop();
instk[top] = 0;
scc[top] = cscc; // 记录所属的强连通分量
} while (top != p); // 直到弹出p才停止
}
}
Dijkstra最短路O(Elogv)
const int max_V=1e5+7;
struct edge { int to, cost; };
typedef pair <int ,int > P;//first是最短距离,second是顶点的编号
int V;
vector <edge> G[max_V];
int d[max_V];
void dijkstra(int s){
priority_queue<P, vector<P>, greater<P> > que;
fill(d,d+V+1,INF);
d[s]=0;
que.push(P(0,s));
while(!que.empty()){
P p=que.top();
que.pop();
int v=p.second;
if(d[v]<p.first){
continue;
}
for(int i=0;i<G[v].size();i++){
edge e=G[v][i];
if(d[e.to]>d[v]+e.cost){
d[e.to]=d[v]+e.cost;
que.push(P(d[e.to],e.to));
}
}
}
}
Bellman-Ford最短路O(nm)
int INF = 0x3f3f3f3f;
struct edge { int from, to, cost; }edges[12405];
int n,m;
edge es[1000];
int d[2510];
void shortest_path(int s){
for(int i=0;i<=n;i++){
d[i]=INF;
}
d[s]=0;
while(true){
bool update=false;
for(int i=0;i<m*2;i++){
if(d[edges[i].from]!=INF&&d[edges[i].to]>d[edges[i].from]+edges[i].cost){
d[edges[i].to]=d[edges[i].from]+edges[i].cost;
update=true;
}
}
if(!update){
break;
}
}
}
for(i=0;i<m;i++){
cin>>u>>v>>w;
edges[i*2].from=u;
edges[i*2].to=v;
edges[i*2].cost=w;
edges[i*2+1].from=v;
edges[i*2+1].to=u;
edges[i*2+1].cost=w;
}
shortest_path(s);
cout<<d[t]<<endl;
Floyd-Warshall最短路O(n^3)
int INF = 0x3f3f3f3f;
int d[2510][2510];
int n,m;
void warshall_floyd(){
for(int k=0;k<=n;k++){
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
}
for(i=0;i<=n;i++){
for(j=i+1;j<=n;j++){
d[i][j]=INF;
d[j][i]=INF;
}
}
for(i=0;i<m;i++){
cin>>u>>v>>w;
d[u][v]=w;
d[v][u]=w;
}
warshall_floyd();
cout<<d[s][t]<<endl;
gcdO(log a)
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
mod_powO(log n)
ll mod_pow(ll x,ll n,ll mod){
if(n==0) return 1;
ll res=mod_pow(x*x%mod,n/2,mod);
if(n&1) res=res*x%mod;
return res;
}
STL
//vector容器
vector<pair<ll,ll>> a;
for(int i=1;i<=n;i++){
ll c,b;
cin>>c;
b=i;
a.push_back(make_pair(c,b));
}
sort(a.begin(),a.end());
for(auto p:a){
//p为a数组的值
}
//
vector <vector<int >> res(k+1);//res[k+1][]
for(int i=1;i<=n;i++){
cin>>a[i];
res[a[i]].push_back(i);
}
for(int i=0;i<res.size();i++){
cout<<res[i]<<endl;
}
res[i].empty()//true为空,false为非空
//
vector <string > s;
string str;
cin>>str;
s.push_back(str);
//map容器(可做桶用
map<string,bool> s1;
string str;
cin>>str;
s1[str]=1;
并查集
const int max_N=1e5+7;
int par[max_N];
int rank[max_N];
int value[max_N];
//初始化 max_N个元素
void init(int n){
if(n==max_N) n--;
for(int i=0;i<=n;i++){
par[i]=i;
rank[i]=0;
value[i]=1;
}
}
//查询树的根
int find(int x){
if(par[x]==x){
return x;
}else{
return par[x]=find(par[x]);
}
}
//合并x和y所属的集合
void unite(int x,int y){
x=find(x);
y=find(y);
if(x==y) return ;
if(rank[x]<rank[y]){
par[x]=y;
value[y]+=value[x];
}else{
par[y]=x;
value[x]+=value[y];
if(rank[x]==rank[y]) rank[x]++;
}
}
//判断x和y是否属于同一个集合
bool same(int x,int y){
return find(x)==find(y);
}
查找子串find()
std::string s = "This is a sample string. Sample strings are useful for samples.";
std::string str = "sample";
size_t pos = s.find(str);
int count = 0;
while (pos != std::string::npos) {
count++;
pos = s.find(str, pos + 1);
}
二维差分
// 给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c
void add(int x1, int y1, int x2, int y2, int c)
{
f[x1][y1] += c;
f[x2 + 1][y1] -= c;
f[x1][y2 + 1] -= c;
f[x2 + 1][y2 + 1] += c;
}
高精度
const int LEN = 1004;
void clear(int a[]) {
for (int i = 0; i < LEN; ++i) a[i] = 0;
}
void read(int a[]) {
static char s[LEN + 1];
scanf("%s", s);
clear(a);
int len = strlen(s);
// 如上所述,反转
for (int i = 0; i < len; ++i) a[len - i - 1] = s[i] - '0';
// s[i] - '0' 就是 s[i] 所表示的数码
// 有些同学可能更习惯用 ord(s[i]) - ord('0') 的方式理解
}
void print(int a[]) {
int i;
for (i = LEN - 1; i >= 1; --i)
if (a[i] != 0) break;
for (; i >= 0; --i) putchar(a[i] + '0');
putchar('\n');
}
void add(int a[], int b[], int c[]) {
clear(c);
// 高精度实现中,一般令数组的最大长度 LEN 比可能的输入大一些
// 然后略去末尾的几次循环,这样一来可以省去不少边界情况的处理
// 因为实际输入不会超过 1000 位,故在此循环到 LEN - 1 = 1003 已经足够
for (int i = 0; i < LEN - 1; ++i) {
// 将相应位上的数码相加
c[i] += a[i] + b[i];
if (c[i] >= 10) {
// 进位
c[i + 1] += 1;
c[i] -= 10;
}
}
}
void sub(int a[], int b[], int c[]) {
clear(c);
for (int i = 0; i < LEN - 1; ++i) {
// 逐位相减
c[i] += a[i] - b[i];
if (c[i] < 0) {
// 借位
c[i + 1] -= 1;
c[i] += 10;
}
}
}
void mul_short(int a[], int b, int c[]) {
clear(c);
for (int i = 0; i < LEN - 1; ++i) {
// 直接把 a 的第 i 位数码乘以乘数,加入结果
c[i] += a[i] * b;
if (c[i] >= 10) {
// 处理进位
// c[i] / 10 即除法的商数成为进位的增量值
c[i + 1] += c[i] / 10;
// 而 c[i] % 10 即除法的余数成为在当前位留下的值
c[i] %= 10;
}
}
}
void mul(int a[], int b[], int c[]) {
clear(c);
for (int i = 0; i < LEN - 1; ++i) {
// 这里直接计算结果中的从低到高第 i 位,且一并处理了进位
// 第 i 次循环为 c[i] 加上了所有满足 p + q = i 的 a[p] 与 b[q] 的乘积之和
// 这样做的效果和直接进行上图的运算最后求和是一样的,只是更加简短的一种实现方式
for (int j = 0; j <= i; ++j) c[i] += a[j] * b[i - j];
if (c[i] >= 10) {
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
}
}
// 被除数 a 以下标 last_dg 为最低位,是否可以再减去除数 b 而保持非负
// len 是除数 b 的长度,避免反复计算
bool greater_eq(int a[], int b[], int last_dg, int len) {
// 有可能被除数剩余的部分比除数长,这个情况下最多多出 1 位,故如此判断即可
if (a[last_dg + len] != 0) return true;
// 从高位到低位,逐位比较
for (int i = len - 1; i >= 0; --i) {
if (a[last_dg + i] > b[i]) return true;
if (a[last_dg + i] < b[i]) return false;
}
// 相等的情形下也是可行的
return true;
}
void div(int a[], int b[], int c[], int d[]) {
clear(c);
clear(d);
int la, lb;
for (la = LEN - 1; la > 0; --la)
if (a[la - 1] != 0) break;
for (lb = LEN - 1; lb > 0; --lb)
if (b[lb - 1] != 0) break;
if (lb == 0) {
puts("> <");
return;
} // 除数不能为零
// c 是商
// d 是被除数的剩余部分,算法结束后自然成为余数
for (int i = 0; i < la; ++i) d[i] = a[i];
for (int i = la - lb; i >= 0; --i) {
// 计算商的第 i 位
while (greater_eq(d, b, i, lb)) {
// 若可以减,则减
// 这一段是一个高精度减法
for (int j = 0; j < lb; ++j) {
d[i + j] -= b[j];
if (d[i + j] < 0) {
d[i + j + 1] -= 1;
d[i + j] += 10;
}
}
// 使商的这一位增加 1
c[i] += 1;
// 返回循环开头,重新检查
}
}
}
树上dp
#include <bits/stdc++.h>
using namespace std ;
const int max_N=2e5+7;
const int INF = 0x3f3f3f3f;
typedef long long ll;
vector<int> g[max_N];
ll dp[max_N];
void dfs(int u,int fa){
if(g[u].size()==1&&u-1){
dp[u]=1;
}
for(auto v : g[u]){
if(v==fa){
continue ;
}
dfs(v,u);
dp[u]+=dp[v];
}
}
void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
g[i].clear();
dp[i]=0;
}
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1,0);
int q;
cin>>q;
for(int i=1;i<=q;i++){
int x,y;
cin>>x>>y;
cout<<dp[x]*dp[y]<<endl;
}
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}
位运算符
0&0=0;
0&1=0;
1&0=0;
1&1=1;
//全1出1
0|0=0;
0|1=1;
1|0=1;
1|1=1;
//有1出1
0^0=0;
0^1=1;
1^0=1;
1^1=0;
//一样出0,不一样出1
逆元(除数取模)
//拓展欧几里得
ll exgcd(ll a, ll b, ll &x, ll &y)// 拓欧
{
if (b == 0)
{
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
ll inv(ll a, ll p)
{
ll x, y;
if (exgcd(a, p, x, y) != 1) // 无解的情形
return -1;
return (x % p + p) % p;
}
//inv(a,p)就是取模p意义下的1/a;
//费马小定理
inline ll qpow(ll a, ll n, ll p)// 快速幂
{
ll ans = 1;
while (n)
{
if (n & 1)
ans = ans % p * a % p;
a = a % p * a % p;
n >>= 1;
}
return ans;
}
inline ll inv(ll a, ll p)
{
return qpow(a, p - 2, p);
}
三维bfs
char g[max_N][max_N][max_N];
const int dx[]={0,0,0,0,-1,1},
dy[]={0,0,-1,1,0,0},
dz[]={-1,1,0,0,0,0};
struct node
{
int x,y,z;
};
int l,r,c;
queue<node>q;
int d[max_N][max_N][max_N];
int bfs(node st){
while(!q.empty()){
q.pop();
}
q.push(st);
d[st.x][st.y][st.z]=0;
while(!q.empty()){
node t=q.front();
q.pop();
g[t.x][t.y][t.z]='#';
for(int i=0;i<6;i++){
int x=t.x+dx[i],y=t.y+dy[i],z=t.z+dz[i];
if(x<0||x>=l||y<0||y>=r||z<0||z>=c){
continue;
}
if(g[x][y][z]=='#'){
continue;
}
d[x][y][z]=d[t.x][t.y][t.z]+1;
if(g[x][y][z]=='E'){
return d[x][y][z];
}
g[x][y][z]='#';
node tt;
tt.x=x,tt.y=y,tt.z=z;
q.push(tt);
}
}
return 0;
}
快速幂
#include <iostream>
long long Quick_Power(long long x,long long n)
{
if(n==0)
{
return 1;
}
else if(n%2==1)
{
return Quick_Power(x,n-1)*x;
}
else
{
long long tmp=Quick_Power(x,n/2);
return tmp*tmp;
}
}
using namespace std;
int main()
{
long long x,n;
cin>>x>>n;
cout<<Quick_Power(x,n);
}
long long ksm(long long a, long long b)
{
long long res = 1;
while(b)
{
if(b & 1)
res = res * a % M;
a = a * a % M;
b >>= 1;
}
return res;
}
tarjan强连通
void tarjan(int now)
{
dfn[now]=low[now]=++cnt; //初始化
stack[++t]=now; //入栈操作
v[now]=1; //v[]代表该点是否已入栈
for(int i=f[now];i!=-1;i=e[i].next) //邻接表存图
if(!dfn[e[i].v]) //判断该点是否被搜索过
{
tarjan(e[i].v);
low[now]=min(low[now],low[e[i].v]); //回溯时更新low[ ],取最小值
}
else if(v[e[i].v])
low[now]=min(low[now],dfn[e[i].v]); //一旦遇到已入栈的点,就将该点作为连通量的根
//这里用dfn[e[i].v]更新的原因是:这个点可能
//已经在另一个强连通分量中了但暂时尚未出栈,所
//以now不一定能到达low[e[i].v]但一定能到达
//dfn[e[i].v].
if(dfn[now]==low[now])
{
int cur;
do
{
cur=stack[t--];
v[cur]=false; //不要忘记出栈
}while(now!=cur);
}
}

浙公网安备 33010602011771号