前言
这里记录&总结一些平常 O I OI OI 竞赛(我还没有AFO的时候)中并不是很常用的毒瘤玩意。
黑科技
黑科技数据结构
1. 李超线段树
一种线段树维护某一类信息的方法。
支持区间加入一次函数,询问区间的一次函数最值。
修改复杂度为 O ( l o g 2 n ) O(log^2n) O(log2n) , 如果是全局加入就是 O ( l o g n ) O(log\ n) O(log n)
查询复杂度为 O ( l o g n ) O(log\ n) O(log n)
维护的信息:
flag[u] 当前节点是否插入了一次函数
K[u],B[u] 表示当前节点区域中最优的一次函数
Min/Max[u] 区间的最小值或最大值,一般是在询问的是区间的时候才有必要维护
李超线段树维护信息的方法就是线段树的标记永久化 , 似乎不能支持删除。
对于一个区间修改 , 如果当前区间没有加入过直线 , 那么直接加入。
如果已有直线,比较两条直线,如果一条完全优于另外一条 , 那么替换或者是直接返回。
如果各有优劣,则看哪一条在当前区间内优势的部分较多,保留多的那一个,劣的继续往下递归。
由于一次函数的单调性 , 如果我们保留长的在当前区间 , 那么往下递归插入的一次函数只用插向一边,因为只会存在一个优劣转换的点,这样长度每次减半,复杂度就是 O ( l o g n ) O(log\ n) O(log n)
如果是区间插入 , 那么要先定位到 O ( l o g n ) O(log\ n) O(log n) 个区间,复杂度就是 O ( l o g 2 n ) O(log^2n) O(log2n) 了。
对于比较那个点的优势区间更长,可以采取算交点的方式,但我一般采用比较中点函数值的大小来推算,稍加讨论就行了。
T i p s : Tips: Tips:
如果插入的一次函数并不是平常的连续一次函数 , 而是有实际意义的 , 那么一定要搞清楚到底 x x x 是什么 , 选好合适的 x x x 的含义来简化计算!
区间插入的模板:
void Modify(int u,int l,int r,int L,int R,ll k,ll b){
int mid=l+r>>1;
if(l>=L&&r<=R){
if(!T[u].flag) {
T[u].Fill(k,b,l,r);return;}
ll yln=F(k,b,l),yrn=F(k,b,r),ylo=T[u].F(l),yro=T[u].F(r);
if(yln>=ylo&&yrn>=yro) return;
if(yln<=ylo&&yrn<=yro) return T[u].Fill(k,b,l,r);
else {
ll ymn=F(k,b,mid),ymo=T[u].F(mid);
if(ymn>=ymo) {
if(yln>=ylo) Modify(rs,mid+1,r,L,R,k,b);
else Modify(ls,l,mid,L,R,k,b);
}
else {
if(yln>=ylo) Modify(ls,l,mid,L,R,T[u].k,T[u].b),T[u].Fill(k,b,l,r);
else Modify(rs,mid+1,r,L,R,T[u].k,T[u].b),T[u].Fill(k,b,l,r);
}
T[u].Min=min(T[u].Min,min(T[ls].Min,T[rs].Min));
return;
}
if(l==r) return;
}
if(mid>=L) Modify(ls,l,mid,L,R,k,b);
if(mid< R) Modify(rs,mid+1,r,L,R,k,b);
T[u].Min=min(T[u].Min,min(T[ls].Min,T[rs].Min));
return;
}
2.后缀平衡树
用平衡树来动态维护 SA 数组,支持动态往前加入字符。
当前面新加入一个字符的时候,容易发现这只是一个普通的插入过程,我们只需要在平衡树上走并比较就行了。
所以我们只需要找到一种能够支持快速比较的方法。第一位直接比较,如果不同直接得出大小关系,相同时我们发现就是比较下一位的两个后缀。也就是说我们需要快速得到之前平衡树里另外节点的前后顺序关系。如果继续在平衡树中进行查找,那么时间复杂度多了一个 l o g log log,不优秀。
解决方法是采用对于每一个节点赋权值的方式来完成快速比较。
一开始权值区间设为 (0,1) 根节点为 0.5,然后左右的范围分别变成 (0,0.5) , (0.5,1),节点权值取中点,以此类推。
这样的话写起来非常方便,但我们要求树高不能太高,所以我们使用替罪羊树来维护是再好不过的选择了。
板子题 BZOJ Phorni
黑科技算法
1.斯坦那树
其实只是一种解决一类状压dp的方法。
当我们需要进行有关联通性的状压 dp 时 , 只要求关键点连通且关键点较少 , 但是非关键点比较多的时候 , 可以用到斯坦那树。
其实是一类不要求所有点连通而只要求一部分点连通的 M S T MST MST , 只能状压来求。
状压关键点的连通性 , 并确定一个点已经在树中 , 那么在不影响关键点的状态 , 也就是相同状态中可以用最短路来转移 , 而不同状态一般固定一个点 , 枚举子集然后合并。
通过最短路巧妙解决了非关键之间连边的决策。
2.朱刘算法
最小树形图:
求解步骤:
首先要明确的一点是每一个点只会有一条入边。
- 为所有点找到一个最小的入边。
- 把这些边的权值加入答案 , 然后判断图中是否存在环。
- 不存在环那么算法结束 , 否则缩环(直接往入边方向跳即可,不要tarjan) , 并把所有不在环内边的边的权值减去指向点的最小入边(之前的环不合法 , 其中要去掉一条边 , 这里减去原入边边权就是在之后选择新的边的时候考虑了这个权值的变化)
代码:
#include<bits/stdc++.h>
using namespace std;
template<class T>inline void init(T&x){
x=0;char ch=getchar();bool t=0;
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
if(t) x=-x;return;
}
const int M=1e4+10;
const int N=101;
namespace Directed_MST{
struct edge{
int u,v,w;edge(){
u=v=w=0;}edge(int a,int b,int c){
u=a,v=b,w=c;}}a[M],b[M];
int cnt=0;
int ans=0;
int In[N],bel[N],vis[N];
int n,m,r,bcc=0;
inline int MST(){
for(int i=1;i<=n;++i) In[i]=bel[i]=vis[i]=0;bcc=0;
for(int i=1;i<=m;++i) {
// find minum_weight in_edge
int u=a[i].u,v=a[i].v,w=a[i].w;
if((!In[v])||a[In[v]].w>w) In[v]=i;
}
for(int u=1;u<=n;++u) {
// judge circle and shrink them , sum the cost
if(!In[u]&&u!=r) return -1;
if(u!=r) ans+=a[In[u]].w;
if(vis[u]) continue;
int v=u;
for(;v^r&&vis[v]^u&&!bel[v];v=a[In[v]].u) vis[v]=u;// root needn't go back
if(v^r&&!bel[v]) {
// is a circle
int s=v;bel[s]=++bcc;
for(s=a[In[s]].u;v^s;s=a[In[s]].u) bel[s]=bcc;
}
}if(!bcc) return 0;
for(int i=1;i<=n;++i) if(!bel[i]) bel[i]=++bcc;// other node
cnt=0;
for(int i=1;i<=m;++i){
// shrink circle , create new edge
int u=a[i].u,v=a[i].v,w=a[i].w;
if(bel[u]==bel[v]) continue;
int goi=a[In[v]].w;
b[++cnt]=edge(bel[u],bel[v],w-goi);
}
for(int i=1;i<=cnt;++i) a[i]=b[i];m=cnt,cnt=0;
n=bcc;return 1;
}
void work(){
init(n),init(m);init(r);
int u,v,w;
for(int i=1;i<=m;++i) {
init(u),init(v),init(w);if(u==v) --i,--m;else a[i]=edge(u,v,w);}
int ret;
while((ret=MST())==1) r=bel[r];
if(~ret) printf("%d\n",ans);
else puts("-1");
}