模板目录
简单算法
快读
inline int read()
{
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
x = x * 10 + c - '0', c = getchar();
return x * f;
}
快速幂+逆元
int quickpow(int a, int b) //快速幂求a^b%mod
{
if (b < 0)
return 0;
int ret = 1;
a %= mod;
while (b)
{
if (b & 1)
ret = (ret * a) % mod;
b >>= 1;
a = (a * a) % mod;
}
return ret;
}
int inv(int a)
{
return quickpow(a, mod - 2);
}
排列组合
int C(int m, int n)
{
int ans = 1;
for (int i = 1, j = m; i <= n; i++, j--)
{
ans *= j;
ans /= i;
}
return ans;
}
int A(int m,int n){
int ans=1;
for(int i=1,j=m;i<=n;i++,j--){
ans*=j;
}
return ans;
}
子集枚举
int a[N];
bitset<1000010> st;
vector<int> v;
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
st[0] = 1;
for (int i = 1; i <= n; i++)
st |= st << a[i];
for (int i = 1; i < 1000010; i++)
{
if (st[i])
v.push_back(i);
}
二维前缀和
long long prefixSum[N][N]; //前缀和数组
int nums[N][N]; //原数组
int n, m;
void init()
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1] + nums[i][j];
}
}
int query(int x1, int y1, int x2, int y2) {
return (prefixSum[x2][y2] - prefixSum[x2][y1 - 1] - prefixSum[x1 - 1][y2] + prefixSum[x1 - 1][y1 - 1]);
}
差分
int a[N]; //原函数
int b[N]; //差分函数
/*在原函数没有赋值全为0的时候,差分函数自然也是全为0*/
void insert(int l, int r, int c) //操作函数,把a[L]~a[R]全部加上c
{
b[l] += c;
b[r + 1] -= c;
}
/*
ll cf[1005][1005];
ll insert(ll x1, ll y1, ll x2, ll y2,ll c)
{
cf[x1][y1] += c;
cf[x1][y2 + 1] -= c;
cf[x2+1][y1] -= c;
cf[x2+1][y2 + 1] += c;
}
*/
Bound函数
/*
int a[10] = { 1, 2, 3, 4, 6, 7, 7, 8, 9, 10 };
//第一个不小于
int x = lower_bound(a, a + 10, 5) - a;
cout << x << ' ' << a[x] << endl;
//第一个大于
x = upper_bound(a, a + 10, 5) - a;
cout << x << ' ' << a[x];
*/
离散化
int n,m;
int a[N],s[N];
void discreatize()
{
int id=0;
for(int i=1;i<=n;i++)
s[++id]=a[i];
int m=unique(s+1,s+1+id)-s-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(s+1,s+1+n,a[i])-s;
}
欧拉筛
int st[N];
int primes[N], cnt;
void ola()
{
st[1] = 1;
for (int i = 2; i < N; i++)
{
if (!st[i])
primes[cnt++] = i;
for (int j = 0; primes[j] * i < N; j++)
{
st[primes[j] * i] = 1;
if (i % primes[j] == 0)
break;
}
}
}
数据结构
线段树
int a[N];
struct Node
{
int l, r; //树的点代表的边界
int sum, lazy; //总和,懒标记
} tr[N << 2];
inline void pushup(int u) //向上更新父节点
{
tr[u].sum = tr[u * 2].sum + tr[u * 2 + 1].sum;
}
inline void pushdown(int u) //懒标记下推
{
if (tr[u].lazy)
{
//修改左右儿子的和值
tr[u * 2].sum += tr[u].lazy * (tr[u * 2].r - tr[u * 2].l + 1);
tr[u * 2 + 1].sum += tr[u].lazy * (tr[u * 2 + 1].r - tr[u * 2 + 1].l + 1);
//修改左右儿子懒标记值
tr[u * 2].lazy += tr[u].lazy;
tr[u * 2 + 1].lazy += tr[u].lazy;
tr[u].lazy = 0;
}
}
void build(int u, int l, int r) //建树
{
tr[u].l = l, tr[u].r = r;
if (l == r)
{
tr[u].sum = a[l];
return;
}
int mid = (l + r) >> 1;
build(u * 2, l, mid), build(u * 2 + 1, mid + 1, r);
pushup(u);
}
void modify(int u, int l, int r, int d) //区间修改
{
if (l <= tr[u].l && r >= tr[u].r) //结点区域为修改区域子集
{
tr[u].sum += d * (tr[u].r - tr[u].l + 1); //打上懒标记返回
tr[u].lazy += d;
return;
}
pushdown(u); //懒标记下推
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(u * 2, l, r, d);
if (r > mid) modify(u * 2 + 1, l, r, d);
pushup(u);
}
int query(int u, int l, int r) //区间查询
{
if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
pushdown(u); //懒标记下推
int mid = (tr[u].l + tr[u].r) >> 1;
int sum = 0;
if (l <= mid) sum += query(u * 2, l, r);
if (r > mid) sum += query(u * 2 + 1, l, r);
return sum;
}
/*
build(1, 1, n);
*/
树套树(线段树+平衡树)
int n, m;
int w[N];
struct Node
{
int l, r;
vector<int> s;
} tr[N << 2];
void build(int u, int l, int r)
{
tr[u] = { l, r };
if (l == r)
{
tr[u].s.pb(w[l]);
return;
}
int mid = (l + r) >> 1;
int ls = u << 1, rs = u << 1 | 1;
build(ls, l, mid), build(rs, mid + 1, r);
int lsz = tr[ls].s.size(), rsz = tr[rs].s.size();
int i = 0, j = 0;
int a, b;
while (i < lsz && j < rsz)
{
a = tr[ls].s[i], b = tr[rs].s[j];
if (a <= b)
{
tr[u].s.pb(a);
i++;
}
else
{
tr[u].s.pb(b);
j++;
}
}
while (i < lsz)
{
a = tr[ls].s[i];
tr[u].s.pb(a);
i++;
}
while (j < rsz)
{
b = tr[rs].s[j];
tr[u].s.pb(b);
j++;
}
}
//build(1,1,n)
void update(int u, int pos, int x) //把pos变为x
{
tr[u].s.erase(lower_bound(all(tr[u].s), w[pos]));
tr[u].s.insert(lower_bound(all(tr[u].s), x), x);
int mid = (tr[u].l + tr[u].r) >> 1;
if (tr[u].l == tr[u].r)
return;
if (pos <= mid)
update(u << 1, pos, x);
else
update(u << 1 | 1, pos, x);
}
//w[pos]=x;
int query_rank(int u, int l, int r, int x) //有多少小于k
{
if (l <= tr[u].l && tr[u].r <= r)
return lower_bound(all(tr[u].s), x) - tr[u].s.begin();
int mid = (tr[u].l + tr[u].r) >> 1;
int res = 0;
if (l <= mid)
res += query_rank(u << 1, l, r, x);
if (mid < r)
res += query_rank(u << 1 | 1, l, r, x);
return res;
}
int query_k(int l, int r, int k) // l到r区间内第k大的数
{
int le = 0, ri = 1e8, mid;
while (le < ri)
{
mid = (le + ri + 1) >> 1;
if (query_rank(1, l, r, mid) + 1 <= k)
le = mid;
else
ri = mid - 1;
}
return ri;
}
int query_pre(int u, int l, int r, int x) //找x的前驱
{
if (l <= tr[u].l && tr[u].r <= r)
{
auto it = lower_bound(all(tr[u].s), x);
if (it == tr[u].s.begin())
return -INF;
else
return *--it;
}
int mid = (tr[u].l + tr[u].r) >> 1;
int res = -INF;
if (l <= mid)
res = max(res, query_pre(u << 1, l, r, x));
if (mid < r)
res = max(res, query_pre(u << 1 | 1, l, r, x));
return res;
}
int query_nex(int u, int l, int r, int x) //找x的后继
{
if (l <= tr[u].l && tr[u].r <= r)
{
auto it = upper_bound(all(tr[u].s), x);
return (it == tr[u].s.end() ? INF : *it);
}
int mid = (tr[u].l + tr[u].r) >> 1;
int res = INF;
if (l <= mid)
res = min(res, query_nex(u << 1, l, r, x));
if (mid < r)
res = min(res, query_nex(u << 1 | 1, l, r, x));
return res;
}
树状数组
int tr[N];
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int v)//实现第x个数加v
{
for (int i = x; i < N; i += lowbit(i))tr[i] += v;
}
int query(int x)//询问1-x的和
{
int res = 0;
for (int i = x; i > 0; i -= lowbit(i))res += tr[i];
return res;
}
树链剖分
int n, m;
int a[N];
int h[N], e[N], ne[N], cnt;
//树链剖分后编号,权值,众联顶点编号,父节点,深度,子树结点数量,重儿子编号
int id[N], nw[N], top[N], fa[N], dep[N], sz[N], son[N], idx;
struct node
{
int l, r, sum, lazy;
} tr[N << 2];
void add(int a, int b)
{
ne[cnt] = h[a];
h[a] = cnt;
e[cnt++] = b;
}
/*树链剖分预处理*/
//预处理找出重儿子,深度
void dfs1(int u, int f, int d)
{
dep[u] = d, fa[u] = f, sz[u] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == f)
continue;
dfs1(j, u, d + 1);
sz[u] += sz[j];
if (sz[son[u]] < sz[j])
son[u] = j;
}
}
//化树为链,t是重链的顶点
void dfs2(int u, int t)
{
id[u] = ++idx, nw[idx] = a[u], top[u] = t;
if (!son[u])
return;
dfs2(son[u], t);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa[u] || j == son[u])
continue;
dfs2(j, j);
}
}
/*线段树的部分*/
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.lazy)
{
left.sum += root.lazy * (left.r - left.l + 1);
left.lazy += root.lazy;
right.sum += root.lazy * (right.r - right.l + 1);
right.lazy += root.lazy;
root.lazy = 0;
}
}
void build(int u, int l, int r)
{
tr[u] = { l, r, nw[r], 0 };
if (l == r)
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 l, int r, int k)
{
if (l <= tr[u].l && r >= tr[u].r)
{
tr[u].lazy += k;
tr[u].sum += k * (tr[u].r - tr[u].l + 1);
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid)
update(u << 1, l, r, k);
if (mid < r)
update(u << 1 | 1, l, r, k);
pushup(u);
}
int query(int u, int l, int r)
{
if (l <= tr[u].l && r >= tr[u].r)
return tr[u].sum;
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
int res = 0;
if (l <= mid)
res += query(u << 1, l, r);
if (mid < r)
res += query(u << 1 | 1, l, r);
return res;
}
/*树链剖分部分*/
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])
swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
void update_path(int u, int v, int k)
{
while (top[u] != top[v])
{
// u是深度高的重链
if (dep[top[u]] < dep[top[v]])
swap(u, v);
update(1, id[top[u]], id[u], k);
u = fa[top[u]];
}
if (dep[u] < dep[v])
swap(u, v);
update(1, id[v], id[u], k);
}
int query_path(int u, int v)
{
int res = 0;
while (top[u] != top[v])
{
if (dep[top[u]] < dep[top[v]])
swap(u, v);
res += query(1, id[top[u]], id[u]);
u = fa[top[u]];
}
if (dep[u] < dep[v])
swap(u, v);
res += query(1, id[v], id[u]);
return res;
}
void update_tree(int u, int k)
{
update(1, id[u], id[u] + sz[u] - 1, k);
}
int query_tree(int u)
{
return query(1, id[u], id[u] + sz[u] - 1);
}
树上差分
int n, m;
int a[N];
int h[N], e[N], ne[N], cnt;
//树链剖分后编号,权值,众联顶点编号,父节点,深度,子树结点数量,重儿子编号
int top[N], fa[N], dep[N], sz[N], son[N];
int fath[N][21];
struct node
{
int l, r, sum, lazy;
} tr[N << 2];
void add(int a, int b)
{
ne[cnt] = h[a];
h[a] = cnt;
e[cnt++] = b;
}
//预处理找出重儿子,深度
void dfs1(int u, int f, int d)
{
dep[u] = d, fa[u] = f, sz[u] = 1;
fath[u][0] = f;
for (int i = 1; (1 << i) <= dep[u]; i++)
fath[u][i] = fath[fath[u][i - 1]][i - 1];
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == f)
continue;
dfs1(j, u, d + 1);
sz[u] += sz[j];
if (sz[son[u]] < sz[j])
son[u] = j;
}
}
//化树为链,t是重链的顶点
void dfs2(int u, int t)
{
top[u] = t;
if (!son[u])
return;
dfs2(son[u], t);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa[u] || j == son[u])
continue;
dfs2(j, j);
}
}
int LCA(int x, int y)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]])
swap(x, y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int cf[N];
void init()
{
for (int i = 1; i <= n; i++)
{
cf[i] += a[i];
cf[i + 1] -= a[i];
}
}
void add_edge(int u, int v, int k)
{
cf[u] += k;
cf[v] += k;
cf[LCA(u, v)] -= 2 * k;
}
void add_point(int u, int v, int k)
{
cf[u] += k;
cf[v] += k;
int anc = LCA(u, v);
cf[anc] -= k;
cf[fa[anc]] -= k;
}
void dfs(int u, int f)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j==f)continue;
dfs(j, u);
cf[u] += cf[j];
}
}
并查集
int n;
int p[N];
void init()
{
for(int i=0;i<N;i++)p[i]=i;
}
int find(int x)
{
return x==p[x]?x:p[x]=find(p[x]);
}
void join(int x,int y)
{
if(find(x)!=find(y))
p[find(x)]=find(y);
}
带权并查集
int p[N],d[N];//d为到根节点的距离
int find(int x)
{
if(x!=p[x])
{
int fa=p[x];
p[x]=find(p[x]);
d[x]+=d[fa];
}
return p[x];
}
void join(int x,int y,int s)//将两点合并,xh,两点间距离为s
{
int px=find(x);
int py=find(y);
if(px!=py)
{
p[px]=p[y];
d[px]=d[y]-d[x]+s;
}
}
int getdis(int x,int y)//获取同并查集中两点距离
{
int px=find(x),py=find(y);
if(px!=py)return -1;
else return max((long long)0,abs(d[x]-d[y])+1);
}
/*
for(int i=1;i<N;i++)p[i]=i;
*/
KMP
int te;
int ne[N];
string w, t;//t中有多少个w
int num;
void setNext()
{
int lw = w.size();
int i = 0, j = -1;
ne[0] = -1;
while (i < lw)
{
if (j == -1 || w[i] == w[j])
ne[++i] = ++j;
else
j = ne[j];
}
}
void kmp()
{
int lw = w.size();
int lt = t.size();
int i = -1, j = -1;
while (i < lt)
{
if (j == -1 || w[j] == t[i])
{
j++, i++;
if (j == lw) num++;
}
else
j = ne[j];
}
}
//循环节最长为 a.size()-next[a.size()]
扩展KMP
int te;
int ne[N],ex[N];
string w, t;//t中有多少个w
int num;
void setNext()
{
int lw = w.size();
int i = 0, j = -1;
ne[0] = -1;
while (i < lw)
{
if (j == -1 || w[i] == w[j])
ne[++i] = ++j;
else
j = ne[j];
}
}
void kmp()
{
int lw = w.size();
int lt = t.size();
int i = -1, j = -1;
while (i < lt)
{
if (j == -1 || w[j] == t[i])
{
j++, i++;
ex[i]=j;
if (j == lw) num++;
}
else
j = ne[j];
}
}
AC自动机
int tr[N][26], id; // TRIE
int cnt[N]; //标记字符串结尾
int fail[N]; // fail指针
void insert(string s)
{ //插入模式串
int p = 0;
for (int i = 0; i<s.size(); i++)
{
int k = s[i] - 'a';
if (!tr[p][k])
tr[p][k] = ++id;
p = tr[p][k];
}
cnt[p]++;
}
void build()
{
queue<int> q;
memset(fail, 0, sizeof(fail));
for (int i = 0; i < 26; i++)
if (tr[0][i])
q.push(tr[0][i]);
//首字符入队
//不直接将0入队是为了避免指向自己
while (!q.empty())
{
int k = q.front();
q.pop(); //当前结点
for (int i = 0; i < 26; i++)
{
if (tr[k][i])
{
fail[tr[k][i]] = tr[fail[k]][i]; //构建当前的fail指针
q.push(tr[k][i]); //入队
}
else
tr[k][i] = tr[fail[k]][i];
//匹配到空字符,则索引到父节点fail指针对应的字符,以供后续指针的构建
//类似并差集的路径压缩,把不存在的tr[k][i]全部指向tr[fail[k]][i]
//这句话在后面匹配主串的时候也能帮助跳转
}
}
}
int query(string t)
{
int p = 0, res = 0;
for (int i = 0; i<t.size(); i++)
{
p = tr[p][t[i] - 'a'];
for (int j = p; j && ~cnt[j]; j = fail[j])
res += cnt[j], cnt[j] = -1;
}
return res;
}
后缀数组
int n, m, k, S, T;
// x--存储第一关键字
// y--存储第二关键字y[i]=j,按照第二关键字排名第i的后缀的下标为j
// c--存储每个数值的数目
// rk--rk[i]表示从i开始的后缀的排名
// sa--sa[i]表示排名为i的后缀的起始下标
// h--h[i]表示排名为i的后缀和排名为i - 1的后缀的最长前缀
int x[N], y[N], c[N], rk[N], sa[N], h[N];
char s[N];
void get_sa()
{
//先把ASCII码转换为第一关键字排序
for (int i = 1; i <= n; ++i)
c[x[i] = s[i]]++;
for (int i = 2; i <= m; ++i)
c[i] += c[i - 1];
/*排序*/
for (int i = n; i; --i)
sa[c[x[i]]--] = i;
for (int k = 1; k <= n; k <<= 1)
{
int num = 0;
//先按照第二关键字排序,下标从i开始的后缀的第二关键字为从i+k开始的第一关键字
//先将无第二关键字的后缀排先名,此时y[i]表示按照第二关键字排名为i的起始下标
/*无第二关键字排序*/
for (int i = n - k + 1; i <= n; ++i)
y[++num] = i;
/*有第二关键字排序*/
for (int i = 1; i <= n; ++i)
{
//上一层循环中按照第一关键字的排名的下标已经存在sa数组中
//按从小到大顺序枚举sa数组可保证按照第一关键字从小到大
//所有起始下标超过k才存在第二关键字
//将所有存在第二关键字的后缀存储在y数组中,此时是按i + k的第一关键字,下标要-k
if (sa[i] > k)
y[++num] = sa[i] - k;
}
//将计数数组清空
for (int i = 1; i <= m; ++i)
c[i] = 0;
for (int i = 1; i <= n; ++i)
c[x[i]]++;
for (int i = 2; i <= m; ++i)
c[i] += c[i - 1];
// y数组存储的是按照第二关键字从小到大排序后的后缀的起始下标
// i从大到小枚举即可保证按照第一关键字排序后依然是按照第二关键字排序后的相对顺序不变
/*第一关键字排序*/
for (int i = n; i; --i)
sa[c[x[y[i]]]--] = y[i], y[i] = 0;
swap(x, y), num = 1;
//将所有后缀离散化
x[sa[1]] = 1;
//此时y存储的是从i开始的后缀的第一关键字
for (int i = 2; i <= n; ++i)
{
//若当前后缀和排名前一位的后缀第一关键字和第二关键字相同则离散化后的数值相同,否则+1
x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] and y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
}
if (num == n)
break;
m = num;
}
}
void get_h()
{
for (int i = 1; i <= n; ++i)
rk[sa[i]] = i;
for (int i = 1, k = 0; i <= n; ++i)
{
if (rk[i] == 1)
continue;
// h[rk[i]] >= h[rk[i - 1]] - 1
if (k)
k--;
// j为排名前一位的起始下标
int j = sa[rk[i] - 1];
//求最长相同前缀
while (i + k <= n and j + k <= n and s[i + k] == s[j + k])
k++;
h[rk[i]] = k;
}
}
void solve()
{
cin >> s + 1;
n = strlen(s + 1), m = 122;
get_sa();
get_h();
}
/*
Q1:一个串中两个串的最大公共前缀
A1:就是Height,用rmq预处理,再O(1)查询。
Q2:一个串中可重叠的重复最长子串是多长?
A2:就是求任意两个后缀的最长公共前缀,而任意两个后缀的最长公共前缀都是Height 数组里某一段的最小值,那最长的就是Height中的最大值。
Q3:一个串种不可重叠的重复最长子串是多长?
A3:先二分答案,转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求,只需把排序后的后缀分成若干组,其中每组的后缀之间的Height 值都不小于k,再判断其中有没有不重复的后缀,具体就是看最大的SA值和最小的SA值相差超不超过k,有一组超过的话k就是合法答案。
A4:一个字符串不相等的子串的个数是多少?
Q4:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。而且可以发现每一个后缀Suffix[SA[i]]的贡献是Len - SA[i] + 1,但是有子串算重复,重复的就是Heigh[i]个与前面相同的前缀,那么减去就可以了。最后,一个后缀Suffix[SA[i]]的贡献就是Len - SA[k] + 1 - Height[k]。
*/
Manacher
char b[N];
string s;
int len, lenb;
int p[N];
void inin()
{
b[lenb++] = '$';
b[lenb++] = '#';
for (int i = 0; i < s.size(); i++)
{
b[lenb++] = s[i];
b[lenb++] = '#';
}
b[lenb++] = '^';
}
void manachar()
{
inin();
int mr = 0, mid = 0;
for (int i = 1; b[i] != '^'; i++)
{
if (i < mr)
p[i] = min(p[2 * mid - i], mr - i);
else
p[i] = 1;
while (b[i + p[i]] == b[i - p[i]])
p[i]++;
if (p[i] + i > mr)
{
mr = p[i] + i;
mid = i;
}
}
}
void solve()
{
cin >> s;
manachar();
int res = 0;
for (int i = 1; i <= lenb; i++)
{
res = max(res, p[i]);
}
cout << res - 1; //记得减一
}
ST表
int n;
int a[N];
int dp[N][20];
void init()
{
for (int j = 0; j < 18; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
if (!j)
dp[i][j] = a[i];
else
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
int query(int l,int r){
int k = r - l + 1;
k = log(k) / log(2);
int ans = max(dp[l][k], dp[r - (1 << k) + 1][k]);
return ans;
}
字典树
const int N = 1000050;
int trie[N][26];
int cnt[N];
int id;
void insert(string s)
{
int p = 0;
for (int i = 0; i < s.size(); i++)
{
int x = s[i] - 'a';
if (trie[p][x] == 0) trie[p][x] = ++id;
p = trie[p][x];
}
cnt[p]++;
}
int find(string s)
{
int p = 0;
for (int i = 0; i < s.size(); i++)
{
int x = s[i] - 'a';
if (trie[p][x] == 0)return 0;
p = trie[p][x];
}
return cnt[p];
}
Treap平衡树
int n;
struct Node
{
int l, r; //左右节点编号
int k; //键值
int val; //随机分配的堆中的编号,不断旋转保证编号大的点一定在编号小的点的上方
int cnt, size; //cnt代表k有多少个,size代表以u为根的树有多少个点
}tr[N];
int root, idx;
void pushup(int u)
{
tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt;
}
int new_node(int k) //新建点
{
tr[++idx].k = k;
tr[idx].val = rand(); //rand函数赋予随机值
tr[idx].cnt = 1;
tr[idx].size = 1;
return idx; //返回节点编号方便递归建树
}
void zag(int& u) //左旋函数,看不懂可以画图理解一下
{
int q = tr[u].r;
tr[u].r = tr[q].l;
tr[q].l = u;
u = q;
pushup(tr[u].l);
pushup(u);
}
void zig(int& u) //右转函数
{
int q = tr[u].l;
tr[u].l = tr[q].r;
tr[q].r = u;
u = q;
pushup(tr[u].r);
pushup(u);
}
void build() //建树,树的初始化
{
new_node(-INF), new_node(INF); //防止越界的哨兵
root = 1, tr[1].r = 2;
pushup(root);
if (tr[1].val < tr[2].val) zag(root);
}
void del(int& u, int k) //树的删除操作
{
if (u == 0) return; //没有这个点就不操作
if (tr[u].k == k) //找到了这个点
{
if (tr[u].cnt > 1) tr[u].cnt--; //这个点的数量大于1就直接减去一个就可以了
else
{
if (tr[u].l || tr[u].r) //不是叶子节点
{
if (!tr[u].r || tr[tr[u].l].val)
//如果左边有点就把要删除的点通过右转转到右边去,然后通过递归不断旋转直到转到叶子节点后删除
{
zig(u);
del(tr[u].r, k);
}
else //否则转到左边去
{
zag(u);
del(tr[u].l, k);
}
}
else //是叶子节点就可以直接删除了
u = 0;
}
}
else if (tr[u].k > k) del(tr[u].l, k);//如果没有找到就判断一下在左右两边的哪一边
else del(tr[u].r, k);//找一下
pushup(u);//上传更改
}
void insert(int& u, int k) //点的插入操作
{
if (u == 0) u = new_node(k); //走到0说明当前位置没有点,就把点插在这里
else
{
if (tr[u].k == k) //重复点数量直接加1
tr[u].cnt++;
else
{
if (tr[u].k > k) //比当前点的键值大就插入左边
{
insert(tr[u].l, k);
if (tr[tr[u].l].val > tr[u].val) zig(u); //平衡旋转
}
else //否则插入右边
{
insert(tr[u].r, k);
if (tr[tr[u].r].val > tr[u].val) zag(u); //平衡旋转
}
}
}
pushup(u);//更新节点信息
}
int get_rank(int u, int k) //求k的位置
{
if (u == 0) return 0;//是0随便返回就行
if (tr[u].k == k) return tr[tr[u].l].size + 1;//相等了那排名应该就是左边的数量加上自己
if (tr[u].k > k) return get_rank(tr[u].l, k);//大了找左边
return tr[tr[u].l].size + tr[u].cnt + get_rank(tr[u].r, k);//找右边
}
int get_key(int u, int rank) //求该位置的值
{
if (u == 0) return INF;
if (tr[tr[u].l].size >= rank) return get_key(tr[u].l, rank);//找左边
if (tr[tr[u].l].size + tr[u].cnt >= rank) return tr[u].k;//如果满足条件就直接return
return get_key(tr[u].r, rank - tr[tr[u].l].size - tr[u].cnt);//不然就找右边
}
int get_pr(int u, int k)//前驱,小于k的最大的数
{
if (u == 0) return -INF;
if (tr[u].k >= k) return get_pr(tr[u].l, k);//找左边
return max(get_pr(tr[u].r, k), tr[u].k);//可能是右边可能是这个数,所以用个max
}
int get_ne(int u, int k)//后继,大于k的最小的数
{
if (u == 0) return INF;//后继的写法和前驱相反,大家可以注意一下
if (tr[u].k <= k) return get_ne(tr[u].r, k);
return min(get_ne(tr[u].l, k), tr[u].k);
}
/*
build();
*/
图论
链式前向星
int e[N], ne[N], h[N], w[N], cnt = 1;
void add(int x, int y, int z) { e[cnt] = y, w[cnt] = z, ne[cnt] = h[x], h[x] = cnt, cnt++; }
/*for (int i = 1; i <= n; i++)h[i] = -1;*/
朴素Dijkstra
int n, m;
int dis[505]; //dis[i]代表了i点到起点1的距离,初始化为无限大(0x3f3f3f3f)
int tu[505][505]; //邻接矩阵存图
bool ch[505]; //判断该点是否距离确定
void dijkstra(int s,int e)
{
memset(dis, 0x3f, sizeof dis); //距离初始化为正无穷大
dis[s] = 0;//起点到起点的距离为0,第一次t一定是起点
/*算法实现过程,n次循环每次找到未确定的离起点最短点贪心求解*/
for (int i = 1; i <= n; i++)
{
int t = -1; //t为该次循环所确定的离起点距离最短的点的编号
for (int j = 1; j <= n; j++)
{
if (!ch[j] && (t == -1 || dis[t] > dis[j])) //每次找到未确定最短点
t = j;
}
ch[t] = 1; //每次循环确定一个点离起点的最小距离
for (int j = 1; j <= n; j++) //判断j点是直接从起点来的距离短还是路过该次循环所确定的点到达这个点的距离短
{
dis[j] = min(dis[j], dis[t] + tu[t][j]);
}
}
if (dis[e] < 0x3f3f3f)
cout << dis[e] << endl;
else //如果目标点在算法中未被更新,则无法到达
cout << "-1" << endl;
}
/* memset(tu, 0x3f,sizeof tu); //邻接矩阵初始化 */
堆优化Dijkstra
int h[N],e[N],ne[N], v[N], idx;//邻接表(链式前向星)
int dis[N];
bool ch[N];
int n, m;
void add(int a, int b, int c)//存边的add函数
{
e[idx] = b;
ne[idx] = h[a];
v[idx] = c;
h[a] = idx++;
}
void dijkstra(int s,int end)
{
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;
/*PII中,first代表与起点的距离,second代表点的编号*/
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({ 0,s });//放入起点
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
if (ch[ver])continue;
ch[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])//邻接表的遍历
{
int j = e[i];
if (dis[j] >( distance + v[i]))
{
dis[j] = distance + v[i];
heap.push({ dis[j], j });
}
}
}
if (dis[end] >=0x3f3f3f3f)
cout << "-1" << endl;
else
cout << dis[end] << endl;
}
/* memset(h, -1, sizeof h);//邻接表初始化为-1 */
A*算法(K短路问题)
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
typedef pair<int, PII> PIII;
const int N = 1010, M = 200010;
int n, m, S, T, K;
int h[N], rh[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N];
bool st[N];
void add(int h[],int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
void dijkstra()
{
priority_queue<PII,vector<PII>,greater<PII>> heap;
heap.push({0,T});//终点
memset(dist, 0x3f, sizeof dist);
dist[T] = 0;
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y;
if(st[ver]) continue;
st[ver] = true;
for(int i=rh[ver];i!=-1;i=ne[i])
{
int j = e[i];
if(dist[j]>dist[ver]+w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j],j});
}
}
}
}
int astar()
{
priority_queue<PIII, vector<PIII>, greater<PIII>> heap;
// 谁的d[u]+f[u]更小 谁先出队列
heap.push({dist[S], {0, S}});
//{起点经过当前点到终点的估值,{起点到当前点的确定值但不一定最小,当前点编号}}
while(heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y.y,distance = t.y.x;
cnt[ver]++;
//如果终点已经被访问过k次了 则此时的ver就是终点T 返回答案
if(cnt[T]==K) return distance;
for(int i=h[ver];i!=-1;i=ne[i])
{
int j = e[i];
/*
如果走到一个中间点都cnt[j]>=K,则说明j已经出队k次了,且astar()并没有return distance,
说明从j出发找不到第k短路(让终点出队k次),
即继续让j入队的话依然无解,
那么就没必要让j继续入队了
*/
if(cnt[j] < K)
{
// 按 真实值+估计值 = d[j]+f[j] = dist[S->t] + w[t->j] + dist[j->T] 堆排
// 真实值 dist[S->t] = distance+w[i]
heap.push({distance+w[i]+dist[j],{distance+w[i],j}});
}
}
}
// 终点没有被访问k次
return -1;
}
int main()
{
cin >> m >> n;
memset(h,-1,sizeof h);
memset(rh,-1,sizeof rh);
for(int i=0;i<n;i++)
{
int a,b,c;
cin >> a >> b >> c;
add(h,a,b,c);
add(rh,b,a,c);
}
cin >> S >> T >> K;
// 起点==终点时 则d[S→S] = 0 这种情况就要舍去 ,总共第K大变为总共第K+1大
if (S == T) K ++ ;
// 从各点到终点的最短路距离 作为估计函数f[u]
dijkstra();
cout << astar();
return 0;
}
Bellman_Ford
int n, m, k;
int dis[N], te[N];
struct edge //边
{
int f, t, w; //定义边的起点,终点,长度
};
edge ed[N];
int Bellman_Ford(int s,int end)
{
mem(dis, 0x3f); //初始化距离为一个很大的数
dis[s] = 0; //起点到起点的距离为0
for (int i = 0; i < k; i++) //遍历步数
{
memcpy(te, dis, sizeof dis); //复制数组函数
for (int j = 1; j <= m; j++) //遍历每一条边
{
int f = ed[j].f, t = ed[j].t, w = ed[j].w;
dis[t] = min(dis[t], te[f] + w);
}
}
return dis[end]; //返回最后的答案
}
SPFA
int e[N], ne[N], w[N], h[N], id = 1; //链式前向星
int n, m;
int num[N]; //判环
int dis[N]; // dis[i]代表从起点到i的距离
bool ch[N]; //用来判断一个点是否在队列中
void add(int a, int b, int c) //加边函数
{
e[id] = b;
w[id] = c;
ne[id] = h[a];
h[a] = id;
id++;
}
int spfa(int s, int end)
{
mem(dis, 0x3f); //距离的初始化
dis[s] = 0; //起点到自己的距离为0
queue<int> q;
q.push(s); //先把起点放进去
ch[s] = true; //记录一下起点放进去了
while (!q.empty()) //循环条件:队列非空
{
int f = q.front(); //取出队首元素
q.pop(); //把队首踢出去
ch[f] = false; //记录一下队首已经被踢出去了
for (int i = h[f]; i != -1; i = ne[i]) //链式前向星遍历所有以f为起点的边
{
int j = e[i];
if (dis[j] > dis[f] + w[i]) //如果点j发生了更新
{
dis[j] = dis[f] + w[i]; //更新一下
num[j] = num[f] + 1;
if (num[j] >= n)
return -INF;
if (!ch[j]) //如果点j没在队列中
{
ch[j] = true; //把点j放进队列
q.push(j);
}
}
}
}
return dis[end]; //输出最后的结果
}
/*
mem(h, -1); //初始化链式前向星
*/
差分约束
1)求最大可行解
最大可行解:所有的可行解中某个未知数X的值最大
未知数X的最大可行解为:从超级源点到点X的最短路dis[x]
有向图建立方法:
将所有的不等式转化为X <= Y + c 的形式
建立有向边Y-->X,长度为c
注意一定是<=,如果是X < Y可以转化为X <= Y - 1
如果存在不等式形如c <= Y,转化为X0 <= Y - c,X0为超级源点。
2)求最小可行解
最小可行解:所有的可行解中某个未知数X的值最小
未知数X的最小可行解为:从超级源点到点X的最长路dis[x]
有向图建立方法:
将所有的不等式转化为X + c <= Y 的形式
建立有向边X-->Y,长度为c
注意一定是<=,如果是X < Y可以转化为X + 1 <= Y
如果存在不等式形如Y <= c,转化为Y - c <= X0,X0为超级源点。
LCA最近公共祖先(在线)
int e[N], ne[N], h[N], w[N], cnt = 1;
void add(int x, int y, int z)
{
e[cnt] = y, w[cnt] = z, ne[cnt] = h[x], h[x] = cnt, cnt++;
}
/*mem(h,-1)*/
int n, m, s;
//点数,询问数,结点数
int f[N][20], dep[N];
// f[i][j]代表i点上跳2^j后的结点编号
// dep[i]代表i号结点的深度
//树上两点距离,全转化为到根节点距离
int dis[N];
void dfs1(int x, int fa)
{
for (int i = h[x]; ~i; i = ne[i])
{
int j = e[i];
if (j != fa)
{
dis[j] = dis[x] + w[i];
dfs1(j, x);
}
}
}
//dist(x,y)=dis[x]+dis[y]-2*dis[L]
//dfs求f和dep数组
void dfs(int x, int fa)
{
dep[x] = dep[fa] + 1;
f[x][0] = fa;
//上跳到不能跳为止
for (int i = 1; (1 << i) <= dep[x]; i++)
f[x][i] = f[f[x][i - 1]][i - 1];
for (int i = h[x]; i; i = ne[i])
{
int j = e[i];
if (j != fa)
dfs(j, x);
}
}
int LCA(int x, int y)
{
if (dep[x] < dep[y])
swap(x, y);
//先让下方的点跳上来到同一高度
while (dep[x] > dep[y])
x = f[x][( int )log2(dep[x] - dep[y])];
if (x == y)
return x;
//两个点同时上跳,直到跳到父节点就是最近公共祖先
for (int i = log2(dep[x]); i >= 0; i--)
{
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
}
return f[x][0];
}
LCA最近公共祖先(离线Tarjan)
int n, m;
int e[N], ne[N], h[N], w[N], cnt = 1;
int dis[N];
int res[N];
int st[N];
vector<PII> query[N];
// query[i][first][second] first存查询距离i的另外一个点j,second存查询编号idx
void add(int x, int y, int z)
{
e[cnt] = y, w[cnt] = z, ne[cnt] = h[x], h[x] = cnt, cnt++;
}
int p[N];
void init()
{
for (int i = 0; i < N; i++)
p[i] = i;
}
int find(int x)
{
return x == p[x] ? x : p[x] = find(p[x]);
}
void join(int x, int y)
{
if (p[x] != p[y])
{
p[x] = find(p[y]);
}
}
void dfs(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa)
continue;
dis[j] = dis[u] + w[i];
dfs(j, u);
}
}
void tarjan(int u)
{
st[u] = 1;//当前路径结点标记为1
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!st[j])
{
tarjan(j);//往左下搜
join(j, u);//将左下的点全部合并到根结点
// p[j] = u;
}
}
//遍历所有和u相关的询问
for (auto item : query[u])
{
int y = item.first, id = item.second;
//如果另外一个询问点已经被遍历过了
//那另外一个结点的父结点就是最近公共祖先
if (st[y] == 2)
{
int anc = find(y);
//更新答案
res[id] = dis[u] + dis[y] - dis[anc] * 2;
}
}
//标记该节点以及子树已经被完全遍历过
st[u] = 2;
}
/*
cin >> n >> m;
mem(h, -1);
for (int i = 0; i < n - 1; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
if (a != b)
{
query[a].push_back({ b, i });
query[b].push_back({ a, i });
}
}
init();
dfs(1, -1);
tarjan(1);
for (int i = 1; i <= m; i++)
cout << res[i] << endl;
*/
次小生成树
typedef long long LL;
const int N = 100010, M = 300010, INF = 0x3f3f3f3f;
int n, m;
struct Edge
{
int a, b, w;
bool used;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}edge[M];
int p[N];
int h[N], e[M], w[M], ne[M], idx;
int depth[N], fa[N][17], d1[N][17], d2[N][17];//log2(1e5)=16 d1最大边 d2次大边
void add(int a,int b,int c)
{
e[idx] = b,ne[idx] = h[a],w[idx] = c,h[a] = idx++;
}
int find(int x)
{
if(x!=p[x])p[x]= find(p[x]);
return p[x];
}
LL kruskal()
{
for (int i = 1; i <= n; i ++ ) p[i] = i;
sort(edge, edge + m);
LL res = 0;
for (int i = 0; i < m; i ++ )
{
int a = find(edge[i].a), b = find(edge[i].b), w = edge[i].w;
if (a != b)
{
p[a] = b;
res += w;
edge[i].used = true;
}
}
return res;
}
void build()
{
memset(h,-1,sizeof h);
for(int i = 0;i<m;i++)
{
if(edge[i].used)
{
int a = edge[i].a,b = edge[i].b,w = edge[i].w;
add(a,b,w),add(b,a,w);
}
}
}
void bfs()
{
memset(depth,0x3f,sizeof depth);
depth[0] = 0,depth[1] = 1;//哨兵0 根节点1
queue<int> q;
q.push(1);
while(q.size())
{
int t = q.front();
q.pop();//日常漏
for(int i = h[t];~i;i=ne[i])
{
int j = e[i];
// j没有被遍历过
if(depth[j]>depth[t]+1)
{
depth[j] = depth[t]+1;
q.push(j);
fa[j][0] = t;
d1[j][0] = w[i],d2[j][0] = -INF;
for(int k = 1;k<=16;k++)
{
/*
→ →
o---o---o
j anc
d1[i,k-1],d2[i,k-1] d1[anc,k-1],d2[anc,k-1]
*/
int anc = fa[j][k - 1];
fa[j][k] = fa[anc][k - 1];
int distance[4] = {d1[j][k - 1], d2[j][k - 1], d1[anc][k - 1], d2[anc][k - 1]};
//初始化d1[j][k]和d2[j][k]
d1[j][k] = d2[j][k] = -INF;
for (int u = 0; u < 4; u ++ )
{
int d = distance[u];
// 更新最大值d1和次大值d2
if (d > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = d;
// 严格次大值
else if (d != d1[j][k] && d > d2[j][k]) d2[j][k] = d;
}
}
}
}
}
}
// lca求出a, b之间的最大边权与次大边权
int lca(int a,int b,int w)
{
static int distance[N * 2];
int cnt = 0;
// a和b中取深度更深的作为a先跳
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k -- )
// 如果a 跳2^k后的深度比b深度大 则a继续跳
// 直到两者深度相同 depth[a] == depth[b]
if (depth[fa[a][k]] >= depth[b])
{
distance[cnt ++ ] = d1[a][k];
distance[cnt ++ ] = d2[a][k];
a = fa[a][k];
}
// 如果a和b深度相同 但此时不是同一个点 两个同时继续向上跳
if (a != b)
{
for (int k = 16; k >= 0; k -- )
if (fa[a][k] != fa[b][k])
{
distance[cnt ++ ] = d1[a][k];
distance[cnt ++ ] = d2[a][k];
distance[cnt ++ ] = d1[b][k];
distance[cnt ++ ] = d2[b][k];
a = fa[a][k], b = fa[b][k];
}
// 此时a和b到lca下同一层 所以还要各跳1步=跳2^0步
distance[cnt ++ ] = d1[a][0];
distance[cnt ++ ] = d1[b][0];
}
// 找a,b两点距离的最大值dist1和次大值dist2
int dist1 = -INF, dist2 = -INF;
for (int i = 0; i < cnt; i ++ )
{
int d = distance[i];
if (d > dist1) dist2 = dist1, dist1 = d;
else if (d != dist1 && d > dist2) dist2 = d;
}
// ⭐ dist1和dist2是a和b之间的最大边权和次大边权 所以可以用w替换而仍然保持生成树(包含所有节点)
// 因为加入w这条边 原来的树会形成环
// 删除环中边权最大的边(如果最大的边和加入的边相等,那么删去次大边)。
// 如果w>这条路的最大边 w替换dist1
if (w > dist1) return w - dist1;
// 否则w==dist1 w替换dist2
if (w > dist2) return w - dist2;
// 不加这个return INF也是可以的
// ⭐因为非树边w的值域是一定≥dist1 否则在之前kruskal求最小生成树的时候把w替换dist1连接a和b就得到一个更小的生成树了 矛盾
// 所以最坏情况是w==dist1
// return INF;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edge[i] = {a, b, c};
}
// kruskal建最小树(把用到的边标记)
LL sum = kruskal();
// 对标记的边建图
build();
bfs();
LL res = 1e18;
//从前往后枚举非树边
for (int i = 0; i < m; i ++ )
if (!edge[i].used)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
// lca(a,b,w) 返回用w替换w[i] 的差值 = w-w[i]
res = min(res, sum + lca(a, b, w));
}
printf("%lld\n", res);
return 0;
}
Folyd
int n, m, k;
int dp[1000][1000]; //动态规划数组,dp[i][j]代表从i到j的最短距离
void floyd() //Floyd算法实现函数
{
for (int k = 1; k <= n; k++) //枚举中间的点,即一定经过k点
for (int i = 1; i <= n; i++) //枚举起点
for (int j = 1; j <= n; j++) //枚举终点
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]); //动态规划状态转移方程
}
/*
for(int i=1;i<=n;i++) //初始化操作
for (int j = 1; j <= n; j++)
{
if (i == j)dp[i][j] = 0; //自己到自己的距离为0
else dp[i][j] = INF; //把每两个不同点的距离初始化为无限大
}
for (int i = 0; i < m; i++) //输入边的操作
{
int x, y, z;
cin >> x >> y >> z;
dp[x][y] = min(dp[x][y], z); //如果两点不止一条边,保留最短的边
}
floyd(); //实现算法
*/
Tarjan(强连通分量)
int n, m;
int h[N], e[N], ne[N], idx;
int dfn[N], low[N], timestamp;
int stk[N], tt;
bool in_stk[N];
int id[N], sz[N], scc_cnt;//查找新点id,id中点的数量,新点总数
int din[N], dout[N];
void init()
{
memset(h, -1, sizeof h);
for (int i = 0; i <= n * 10; i++)
{
in_stk[i] = false;
dfn[i] = low[i] = stk[i] = id[i] = sz[i] = din[i] = dout[i] = 0;
idx = timestamp = tt = scc_cnt = 0;
}
}
void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; }
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;
stk[++tt] = u, in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j])
{
low[u] = min(low[u], dfn[j]);
}
}
if (dfn[u] == low[u])
{
++scc_cnt;
int v;
do {
v = stk[tt--];
in_stk[v] = false;
id[v] = scc_cnt;
sz[scc_cnt]++;
} while (v != u);
}
}
/*
cin >> n >> m;
init();
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
add(x, y);
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i]) tarjan(i);
}
for (int i = 1; i <= n; i++)
{
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
int a = id[i], b = id[k];
if (a != b) dout[a]++, din[b]++;
}
}
*/
Kruskal
struct edge //定义边的结构体
{
int f, t, w; //起点,终点,长度
}e[N];
bool cmp(edge a, edge b) //重写排序函数
{
return a.w < b.w;
}
int p[N]; //并查集祖先数组
int n, m;
int ans = 0; //答案
int cnt = 0; //计数器,记录存进最小生成树的边的数量
int find(int x) //并查集查找函数
{
if (x != p[x]) return p[x] = find(p[x]);
else return x;
}
void init() //并查集初始化
{
for (int i = 0; i <= n; i++)
p[i] = i;
}
void kruskal() //算法实现函数
{
init();
for (int i = 0; i < m; i++) //遍历每一条边
{
int x = e[i].f, y = e[i].t, z = e[i].w;
if (find(x) != find(y)) //不连通就连通
{
p[find(x)] = find(y);
ans += z;
cnt++;
}
}
}
/*
cin >> n >> m;
for (int i = 0; i < m; i++) //边的输入
{
int a, b, c;
cin >> a >> b >> c;
e[i].f = a, e[i].t = b, e[i].w = c;
}
sort(e, e + m, cmp); //排序函数
kruskal();
if (cnt == n - 1) //如果生成树中边小于n-1就说明原图没有最小生成树
cout << ans << endl;
else
cout << "impossible" << endl;
*/
Prim
int n, m;
int G[550][550]; //邻接矩阵存图
int dis[550]; //距离数组
bool ch[550]; //标记
int prim()
{
int ans=0; //初始化答案
for (int j = 0; j < n; j++) //n次循环
{
int t = -1; //初始化
int mi = INF;
for (int i = 1; i <= n; i++)
if (!ch[i] && (t == -1 || dis[i] < mi)) t = i, mi = dis[i];
//每次要找到距离集合最短的点,当集合为空时随便选一个(即t=-1时)
if (j && dis[t] == INF) //如果最小值为无限大代表无答案
return INF;
if(j) //如果不是第一次就加上边
ans += dis[t];
ch[t] = 1; //改一下标记
for (int i = 1; i <= n; i++)//遍历所有与t连接的边
{
if (!ch[i]) //如果不在集合中就更新dis
dis[i] = min(dis[i], G[t][i]);
}
}
return ans; //返回答案
}
/*
mem(dis, 0x3f); //初始化距离
mem(G, 0x3f); //图的初始化
cin >> n >> m;
for (int i = 1; i <= n; i++)G[i][i] = 0; //到自己的距离为0
for (int i = 0; i < m; i++)
{
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)
cout << "impossible" << endl;
else
cout << t << endl;
*/
染色法
int n, m;
int h[M], e[M], ne[M]; //邻接表存图
int id = 1;
int color[N]; //每个点的颜色
void add(int a, int b) //加边函数
{
e[id] = b, ne[id] = h[a], h[a]=id++;
}
bool dfs(int u, int x) //把u染色成x
{
color[u] = x;
for (int i = h[u]; i != -1; i = ne[i]) //遍历与u相连的所有点
{
int j = e[i];
if (!color[j]) //如果这个点没被染色过就染色
{
if (!dfs(j, 3 - x))return false; //染色失败就返回false
}
if (color[j] == color[u])return false; //之前被染色并且和父节点颜色一样就染色失败
}
return true;
}
/*
mem(h, -1);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
for (int i = 1; i <= n; i++)
{
if (!color[i])
{
if (!dfs(i, 1))
{
cout << "No" << endl;
return;
}
}
}
cout << "Yes" << endl;
*/
匈牙利算法
int n1, n2, m;
int h[550], ne[N], e[N], id = 1;// 链式前向星存图
bool st[N];//防止重边的情况导致死循环,防止一个男生重复询问一个女生导致死循环
int match[N];//用来存i匹配的点的编号
/*
mem(h,-1);
*/
void add(int a, int b)
{
e[id] = b, ne[id] = h[a], h[a] = id++;
}
int find(int x) //找男生x能不能匹配到女生
{
for (int i = h[x]; i != -1; i = ne[i]) //遍历所有的边
{
int j = e[i];
if (!st[j]) //防止重边导致的死循环
{
st[j] = true;
if (!match[j] || find(match[j])) //如果这个女生还没有男朋友或者她现在的男朋友有备胎
{
match[j] = x; //那就让她男朋友换备胎,然后她成为x的女朋友
return true; //匹配成功
}
}
}
return false; //匹配失败
}
/*
int res = 0;
for (int i = 1; i <= n1; i++)
{
mem(st, false);
if (find(i))
res++;
}
*/
拓扑排序
int n, m;
int e[N], ne[N], h[N], id = 1; //链式前向星存图
int rd[N]; //存放每个点的入度
bool ch[N]; //用来判断该点有没有被放进序列中
bool haveans = true; //用来判断是否有拓扑序列
queue<int>q; //用来存放拓扑序列的队列
void add(int a, int b) //加边的函数
{
e[id] = b;
ne[id] = h[a];
h[a] = id++;
rd[b]++;
}
void topsort() //拓扑排序实现函数
{
bool flag = true; //flag为true就代表还能找到入度为0的点
while (flag)
{
flag = false; //先改为,找不到入度为0的点
for (int i = 1; i <= n; i++)
{
if (rd[i] == 0&&!ch[i])
{
ch[i] = true;
flag = true; //找到了入度为0的点就把flag改为true,继续循环
q.push(i); //把入度为0的点放进队列q
for (int j = h[i]; j != -1; j = ne[j]) //BFS遍历所有以i为起点的边
{
int k = e[j];
rd[k]--; //每一条边的入度减一,删除该边
}
}
}
}
}
/*
cin >> n >> m;
mem(h, -1);
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b);
}
topsort();
if (q.size() != n) //如果队列中元素数量不等于点的总数就代表图中有环,没有拓扑序列
cout << "-1" << endl;
else //否则输出拓扑序列
while (!q.empty())
{
cout << q.front();
q.pop();
if (!q.empty())
cout << ' ';
else
cout << endl;
}
*/
欧拉图
int n, m;
int type;//1为无向图,0为有向图
int head[N], nex[N], edge[N], ver[N], tot;
int ans[N];
bool used[N];
int cnt;
int din[N], dout[N];
void add(int x, int y)
{
ver[tot] = y;
nex[tot] = head[x];
head[x] = tot++;
}
void dfs(int x)
{
//特殊数据O(n)优化,固定i引用变量,这样head[x]也会变,达到删边的效果
for (int& i = head[x]; ~i;)
{
if (used[i])
{
i = nex[i]; //删边,先删再dfs,防止遍历到祖先节点时该边未被删去
continue;
}
used[i] = true;
if (type == 1)
used[i ^ 1] = true;
int t;
if (type == 1)
{ //无向图就是正反两条边
t = i / 2 + 1; //实际上只有一条,我们建图的时候无向图建了两条
if (i & 1) //奇数就是反向边,根据题意取负数
t = -t;
}
//有向图就是下一条边
else
t = i + 1;
int y = ver[i];
i = nex[i]; //删边
dfs(y);
ans[++cnt] = t;
}
}
signed main()
{
scanf("%d", &type);
scanf("%d%d", &n, &m);
memset(head, -1, sizeof head);
for (int i = 1; i <= m; ++i)
{
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
if (type == 1)
add(y, x);
din[y]++, dout[x]++;
}
//先判断不成立的情况
if (type == 1)
{
for (int i = 1; i <= n; ++i)
if ((din[i] + dout[i]) & 1)
{ /*求的是欧拉回路所以奇数度点要为0*/
puts("NO");
return 0;
}
}
else
{
for (int i = 1; i <= n; ++i)
{
if (din[i] != dout[i])
{
puts("NO");
return 0;
}
}
}
for (int i = 1; i <= n; ++i) //找到一个可行的起点,因为本题中的1~n中的点可能没有连边
if (head[i] != -1)
{
dfs(i);
break;
}
if (cnt < m)
{ //如果不全部连通,则不存在欧拉路
puts("NO");
return 0;
}
puts("YES");
for (int i = cnt; i; i--) //倒序输出
printf("%d ", ans[i]);
puts("");
return 0;
}
其它
F 0 = 0 , F 1 = 1 F_0=0,\,F_1=1 F0=0,F1=1
F n = F n − 1 + F n − 2 ( n > = 3 , n ∈ N ) F_n=F_{n-1}+F_{n-2} \quad (n>=3,n \in \mathbb{N}) Fn=Fn−1+Fn−2(n>=3,n∈N)
F n = 1 5 [ ( 1 + 5 2 ) n + ( 1 − 5 2 ) n ] F_n = \frac{1}{\sqrt5} \left[ \left(\frac{1+\sqrt 5}{2}\right)^n+\left(\frac{1-\sqrt 5}{2}\right)^n \right] Fn=51[(21+5)n+(21−5)n]
F 1 + F 2 + ⋯ + F n = F n + 2 − 1 F_1+F_2+\cdots+F_n=F_{n+2}-1 F1+F2+⋯+Fn=Fn+2−1
F 1 2 + F 2 2 + ⋯ + F n 2 = F n F n + 1 F_1^2+F_2^2+\cdots+F_n^2=F_{n}F_{n+1} F12+F22+⋯+Fn2=FnFn+1
F 1 + F 3 + F 5 + ⋯ + F 2 n − 1 = F 2 n F_1+F_3+F_5+\cdots+F_{2n-1}=F_{2n} F1+F3+F5+⋯+F2n−1=F2n
F 2 + F 3 + F 6 + ⋯ + F 2 n = F 2 n + 1 − 1 F_2+F_3+F_6+\cdots+F_{2n}=F_{2n+1}-1 F2+F3+F6+⋯+F2n=F2n+1−1
F n = F m F n − m + 1 + F m − 1 F n − m F_n=F_m F_{n-m+1}+F_{m-1} F_{n-m} Fn=FmFn−m+1+Fm−1Fn−m
F n − 1 F n + 1 = F n 2 + ( − 1 ) n F_{n-1} F_{n+1}=F_n^2+(-1)^n Fn−1Fn+1=Fn2+(−1)n 从第二项开始,每个奇数项的平方都比前后两项之积多一,每个偶数项的平方比前后两项之积少一.
gcd ( F n , F n − 1 ) = 1 \gcd(F_n,F_{n-1})=1 gcd(Fn,Fn−1)=1
gcd ( F n , F m ) = F g c d ( n , m ) \gcd(F_n,F_m)=F_{gcd(n,m)} gcd(Fn,Fm)=Fgcd(n,m)
n ∣ m ⇔ F n ∣ F m n \mid m \Leftrightarrow F_n \mid F_m n∣m⇔Fn∣Fm
F 2 n / F n = F n − 1 + F n + 1 F_{2n}/F_{n}=F_{n-1}+F_{n+1} F2n/Fn=Fn−1+Fn+1
斐波那契数列的第n+2项代表了集合{1,2,…n}中所有不包含相邻正整数的子集的个数
g++ test.cpp -o test
g++ std.cpp -o std
g++ makedata.cpp -o makedata
:loop
makedata
test
std
fc std.txt test.txt
if %errorlevel%==0 goto loop
pause
mt19937 rnd(time(0));
freopen("in.txt", "r", stdin);
freopen("test.txt", "w", stdout);
ll jc[N],fjc[N];
ll ksm(ll a, ll b)
{
ll res = 1, t = a;
while (b)
{
if (b & 1) res = res * t % mod;
t = t * t % mod;
b >>= 1;
}
return res;
}
ll C(int a, int b)
{
if (a < 0 || b < 0 || a < b) return 0;
if (b == 0 || a == b) return 1;
return jc[a] * fjc[a - b] % mod * fjc[b] % mod;
}
void init()
{
jc[0] = 1;
for (int i = 1; i < N; i++) jc[i] = jc[i - 1] * i % mod;
fjc[N - 1] = ksm(jc[N - 1], mod - 2);
for (int i = N - 2; i; i--) fjc[i] = fjc[i + 1] * (i + 1) % mod;
}//C
namespace IO
{
const int MAXSIZE = 1 << 20;
char buf[MAXSIZE], *p1, *p2;
#define gc() \
(p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
int x = 0;
int f = 1;
char c = gc();
while (!isdigit(c))
{
if (c == '-') f = -1;
else if(c==EOF)return 0;
c = gc();
}
while (isdigit(c)) x = x * 10 + (c ^ 48), c = gc();
return x*f;
}
char pbuf[1 << 20], *pp = pbuf;
inline void push(const char& c)
{
if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;
*pp++ = c;
}
template<typename T>
inline void write(T x)
{
static int sta[35];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) push(sta[--top] + '0');
}
}
int st[25][N];
void init(int nn)
{
for (int j = 0; (1 << j) <= nn; j++)
{
for (int i = 1; i + (1 << j) - 1 <= nn; i++)
if (j == 0)
st[j][i] = a[i];
else
st[j][i] = max(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
}
}
int fd(int l, int r)
{
int len = r - l + 1;
int k = log(len) / log(2);
return max(st[k][l], st[k][r + 1 - (1 << k)]);
}
bool isp(int x)
{
if (x <= 3)return x == 2 || x == 3;
if (x % 2 == 0 || x % 3 == 0)return false;
for (int i = 6; (i - 1) <= x / (i - 1); i += 6)
{
if (x % (i - 1) == 0 || x % (i + 1) == 0)return false;
}
return true;
}
template <class T>
struct trarr
{
T a[N];
int n;
inline int lowbit(int x){return x&-x;}
void init(int nn){n = nn;for (int i = 1; i <= n; i++) a[i] = 0;}
inline T gt(int x)
{T res = 0;for (; x; x -= lowbit(x)) res += a[x];return res;}
inline T gt(int l, int r){return gt(r) - gt(l-1);}
inline void add(int x, T v){for (; x <= n; x += lowbit(x)) a[x] += v;}
void add(int l, int r, T v){add(l,v),add(r+1,-v);}
};
trarr<int> tr;
int p[N], cntp;
bool isp[N];
void init(int mx)
{
memset(isp, true, sizeof(bool) * (mx + 1));
isp[0] = isp[1] = false;
for (int i = 2; i <= mx; i++)
{
if (isp[i])
{
p[++cntp] = i;
}
int top = mx / i;
for (int j = 1; p[j] <= top; j++)
{
isp[i * p[j]] = false;
if (i % p[j] == 0) break;
}
}
}
const int N = 1e6 + 10;
int mod;
ll jc[N],fjc[N];
ll ksm(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll C(int a, int b)
{
if (a < 0 || b < 0 || a < b) return 0;
if (b == 0 || a == b) return 1;
return jc[a] * fjc[a - b] % mod * fjc[b] % mod;
}
void init() //上界为mod
{
jc[0] = 1;
for (int i = 1; i < mod; i++)
jc[i] = jc[i - 1] * i % mod;
fjc[mod - 1] = ksm(jc[mod - 1], mod - 2);
for (int i = mod - 2; i; i--)
fjc[i] = fjc[i + 1] * (i + 1) % mod;
}
int LC(ll a , ll b)
{
if(max(a,b) < mod) return C(a,b);
return C(a%mod,b%mod) * LC(a/mod , b/mod) % mod;
}
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ll long long
#define fi first
#define se second
int n;
pii a[107];
double ans;
inline double R(double l, double r)
{
return double(rand())/RAND_MAX*(r-l)+l;
}
double d(double x, double y)
{
double res = 0;
for(int i = 1 ;i<=n;i++)
{
res+=sqrt((x-a[i].fi)*(x-a[i].fi)+(y-a[i].se)*(y-a[i].se));
}
ans = min(ans,res);
return res;
}
void fix()
{
double stx = R(0,10000) , sty = R(0,10000);
double T0 = 10000;
while(T0>0.01)
{
double tx = R(stx-T0, stx+T0);
double ty = R(sty-T0, sty+T0);
double dt = d(tx,ty) - d(stx,sty);
if(exp(-dt/T0) > R(0,1))stx = tx,sty = ty;
T0 *= 0.99;
}
}
int main()
{
srand(time(0));
cin >> n;
for(int i = 1 ; i <= n ; i++) cin >> a[i].fi >> a[i].se;
ans = 1e19;
for(int i = 1 ; i <= 100 ; i++)
{
fix();
}
cout << (long long)(ans+0.5) << endl;
}
作者:Avalon·Demerzel
更多内容请见作者专栏:图论与数据结构