Tricks / 性质
Trick
曼哈顿\to 切比雪夫
区间\mathrm{LCA} 深度
判断序列一个区间内某些数出现次数是否是k 的倍数
哈希思想,先给这个数列中每一个数设一个非0的随机权值,数
这个
"伪素数":
对于一个数列,如果其中的数值域很大,要将这些数分解不一定要用分解质因数,可以使用伪素数
伪素数集合
考虑增量构造
如果
最后,如果
插入后,要将
这样维护
简要代码:
void Ins(int &a,int &b)
{
int d=gcd(a,b);
a/=d,b/=d;
++psz;
P[psz]=b;
if (d==1) return;
Ins(P[psz],d),Ins(a,d);
}
void Insert(int x)
{
for (int i=1;i<=psz;i++)
{
int d=gcd(P[i],x);
if (d==1) continue;
P[i]/=d,x/=d;
Ins(P[i],d);
}
if (x>1) P[++psz]=x;
int id=0;
for (int i=1;i<=psz;i++)
{
if (P[i]>1)
{
P[++id]=P[i];
}
}
psz=id;
sort(P+1,P+psz+1);
psz=unique(P+1,P+psz+1)-P-1;
}
\mathrm {Kruskal} 重构树
在
这样建出来的树就是
我们记
此时有性质:
原图中两点之间的
初始化时插入
板子:
namespace ODT
{
struct Node
{
int l,r;
mutable int v;
Node(int _l,int _r,int _v) { l=_l,r=_r,v=_v; }
bool operator < (const Node &o) const { return l<o.l; }
};
struct odt
{
set<Node> st;
typedef set<Node>::iterator itr;
itr split(int x)
{
if (x>n) return st.end();
itr it=--st.upper_bound({x,0,0});
int l=it->l,r=it->r,v=it->v;
if (l==x) return it;
st.erase(it); st.insert({l,x-1,v});
return st.insert({x,r,v}).first;
}
void assign(int l,int r,int v)
{
itr rit=split(r+1),lit=split(l); st.erase(lit,rit);
itr p=st.insert({l,r,v}).first; lit=rit=p;
while (lit->v==p->v) lit--; lit++;
while (rit->v==p->v) rit++; rit--;
l=lit->l,r=rit->r; st.erase(lit,++rit); st.insert({l,r,v});
}
PII bel(int x)
{
itr it=--st.upper_bound({x,0,0});
return {it->l,it->r};
}
int ask(int x)
{
itr it=--st.upper_bound({x,0,0});
return it->v;
}
};
} using namespace ODT;
极小\mathrm{mex} 区间
记
结论:极小的
可以
记
考虑枚举
然后在
其中在线求区间
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=100010,NLOGN=N*17;
int n;
int a[N];
vector<int> pos[N];
int getL(int l,int x)
{
int p=lower_bound(pos[x].begin(),pos[x].end(),l)-pos[x].begin();
if (!p) return 0;
return pos[x][p-1];
}
int getR(int r,int x)
{
int p=upper_bound(pos[x].begin(),pos[x].end(),r)-pos[x].begin();
if (p==pos[x].size()) return n+1;
return pos[x][p];
}
namespace Tr
{
struct Node
{
int lc,rc;
int mi;
} tr[N*4+NLOGN];
int rt[N],idx;
int build(int l,int r)
{
int u=++idx;
tr[u].mi=0;
if (l==r) return u;
int mid=(l+r)>>1;
tr[u].lc=build(l,mid);
tr[u].rc=build(mid+1,r);
return u;
}
void pushup(int u)
{
tr[u].mi=1e9;
if (tr[u].lc) tr[u].mi=min(tr[u].mi,tr[tr[u].lc].mi);
if (tr[u].rc) tr[u].mi=min(tr[u].mi,tr[tr[u].rc].mi);
}
int modify(int p,int l,int r,int x,int v)
{
int q=++idx;
tr[q]=tr[p];
if (l==r)
{
tr[q].mi=v;
return q;
}
int mid=(l+r)>>1;
if (x<=mid) tr[q].lc=modify(tr[p].lc,l,mid,x,v);
else tr[q].rc=modify(tr[p].rc,mid+1,r,x,v);
pushup(q);
return q;
}
int query(int u,int l,int r,int x)
{
if (l==r) return l;
int mid=(l+r)>>1;
if (tr[tr[u].lc].mi>=x) return query(tr[u].rc,mid+1,r,x);
else return query(tr[u].lc,l,mid,x);
}
int Mex(int l,int r) { return query(rt[r],0,n,l); }
}
using namespace Tr;
struct Range
{
int l,r;
bool operator < (const Range& o) const
{
if (l!=o.l) return l>o.l;
return r<o.r;
}
};
vector<Range> mex[N];
int main()
{
cin >> n;
for (int i=1;i<=n;i++) cin >> a[i];
for (int i=1;i<=n;i++) pos[a[i]].push_back(i);
rt[0]=build(0,n);
for (int i=1;i<=n;i++) rt[i]=modify(rt[i-1],0,n,a[i],i);
for (int i=1;i<=n;i++)
{
if (a[i]) mex[0].push_back({i,i});
else mex[1].push_back({i,i});
}
for (int x=1;x<=n;x++)
{
for (Range rg : mex[x-1])
{
int l=rg.l,r=rg.r;
int L=getL(l,x-1),R=getR(r,x-1);
if (L>0) mex[Mex(L,r)].push_back({L,r});
if (R<=n) mex[Mex(l,R)].push_back({l,R});
}
sort(mex[x].begin(),mex[x].end());
vector<Range> t;
int nwr=1e9;
for (Range rg : mex[x])
{
int l=rg.l,r=rg.r;
if (r>=nwr) continue;
t.push_back(rg);
nwr=r;
}
mex[x]=t;
}
return 0;
}
集合哈希
对于需要维护一个集合是否存在等问题时,考虑使用集合哈希。对于集合值域的每个数mt19937_64来生成为
然后就可以方便地进行集合加入、删除元素,同时维护整个集合
解递推式
对于形如
考虑设常数
于是
易得
二项式反演
通常用于解决 exactly
我们尝试用
运用二项式反演,有
list 链表合并
链表合并是
void join(list<int> &x,list<int> y) { x.splice(x.end(),y); }
性质
- 一个字符串存在
k -周期等价于S[1,n-k]=S[k+1,n] - 固定
l ,则\gcd_{l\le i\le r}a_i 相同的极大r 连续段只有O(\log V) 个 - 若
a>b ,a\bmod b<\frac{a}{2} -
V$个点,$E$条边的森林连通块个数是$V-E -
a<b<c\Rightarrow \min(a\oplus b,b\oplus c)<a\oplus c - 直径具有合并性质
- 以重心为根,其每个子节点子树大小
\le \frac{n}{2} - 图总度数是
O(m) 的,因此可以O(\sqrt{m})-O(\sqrt{m}) 根号分治。 - 每个点被扩展次数
\le 这个点的最短路 - 射线法:点在多边形内部
\Leftrightarrow 从这个点引出一条射线,与多边形有奇数个交点。 -
\begin{aligned} & \max_{S\subseteq V}\set{d_u\oplus d_v\oplus S}\\ &= \left(\min_{S_1\subseteq V}\set{d_u\oplus S_1}\right)\oplus \left(\max_{S_2\subseteq V}\set{d_v\oplus S_2}\right) \\ \end{aligned} -
对于一个边集
S ,其将树划分为k 个连通块\set{a_1,a_2,\dots,a_k} ,则满足条件的生成树个数为:f(S)=n^{k-2}\prod_{i=1}^k a_i - 有
k 个叶子的树可以用\left\lceil\frac{k}{2}\right\rceil 条路径覆盖。 -
x^n=\sum_{k}{n\brace k}x^{\underline{k}}