本博客是该模板的第二部分
注:由于编者太弱,以下知识可能仅仅只是给自己写的,对dalao没有任何参考价值,请谅解。
众所周知我们可以打标记维护信息。
1.当操作较多时,打标记就非常恶心,例如
考虑用形如
[ A B . . . s i z ] \begin{bmatrix} A\\ B\\ ...\\ siz\\ \end{bmatrix} ⎣⎢⎢⎡AB...siz⎦⎥⎥⎤
的矩阵维护,其中 A A A, B B B,…均为标记。
将转移写成一个 k × k k×k k×k的矩阵,可以使用矩阵乘法快速维护。
例题:「THUSCH 2017」大魔法师
2.当区间数值过大或者是浮点数时,考虑使用离散化。
使用sort
,unique
,lower_bound
等函数几步搞定。
例题:光线追踪
3.当需要维护多颗线段树时,将线段树打成struct
,特别是线段树之间信息对传的情况,一定要冷静调试…
例题:[YL2019 day2]two
没怎么深入学过…
只会板题qwq
#include
#include
#include
#include
using namespace std;
#define MAXN 200000
int n,Q;
int a[MAXN+5],b[MAXN+5];
int root[MAXN+5];
int rnum;
struct node
{
int lc,rc;
int sum;
}tree[MAXN*20+5];
void Build(int &x,int l,int r)
{
x=++rnum;
if(l==r)return ;
int mid=(l+r)>>1;
Build(tree[x].lc,l,mid);
Build(tree[x].rc,mid+1,r);
}
int Insert(int x,int l,int r,int pos)
{
int Nr=++rnum;
tree[Nr]=tree[x];
tree[Nr].sum=tree[x].sum+1;
if(l==r)return Nr;
int mid=(l+r)>>1;
if(pos<=mid)tree[Nr].lc=Insert(tree[Nr].lc,l,mid,pos);
else tree[Nr].rc=Insert(tree[Nr].rc,mid+1,r,pos);
return Nr;
}
int Query(int Lr,int Rr,int l,int r,int val)
{
int Pv=tree[tree[Rr].lc].sum-tree[tree[Lr].lc].sum;
if(l==r)return l;
int mid=(l+r)>>1;
if(val<=Pv)return Query(tree[Lr].lc,tree[Rr].lc,l,mid,val);
else return Query(tree[Lr].rc,tree[Rr].rc,mid+1,r,val-Pv);
}
int main()
{
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++)
{scanf("%d",&a[i]);b[i]=a[i];}
sort(b+1,b+n+1);
int p=unique(b+1,b+n+1)-b-1;
Build(root[0],1,p);
for(int i=1;i<=n;i++)
{
int P=lower_bound(b+1,b+p+1,a[i])-b;
root[i]=Insert(root[i-1],1,p,P);
}
while(Q--)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",b[Query(root[l-1],root[r],1,p,k)]);
}
}
顾名思义,就是将两个线段树合并起来。
常用于一些比较麻烦的子树问题。
注意空间通常要开到50倍。
模板:[HNOI2012]永无乡
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 100000
int rt[MAXN+5],fa[MAXN+5],posd[MAXN+5];
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 ask(int x){return (fa[x]!=x)?fa[x]=ask(fa[x]):x;}
struct Seg_tree
{
int cnt;
struct node
{
int ch[2],sum;
}p[MAXN*50+5];
Seg_tree(){cnt=0;}
int Insert(int l,int r,int pos)
{
int id=++cnt;
p[id].sum=1;
if(l==r)return id;
int mid=(l+r)>>1;
if(pos<=mid)p[id].ch[0]=Insert(l,mid,pos);
else p[id].ch[1]=Insert(mid+1,r,pos);
return id;
}
int merge(int c1,int c2,int l,int r)
{
//printf("%d %d->%d %d\n",l,r,c1,c2);
if(!c1&&!c2)return 0;
if(!c1)return c2;
if(!c2)return c1;
int id=++cnt,mid=(l+r)>>1;
p[id].sum=p[c1].sum+p[c2].sum;
p[id].ch[0]=merge(p[c1].ch[0],p[c2].ch[0],l,mid);
p[id].ch[1]=merge(p[c1].ch[1],p[c2].ch[1],mid+1,r);
return id;
}
int Query(int x,int l,int r,int k)
{
//printf("%d %d %d\n",x,p[x].sum,k);
if(p[x].sum<k)return -1;
if(l==r)return posd[l];
int mid=(l+r)>>1;
int lc=p[x].ch[0],rc=p[x].ch[1];
if(k<=p[lc].sum)return Query(lc,l,mid,k);
else return Query(rc,mid+1,r,k-p[lc].sum);
}
}Tr;
int n,m,Q;
void add(int u,int v)
{
int x=ask(u),y=ask(v);
if(x!=y)
{
fa[x]=y;
rt[y]=Tr.merge(rt[x],rt[y],1,n);
//printf("%d %d %d\n",x,y,rt[y]);
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
{
int rnk=read();
fa[i]=i;
rt[i]=Tr.Insert(1,n,rnk);posd[rnk]=i;
}
for(int i=1;i<=m;i++)add(read(),read());
scanf("%d",&Q);
while(Q--)
{
char c[1];
scanf("%s",c);
int x=read(),k=read();
if(c[0]=='B')add(x,k);
else {printf("%d\n",Tr.Query(rt[ask(x)],1,n,k));}
}
}
本身概念很简单,然而…
这代码长度…[令人感动]
模板:
#include
#include
#include
#include
using namespace std;
#define MAXN 100000
int n,m,rt,MOD,Q;
int val[MAXN+5],dep[MAXN+5],hvs[MAXN+5];
int siz[MAXN+5],id[MAXN+5],fa[MAXN+5],ct;
int top[MAXN+5],head[MAXN+5],nval[MAXN+5];
int tp,ecnt;
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;
}
void Init()
{ecnt=0;memset(head,-1,sizeof(head));}
struct edge{int to,nxt;}e[MAXN*2+5];
void add(int u,int v)
{
e[++ecnt]=(edge){v,head[u]};
head[u]=ecnt;
e[++ecnt]=(edge){u,head[v]};
head[v]=ecnt;
}
struct Seg_tree
{
struct node
{
int sum,tag;
}p[MAXN*4+5];
void Build(int x,int l,int r)
{
if(l==r){p[x].sum=nval[l]%MOD;return ;}
int mid=(l+r)>>1;
Build(x<<1,l,mid);
Build(x<<1|1,mid+1,r);
p[x].sum=(p[x<<1].sum+p[x<<1|1].sum)%MOD;
}
void push_down(int x,int l,int r)
{
if(p[x].tag)
{
int mid=(l+r)>>1;
(p[x<<1].sum+=1LL*(mid-l+1)*p[x].tag%MOD)%MOD;
(p[x<<1|1].sum+=1LL*(r-mid)*p[x].tag%MOD)%MOD;
(p[x<<1].tag+=p[x].tag)%MOD;
(p[x<<1|1].tag+=p[x].tag)%MOD;
p[x].tag=0;
}
}
void Insert(int x,int l,int r,int L,int R,int num)
{
if(r<L||R<l)return ;
if(L<=l&&r<=R)
{
p[x].sum+=1LL*(r-l+1)*num%MOD;
p[x].tag+=num;
return ;
}
push_down(x,l,r);
int mid=(l+r)>>1;
Insert(x<<1,l,mid,L,R,num);
Insert(x<<1|1,mid+1,r,L,R,num);
p[x].sum=(p[x<<1].sum+p[x<<1|1].sum)%MOD;
}
int Query(int x,int l,int r,int L,int R)
{
if(r<L||R<l)return 0;
if(L<=l&&r<=R)return p[x].sum;
push_down(x,l,r);
int mid=(l+r)>>1;
return (Query(x<<1,l,mid,L,R)+Query(x<<1|1,mid+1,r,L,R))%MOD;
}
}Tr;
void dfs1(int x)
{
int ms=0;
siz[x]=1;
for(int i=head[x];i!=-1;i=e[i].nxt)
{
int nxt=e[i].to;
if(nxt==fa[x])continue;
fa[nxt]=x;
dep[nxt]=dep[x]+1;
dfs1(nxt);
siz[x]+=siz[nxt];
if(ms<siz[nxt])hvs[x]=nxt,ms=siz[nxt];
}
}
void dfs2(int x,int ntop)
{
id[x]=++ct;
nval[ct]=val[x];
top[x]=ntop;
if(hvs[x])dfs2(hvs[x],ntop);
for(int i=head[x];i!=-1;i=e[i].nxt)
{
int nxt=e[i].to;
if(nxt==hvs[x]||nxt==fa[x])continue;
dfs2(nxt,nxt);
}
}
void modf_chain(int x,int y,int num)
{
num%=MOD;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
Tr.Insert(1,1,n,id[top[x]],id[x],num);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
Tr.Insert(1,1,n,id[x],id[y],num);
}
int Query_chain(int x,int y)
{
int res=0;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
res=(res+Tr.Query(1,1,n,id[top[x]],id[x]))%MOD;
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
res=(res+Tr.Query(1,1,n,id[x],id[y]))%MOD;
return res;
}
int main()
{
Init();
scanf("%d%d%d%d",&n,&Q,&rt,&MOD);
for(int i=1;i<=n;i++)val[i]=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read();
add(x,y);
}
dep[rt]=1;
dfs1(rt);
dfs2(rt,rt);
Tr.Build(1,1,n);
while(Q--)
{
scanf("%d",&tp);
int x=read(),y,z;
if(tp==1)y=read(),z=read(),modf_chain(x,y,z);
if(tp==2){y=read();printf("%d\n",Query_chain(x,y));}
if(tp==3)z=read(),Tr.Insert(1,1,n,id[x],id[x]+siz[x]-1,z);
if(tp==4)printf("%d\n",Tr.Query(1,1,n,id[x],id[x]+siz[x]-1));
}
}
神奇 O ( n n ) O(n\sqrt{n}) O(nn)算法。
需要注意的是,当询问和插入复杂度不均等时,我们可以调整块的大小以平衡复杂度,这种方法叫均值法。可以用方程求解。
块上暴力维护很多操作都十分方便,例如在上面维护有序数列,凸包,dp数组等,仅仅是复杂度的问题…
基于 O ( n n ) O(n\sqrt{n}) O(nn)分块的离线询问神器。
适用于任何离线的,区间 [ l , r ] [l,r] [l,r]与每次改变一个单位长度的区间有联系(或较低复杂度转移关系)的一切问题。
例题:[BZOJ3809]Gty的二逼妹子序列 分块+莫队
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 100000
#define MAXM 1000000
int read()
{
int x=0,F=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')F=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x*F;
}
int cnt[MAXN+5],bnum[MAXN+5];
int K[MAXN+5],to[MAXN+5],ans[MAXM+5];
struct Query
{
int l,r,a,b,id;
}Q[MAXM+5];
int n,m,Cl;
bool cmp(Query s1,Query s2)
{
if(to[s1.l]==to[s2.l])return s1.r<s2.r;
else return to[s1.l]<to[s2.l];
}
void Insert(int x)
{
cnt[x]++;
if(cnt[x]==1)bnum[to[x]]++;
}
void popout(int x)
{
cnt[x]--;
if(cnt[x]==0)bnum[to[x]]--;
}
int query(int x,int y)
{
int res=0;
int pl=to[x],pr=to[y];
if(pr-pl<=1){
for(int i=x;i<=y;i++)res+=(cnt[i]>0);
return res;
}
for(int i=x;i<=pl*Cl;i++)
res+=(cnt[i]>0);
for(int i=pl+1;i<pr;i++)
res+=bnum[i];
for(int i=(pr-1)*Cl+1;i<=y;i++)
res+=(cnt[i]>0);
return res;
}
int main()
{
n=read(),m=read();
Cl=floor(sqrt(n));
for(int i=1;i<=n;i++)
K[i]=read(),to[i]=(i-1)/Cl+1;
for(int i=1;i<=m;i++)
{
Q[i].l=read(),Q[i].r=read();
Q[i].a=read(),Q[i].b=read(),Q[i].id=i;
}
sort(Q+1,Q+m+1,cmp);
int L=1,R=1;
Insert(K[1]);
for(int i=1;i<=m;i++)
{
while(L<Q[i].l)popout(K[L++]);
while(L>Q[i].l)Insert(K[--L]);
while(R>Q[i].r)popout(K[R--]);
while(R<Q[i].r)Insert(K[++R]);
ans[Q[i].id]=query(Q[i].a,Q[i].b);
}
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
}
简单来说就是将权值较大的子树和他的父亲共用一个 d p / dp/ dp/统计数组。
与子树大小有关的用树上启发式合并。与深度有关的用长链剖分。
CF600E Lomsat gelral
先直接暴力统计以轻儿子为根的子树内的答案并清空,再统计以轻儿子为根的子树内的答案并保留,最后再次dfs一遍轻儿子并把答案加上,合并到父亲中。
复杂度玄学一般地就是: O ( n l o g n ) O(nlogn) O(nlogn)
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 200000
#define MAXC 200000
#define Fi first
#define Se second
#define LL long long
#define LD long double
#define PB push_back
int n;
int color[MAXN+5],dfn[MAXN+5],Ft[MAXN+5],u,v,vc[MAXN+5];
int Qr[MAXN+5],sz[MAXN+5];
LL A_cn;
LL ans[MAXN+5],MAX,num,cnt[MAXC+5];
vector<int> G[MAXN+5];
void dfs1(int x,int fa)
{
sz[x]=1;
dfn[x]=++num;
vc[num]=x;
int G_size=G[x].size();
for(int i=0;i<G_size;i++)
if(G[x][i]!=fa)
{
dfs1(G[x][i],x);
sz[x]+=sz[G[x][i]];
}
Ft[x]=++num;
}
void CG(int x,int del)
{
cnt[color[x]]+=del;
if(cnt[color[x]]>MAX)MAX=cnt[color[x]],A_cn=color[x];
else if(cnt[color[x]]==MAX)A_cn+=color[x];
}
void dfs2(int x,int fa,bool keep)
{
int m_size=-1,big_child=-1,G_size=G[x].size();
for(int i=0;i<G_size;i++)
if(G[x][i]!=fa&&sz[G[x][i]]>m_size){m_size=sz[G[x][i]];big_child=G[x][i];}
for(int i=0;i<G_size;i++)if(G[x][i]!=fa&&G[x][i]!=big_child)dfs2(G[x][i],x,0);
if(big_child!=-1)dfs2(big_child,x,1);
for(int i=0;i<G_size;i++)
{
int xn=G[x][i];
if(xn!=fa&&xn!=big_child)
for(int k=dfn[xn];k<Ft[xn];k++)
CG(vc[k],1);
}
CG(x,1);
//printf("Point:%d\n",x);
//for(int i=1;i<=4;i++)printf("color::%d cnt::%d\n",i,cnt[i]);
ans[x]=A_cn;
if(keep==0)
{
for(int k=dfn[x];k<Ft[x];k++)CG(vc[k],-1);
A_cn=MAX=0;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&color[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
G[u].PB(v);G[v].PB(u);
}
MAX=A_cn=0;
dfs1(1,0);
dfs2(1,0,1);
for(int i=1;i<=n;i++)printf("%lld ",ans[i]);
}
[POI2014]HOT-Hotels加强版
哲学指针优化…
f [ u ] [ i ] f[u][i] f[u][i]表示 u u u子树里距离 u u u为 i i i的节点个数
g [ u ] [ i ] g[u][i] g[u][i]表示 u u u子树里两个点 x , y x,y x,y到其 L C A LCA LCA的距离都是 d d d, L C A LCA LCA到 u u u的距离是 d − i d−i d−i的方案数
有
g [ u ] [ i ] = ∑ v g [ v ] [ j + 1 ] + f [ u ] [ i ] ∗ f [ v ] [ i − 1 ] g[u][i]=\sum_vg[v][j+1]+f[u][i]*f[v][i-1] g[u][i]=∑vg[v][j+1]+f[u][i]∗f[v][i−1]
f [ u ] [ i ] = ∑ v f [ v ] [ i − 1 ] f[u][i]=\sum_vf[v][i-1] f[u][i]=∑vf[v][i−1]
注意到当一个节点只有一个时:
g [ u ] [ i ] = g [ v ] [ j + 1 ] g[u][i]=g[v][j+1] g[u][i]=g[v][j+1]
f [ u ] [ i ] = f [ v ] [ i − 1 ] f[u][i]=f[v][i-1] f[u][i]=f[v][i−1]
可以用指针转移实现 O ( 1 ) O(1) O(1)。
每个节点选择深度最大的那一个点转移即可。
注意到每一个点都属于一个长链,而一条长链中的所有节点都可以 O ( 1 ) O(1) O(1)转移,只有在链顶才会 O ( 该 链 长 度 ) O(该链长度) O(该链长度)地暴力转移。
因此复杂度: O ( 2 ∑ 链 长 度 ) = O ( n ) O(2\sum 链长度)=O(n) O(2∑链长度)=O(n)
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXM 50000
#define MAXK 1000
#define LL long long
int n,x,y,ecnt;
struct edge
{int nxt,to;
}e[MAXM*2+5];
LL space[MAXM*10+1];
LL *f[MAXM+5],*dp[MAXM+5],*tot=space+MAXM;
int dep[MAXM+5],id[MAXM+5],pnt;
int head[MAXM+5],fa[MAXM+5];
LL ans=0;
inline int read()
{
int x=0,f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x*f;
}
void add(int u,int v)
{
e[++ecnt]=(edge){head[u],v};
head[u]=ecnt;
e[++ecnt]=(edge){head[v],u};
head[v]=ecnt;
}
void new_node(int x)
{
dp[x]=tot;tot=tot+dep[x]*2+1;pnt+=dep[x]*4+2;
f[x]=tot;tot=tot+dep[x]*2+1;
}
void dfs1(int x)
{
dep[x]=1;id[x]=0;
for(int i=head[x];i!=-1;i=e[i].nxt)
{
int xnt=e[i].to;
if(xnt==fa[x])continue;
fa[xnt]=x;
dfs1(xnt);
if(dep[x]<dep[xnt]+1)
dep[x]=dep[xnt]+1,id[x]=xnt;
}
}
void dfs2(int x)
{
dp[x][0]=1;
if(id[x])
{
dp[id[x]]=dp[x]+1;
f[id[x]]=f[x]-1;//神奇指针
dfs2(id[x]);
ans+=f[id[x]][1];
}
for(int i=head[x];i!=-1;i=e[i].nxt)
{
int xnt=e[i].to;
if(xnt==fa[x]||xnt==id[x])continue;
new_node(xnt);
dfs2(xnt);
for(int j=dep[xnt];j>=0;j--)
{
ans+=dp[x][j]*f[xnt][j+1];
if(j>0)
{
ans+=f[x][j]*dp[xnt][j-1];
f[x][j]+=dp[x][j]*dp[xnt][j-1];
dp[x][j]+=dp[xnt][j-1];
}
f[x][j]+=f[xnt][j+1];
}
}
}
int main()
{
while(~scanf("%d",&n)&&n)
{
pnt=MAXM;
tot=space+MAXM;
ecnt=0;ans=0;
for(int i=1;i<=n;i++)head[i]=-1;
for(int i=1;i<n;i++)
{
x=read(),y=read();
add(x,y);
}
dfs1(1);
new_node(1);
dfs2(1);
printf("%lld\n",ans);
for(int i=MAXM;i<=pnt;i++)
space[i]=0;
}
}
更多例题:CF 741D Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths[将回文操作转为二进制,从而变成异或运算,启发式合并统计]
显然这一部分要说的是二维数点问题
离线:CDQ分治,树状数组
强制在线:K-D树
小技巧:强制在线可加一维时间变成离线问题
相信都会…这里就…
int lowbit(int x)
{return x&(-x);}
void Insert(int x,LL num)
{while(x<=n)C[x]+=num,x+=lowbit(x);}
LL Sum(int x)
{LL sum=0;while(x>0)sum+=C[x],x-=lowbit(x);return sum;}
二维偏序是将第一位维好序,求逆序对。
三维偏序就是在判断时加个树状数组即可。
归并排序不用写了吧qwq…
又一种玄学数据结构.
复杂度 O ( n n ) O(n\sqrt{n}) O(nn)
然而很容易不平衡,一旦不平衡就要暴力重构。
然而自己太弱只有一个不重构的…
#include
#include
#include
using namespace std;
#define MAXN 1000000
#define MAXM 500000
#define MAXK 2
#define INF 1000000000
int n,Q,root,D;
struct data
{
int val[MAXK],Min[MAXK],Max[MAXK],L,R;
data(int x=0,int y=0)
{
L=R=0,val[0]=x,val[1]=y;
}
}Qu[MAXM+5];
bool operator<(data A,data B)
{
return A.val[D]<B.val[D];
}
struct KDtree
{
int ans;
data t[MAXN+5],T;
void UP(data A,data B,int k)
{
A.Min[k]=min(A.Min[k],B.Min[k]);
A.Max[k]=max(A.Max[k],B.Max[k]);
}
void EQ(int A)
{
for(int i=0;i<MAXK;i++)
t[A].Max[i]=t[A].Min[i]=t[A].val[i];
}
void pushup(int k)
{
data l=t[t[k].L],r=t[t[k].R];
for(int i=0;i<MAXK;i++)
{
if (t[k].L) t[k].Min[i]=min(t[k].Min[i],l.Min[i]),t[k].Max[i]=max(t[k].Max[i],l.Max[i]);
if (t[k].R) t[k].Min[i]=min(t[k].Min[i],r.Min[i]),t[k].Max[i]=max(t[k].Max[i],r.Max[i]);
}
}
int build(int L,int R,int k)
{
D=k;
int mid=(L+R)>>1;
nth_element(Qu+L,Qu+mid,Qu+R+1);
t[mid]=Qu[mid];
EQ(mid);
if(L<mid)t[mid].L=build(L,mid-1,k^1);
if(R>mid)t[mid].R=build(mid+1,R,k^1);
pushup(mid);
return mid;
}
void insert(data p)
{
T=p;
INSERT(root,0);
}
void INSERT(int num,int k)
{
if(T.val[k]>=t[num].val[k])
{
if(t[num].R)INSERT(t[num].R,k^1);
else
{
t[num].R=++n,t[n]=T;
EQ(n);
}
}
else
{
if(t[num].L)INSERT(t[num].L,k^1);
else
{
t[num].L=++n,t[n]=T;
EQ(n);
}
}
pushup(num);
}
int get(data p)
{
int cnt=0;
for(int i=0;i<MAXK;i++)
cnt+=max(p.Min[i]-T.val[i],0)+max(T.val[i]-p.Max[i],0);
return cnt;
}
int dis(data A,data B)
{
int cnt=0;
for(int i=0;i<MAXK;i++)
cnt+=abs(A.val[i]-B.val[i]);
return cnt;
}
int query(data p)
{
ans=INF;T=p;
QUERY(root);
return ans;
}
void QUERY(int num)
{
int d,dl=INF,dr=INF;
d=dis(t[num],T);
ans=min(ans,d);
if(t[num].L)dl=get(t[t[num].L]);
if(t[num].R)dr=get(t[t[num].R]);
if(dl<dr)
{
if(dl<ans)QUERY(t[num].L);
if(dr<ans)QUERY(t[num].R);
}
else
{
if(dr<ans)QUERY(t[num].R);
if(dl<ans)QUERY(t[num].L);
}
}
}Tree;
int main()
{
scanf("%d%d",&n,&Q);
for(int i=1;i<=n;i++)
scanf("%d%d",&Qu[i].val[0],&Qu[i].val[1]);
root=Tree.build(1,n,0);
while(Q--)
{
int F,x,y;
scanf("%d%d%d",&F,&x,&y);
if(F==1)Tree.insert(data(x,y));
else printf("%d\n",Tree.query(data(x,y)));
}
}
再来一道‘简单题’
对于修改操作,都可以视作插入一个点,同样,如果插入后导致KD-Tree不平衡,就要暴力重建.
对于查询操作,我们可以维护每一棵子树中所有点x和y坐标的最大/最小值,如果当前子树整棵树都在当前查询矩形内,就可以直接返回这棵子树中的权值和.如果当前子树整棵树都不在当前查询矩形内,就可以直接返回0.否则,先查询当前节点代表的点是否在查询矩形中,然后递归查询当前节点的两个子节点.
没时间打了qwq
int get_hash(int val)
{
for(int i=0;i<Hs[val%MOD].size();i++)
if(Hs[val%MOD][i].FR==val)return Hs[val%MOD][i].SE;
return 0;
}
void hash_ins(int val)
{
for(int i=0;i<Hs[val%MOD].size();i++)
if(Hs[val%MOD][i].FR==val){Hs[val%MOD][i].SE++;return ;}
Hs[val%MOD].push_back(make_pair(val,1));
}
又叫滚动hash,和二分套在一起可以解决很多问题。
技巧:找一个不太常见的大质数。
例如 900000011 900000011 900000011, 1000000123 1000000123 1000000123。
LL prep[MAXN+5],rhash[MAXN+5];
LL get_hash(int l,int r)
{
long long p=(rhash[l]-1LL*rhash[r+1]*prep[r-l+1]%MOD)%MOD;
p=(p+MOD)%MOD;return p;
}
int main()
{
prep[0]=1;
for(int i=1;i<=MAXN;i++)
prep[i]=(1LL*prep[i-1]*CHSIZ)%MOD;
rhash[n+1]=0;
for(int i=n;i>=0;i--)
rhash[i]=(rhash[i+1]*CHSIZ+s[i]-'a'+1)%MOD;
}
经典例题:[NOI2016]优秀的拆分
#include
#include
#include
#include
#include
using namespace std;
#define MOD 900000011
#define MAXN 30000
#define CHSIZ 29
#define LL long long
int T,n;
char s[MAXN+5];
LL prep[MAXN+5],rhash[MAXN+5],A[MAXN+5],B[MAXN+5];
LL get_hash(int l,int r)
{
long long p=(rhash[l]-1LL*rhash[r+1]*prep[r-l+1]%MOD)%MOD;
p=(p+MOD)%MOD;return p;
}
int main()
{
scanf("%d",&T);
prep[0]=1;
for(int i=1;i<=MAXN;i++)
prep[i]=(1LL*prep[i-1]*CHSIZ)%MOD;
while(T--)
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n+1;i++)A[i]=B[i]=0;
rhash[n+1]=0;
for(int i=n;i>=0;i--)
rhash[i]=(rhash[i+1]*CHSIZ+s[i]-'a'+1)%MOD;
for(int L=1;L*2<=n;L++)
for(int i=L*2;i<=n;i+=L)
{
if(s[i-L]!=s[i])continue;
int l=1,r=L,p=0,lst=i-L;
while(l<=r)
{
int mid=(l+r)>>1;
if(get_hash(lst-mid+1,lst)==get_hash(i-mid+1,i))p=mid,l=mid+1;
else r=mid-1;
}
int ml=i-p+1;
l=1,r=L,p=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(get_hash(lst,lst+mid-1)==get_hash(i,i+mid-1))p=mid,l=mid+1;
else r=mid-1;
}
int mr=i+p-1;
ml=max(ml+L-1,i);
mr=min(mr,i+L-1);
if(ml<=mr)
{
B[ml-2*L+1]++,B[mr-2*L+2]--;
A[ml]++,A[mr+1]--;
}
}
LL ans=0;
for(int i=1;i<=n;i++)A[i]+=A[i-1],B[i]+=B[i-1];
for(int i=1;i<n;i++)ans+=A[i]*B[i+1];
printf("%lld\n",ans);
}
}
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 100000
int T;
int n,p[MAXN*2+5];
char s[MAXN*2+5],Ls[MAXN*2+5];
void Init()
{
s[0]='#',s[1]='$';
for(int i=2;i<=n*2;i+=2)
s[i]=Ls[i/2],s[i+1]='$';
s[n*2+2]='@';
}
int mancher()
{
Init();
n=n*2+2;
int pos,mr=0,maxL=0;
for(int i=1;i<n;i++)
{
if(i<mr)p[i]=min(p[pos*2-i],mr-i);
else p[i]=1;
while(s[i-p[i]]==s[i+p[i]])p[i]++;
if(mr<p[i]+i)mr=p[i]+i,pos=i;
maxL=max(maxL,p[i]-1);
}
return maxL;
}
int main()
{
while(~scanf("%s",Ls+1))
{
n=strlen(Ls+1);
memset(p,0,sizeof(p));
memset(s,0,sizeof(s));
printf("%d\n",mancher());
memset(Ls,0,sizeof(Ls));//
}
}
#include
#include
#include
#include
#include
using namespace std;
#define MAXM 10000
#define MAXN 1000000
int T,n,m;
char a[MAXN+5],b[MAXM+5];
int nxt[MAXN+5];
void prepare()
{
int i=1,j=0;
nxt[0]=-1,nxt[1]=0;
while(i<m)
if(j==-1||b[i]==b[j])nxt[++i]=++j;
else j=nxt[j];
}
int cal()
{
int i=0,j=0,cnt=0;
while(i<n)
if(j==-1||a[i]==b[j])i++,j++;
else if(j==m)cnt++,j=nxt[j];
else j=nxt[j];
if(j==m)cnt++;
return cnt;
}
int main()
{
scanf("%d",&T);
while(T--)
{
memset(nxt,0,sizeof(nxt));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
scanf("%s%s",b,a);
n=strlen(a),m=strlen(b);
prepare();
printf("%d\n",cal());
}
}
有关KMP的结论:len-next[i]为此字符串的最小循环节(i为字符串的结尾),另外如果 l e n ≡ 0 ( m o d    l e n − n e x t i ) len\equiv 0(\mod len-next_i) len≡0(modlen−nexti),此字符串的最小周期就为 l e n l e n − n e x t i \frac{len}{len-next_i} len−nextilen。
模板:Keywords Search
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 1000000
#define MAXCH 26
int n,T;
char Ls[MAXN+5];
struct AC_automaton
{
int tr[MAXN+5][MAXCH],cnt;
int endp[MAXN+5],fail[MAXN+5];
void Clear(){cnt=0;memset(tr[0],0,sizeof(tr[0]));}
void Insert(char *s)
{
int p=0,len=strlen(s);
for(int i=0;i<len;i++)
{
int num=s[i]-'a';
if(!tr[p][num])
{
tr[p][num]=++cnt;
memset(tr[cnt],0,sizeof(tr[cnt]));
endp[cnt]=0,fail[cnt]=0;
}
p=tr[p][num];
}
endp[p]++;
}
void Build()
{
queue<int> Q;
for(int i=0;i<MAXCH;i++)
if(tr[0][i])Q.push(tr[0][i]);
while(!Q.empty())
{
int k=Q.front();
Q.pop();
for(int i=0;i<MAXCH;i++)
if(tr[k][i])
{
fail[tr[k][i]]=tr[fail[k]][i];
Q.push(tr[k][i]);
}
else tr[k][i]=tr[fail[k]][i];
}
}
int Query(char *s)
{
int p=0,res=0,len=strlen(s);
for(int i=0;i<len;i++)
{
int num=s[i]-'a';
p=tr[p][num];
for(int j=p;j&&~endp[j];j=fail[j])
res+=endp[j],endp[j]=-1;
}
return res;
}
}ac;
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
ac.Clear();
for(int i=1;i<=n;i++)
{scanf("%s",Ls);ac.Insert(Ls);}
ac.Build();
scanf("%s",Ls);
printf("%d\n",ac.Query(Ls));
}
}
经典例题:[HDU]考研路茫茫――单词情结
将AC自动机所构成的Tire图化为邻接矩阵。 k k k次行走相当于矩阵的 k k k次方。矩阵快速幂即可
#include
#include
#include
#include
using namespace std;
#define MAXN 27
#define MAXM 5
#define CHSIZ 26
#define LL long long
#define UL unsigned long long
#define RI register int
int T,n,cnt,k;
char Ls[MAXM+1];
struct matrix
{
int siz;
LL mat[MAXN][MAXN];
inline void Init(int xsiz)
{
siz=xsiz;
for(RI i=0;i<=siz;i++)
for(RI j=0;j<=siz;j++)mat[i][j]=0;
}
inline void Init_r()
{
for(RI i=0;i<=siz;i++)mat[i][i]=1;
}
};
struct AC_automaton
{
int tr[MAXN][CHSIZ];
int C_end[MAXN],fail[MAXN];
inline void Clear()
{
cnt=0;
fail[0]=0;
memset(tr[0],0,sizeof(tr[0]));
}
inline void Insert(char *s)
{
RI p=0,len=strlen(s);
for(RI i=0;i<len;i++)
{
int num=s[i]-'a';
if(!tr[p][num])
{
tr[p][num]=++cnt;
C_end[cnt]=0;
memset(tr[cnt],0,sizeof(tr[cnt]));
fail[cnt]=0;
}
p=tr[p][num];
}
C_end[p]=1;
}
inline void Build()
{
queue<int> Q;
memset(fail,0,sizeof(fail));
for(RI i=0;i<CHSIZ;i++)if(tr[0][i])Q.push(tr[0][i]);
while(!Q.empty())
{
RI k=Q.front();
Q.pop();
C_end[k]|=C_end[fail[k]];//在这里就可以处理从k开始跳是否有endpos了
for(RI i=0;i<CHSIZ;i++)
if(tr[k][i])
{
fail[tr[k][i]]=tr[fail[k]][i];
Q.push(tr[k][i]);
}
else tr[k][i]=tr[fail[k]][i];
}
}
}ac;
matrix C;
matrix operator *(const matrix &A,const matrix &B)
{
C.siz=A.siz;
for(RI i=0;i<=A.siz;i++)
for(RI j=0;j<=A.siz;j++)
{
C.mat[i][j]=0;
for(RI k=0;k<=A.siz;k++)
C.mat[i][j]+=A.mat[k][j]*B.mat[i][k];
}
return C;
}
inline void M_pow(matrix &A,matrix &res,int k)
{
while(k)
{
if(k&1)res=res*A;
A=A*A;
k>>=1;
}
}
int main()
{
while(~scanf("%d%d",&n,&k))
{
ac.Clear();
for(RI i=1;i<=n;i++)
{
scanf("%s",Ls);
ac.Insert(Ls);
}
ac.Build();
matrix res,A;
res.Init(cnt+1),A.Init(cnt+1);
res.Init_r();
for(RI i=0;i<=cnt;i++)
{
for(RI j=0;j<CHSIZ;j++)
{
if(ac.C_end[ac.tr[i][j]])continue;
A.mat[i][ac.tr[i][j]]++;
}
}
for(int i=0;i<=cnt+1;i++)A.mat[i][cnt+1]=1;
M_pow(A,res,k);
UL p1=0;
for(RI i=0;i<=cnt+1;i++)p1=p1+res.mat[0][i];
p1--;
matrix B;
B.Init(1),res.Init(1);
res.Init_r();
B.mat[0][0]=26,B.mat[1][0]=1,B.mat[1][1]=1;
M_pow(B,res,k);
UL p2=res.mat[1][0]+res.mat[0][0];
p2--;
p2-=p1;
printf("%I64u\n",p2);
}
}
2.Wireless Password [AC自动机上dp入门]
定义 d p [ i ] [ j ] [ S ] dp[i][j][S] dp[i][j][S]为串长为 i i i,现在在AC自动机上匹配到位置 j j j,已经有 S S S状态的字符完成匹配的方案数。
直接dp即可
#include
#include
#include
#include
using namespace std;
#define MAXN 26
#define MAXM 101
#define MAXK 10
#define CHSIZ 26
#define MOD 20090717
#define LL long long
#define RI int
int n,m,k;
char Ls[MAXK+1];
int pop_cnt[1<<MAXK];
struct AC_automaton
{
int tr[MAXM][CHSIZ],cnt;
int C_end[MAXM],fail[MAXM];
int dp[MAXN][MAXM][1<<MAXK];
void Clear()
{
cnt=0;
fail[0]=0;
memset(tr[0],0,sizeof(tr[0]));
}
void Insert(char *s,int id)
{
RI p=0,len=strlen(s);
for(RI i=0;i<len;i++)
{
int num=s[i]-'a';
if(!tr[p][num])
{
tr[p][num]=++cnt;
C_end[cnt]=0;
memset(tr[cnt],0,sizeof(tr[cnt]));
fail[cnt]=0;
}
p=tr[p][num];
}
C_end[p]|=(1<<id);
}
void Build()
{
queue<int> Q;
memset(fail,0,sizeof(fail));
for(RI i=0;i<CHSIZ;i++)if(tr[0][i])Q.push(tr[0][i]);
while(!Q.empty())
{
RI k=Q.front();
Q.pop();
C_end[k]|=C_end[fail[k]];
for(RI i=0;i<CHSIZ;i++)
if(tr[k][i])
{
fail[tr[k][i]]=tr[fail[k]][i];
Q.push(tr[k][i]);
}
else tr[k][i]=tr[fail[k]][i];
}
}
int DP()
{
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=0;i<n;i++)
for(int j=0;j<=cnt;j++)
for(int S=0;S<(1<<m);S++)
{
if(dp[i][j][S]==0)continue;
for(int p=0;p<CHSIZ;p++)
{
int &tmp=dp[i+1][tr[j][p]][S|C_end[tr[j][p]]];
tmp=tmp+dp[i][j][S];
tmp%=MOD;
}
}
int ans=0;
for(int S=0;S<(1<<m);S++)
{
if(pop_cnt[S]<k)continue;
for(int j=0;j<=cnt;j++)
{
ans=ans+dp[n][j][S];
ans%=MOD;
}
}
return ans;
}
}ac;
int main()
{
for(int S=0;S<(1<<MAXK);S++)
{
int cnt=0,ps=S;
while(ps)ps-=ps&(-ps),cnt++;
pop_cnt[S]=cnt;
}
while(~scanf("%d%d%d",&n,&m,&k)&&n)
{
ac.Clear();
for(int i=0;i<m;i++)
{
scanf("%s",Ls);
ac.Insert(Ls,i);
}
ac.Build();
printf("%d\n",ac.DP());//
}
}
考虑到题目都几乎不可做就弃掉了
并没有深入地学过…
写点最基础的公式就跑吧…
感觉三年初中废了
叉积:对于所有 u , v ∈ R 3 u, v ∈ \R^3 u,v∈R3,定义: u × v = ( ∣ u ∣ ∣ v ∣ sin θ ) u × v = (|u||v|\sin θ) u×v=(∣u∣∣v∣sinθ)其中 θ 为 u v 之间的夹角,n 为垂直于 u v 所在平面的单位。
点积:对于任意两个向量 u, v,定义: u ⋅ v = ∣ u ∣ ∣ v ∣ c o s θ u · v = |u||v| cos θ u⋅v=∣u∣∣v∣cosθ。
两线段 A B AB AB, C D CD CD
规范相交:两线段的交点严格在线段内部(与线段端点不相交)
( C B ⃗ × C D ⃗ ) ( C A ⃗ × C D ⃗ ) < 0 (\vec{CB} ×\vec{CD})(\vec{CA} ×\vec{CD})<0 (CB×CD)(CA×CD)<0
( A D ⃗ × A B ⃗ ) ( A C ⃗ × A B ⃗ ) < 0 (\vec{AD} ×\vec{AB})(\vec{AC} ×\vec{AB})<0 (AD×AB)(AC×AB)<0
非规范相交:两线段交点在端点上,在规范相交的基础上判断某条线
段的端点是否在另一线段上。
点 P P P 与直线 A B AB AB
距离: d i s t ( P , A B ) = ∣ A B ⃗ × A P ⃗ ∣ ∣ A B ∣ dist(P, AB) = \frac{|\vec{AB}×\vec{AP}|}{|AB|} dist(P,AB)=∣AB∣∣AB×AP∣
其它都没学过…
求公切线:如下图,先求出旋转角度,然后移动向量。
于是就有了…「THUSC2017」宇宙广播
[感觉自己菜爆了]
今年估计又有两道提交答案…
大多数情况都是问你一个NP问题,求较优解。
写过一道简单提答的题解[NOI2008]赛程安排
也写过「THUWC 2017」大葱的神力算是THU中最简单的一道提交答案题吧…然而搞了将近3个小时的 8 − 10 8-10 8−10还是只有83分…[我好菜啊…]
这两道都属于NP问题,总体来说这类问题都相对简单,常常用来送分,但是满分却又是不可能的事。
常见骗分方法:
常见的问法有:
exe
文件,写出源文件。给你输出,还原输入。例如[NOI2016]旷野大计算,就是一道经典的构造型问题。
许多构造题都有逆向思维,只有多练才可能有进步。
这种题一般是只能够按照题目要求来骗分的。
当然也要更加仔细的观察数据特点了。
所以也只能凭rp了qwq.
「THUSC2017」宇宙广播一道经典的计算型提交答案。
一般都和提交答案本身没什么关系。唯一的特点就是它的输入文件我们是可以看见的。而这种题目大多会有多个测试数据,或许我们无法解决整个测试点,但是我们可以观察一小个测试点的特性从而骗分。
一类非常有趣的题型。
在OI中,交互题的难度一般不会太高。
通常涉及概率,随机化,或数据结构等。
然而调试起来特别麻烦,所以一定要注意。
例题:硬币翻转
老虎最近沉迷一个翻硬币游戏。
在游戏中,老虎有 100 枚硬币,每次老虎可以选择一枚硬币,并改变其状态——在正面朝上和背面朝上间发生变化。显然,老虎和蒜头的关注点并不一样。蒜头在思考这样的一个游戏:蒜头会持有 100 枚不同的硬币,老虎每次可以给蒜头一个硬币的正反序列来猜测硬币的状态,随后蒜头会告诉老虎他猜对了多少枚硬币,并在 100 枚硬币中等概率选择一枚硬币。若老虎这一轮猜对了这枚硬币,它就会被翻转。老虎的目标是,在某一次完全猜对蒜头的硬币序列,也就是蒜头告诉老虎的答案会是 100。
你能帮助老虎完成这个问题吗?
询问次数限制:200次
一道简单题。
一个显然的想法就是先将答案缩减为0,然后整体翻转一下,就达到了目标。
由于蒜头每次都会翻转一个猜对的硬币,因此不断问同一个硬币序列,总能够使答案为0。注意到题目说蒜头是在全部硬币中选一个翻转,因此在猜对硬币较少时概率会很小。
考虑利用概率小这个性质,我们每次改变一个位置 i i i,如果它现在这个位置猜对,那么它会有 1 n \frac{1}{n} n1被翻过来,否则就不会猜对,且猜对个数减1。那么这样的概率就非常小了。
#include "coin.h"
#include
#include
#include
#include
using namespace std;
void guess(){
srand(666233);
std::string s = "";
for (int x = 0; x < 100; ++x) {
s+=rand()%2+'0';
}
int pts=ask(s);
for(int i=0;i<100;i++)
{
while(1)
{
s[i]=((s[i]-'0')^1)+'0';
int nval=ask(s);
if(nval<pts){pts=nval;break;}
pts=nval;
}
}
for (int x = 0; x < 100; ++x)
s[x]=((s[x]-'0')^1)+'0';
ask(s);
}
register int
,inline
大法好。priority_queue
,operator
,unordered_map
(不过我认为不会用…),Dijkstra
(不明白我为什么会觉得它难拼…),automaton
,sterling
终于没了,一口气写完居然没猝死…
三次考试总分能上200就好…