这里是我的省选的各种模板。
1.Dijkstra算法
题目描述
给出一个有向图,请输出从某一点出发到所有点的最短路径长度。
输入格式
第一行包含三个整数N、M、S,分别表示点的个数、有向边的个数、出发点的编号。
接下来M行每行包含三个整数Fi、Gi、Wi,分别表示第i条有向边的出发点、目标点和长度。
输出格式
一行,包含N个用空格分隔的整数,其中第i个整数表示从点S出发到点i的最短路径长度(若S=i则最短路径长度为0,若从点S无法到达点i,则最短路径长度为2147483647)
输入样例
4 6 1
1 2 2
2 3 2
2 4 1
1 3 5
3 4 3
1 4 4
输出样例
0 2 4 3
数据规模
对于20%的数据:N<=5,M<=15
对于40%的数据:N<=100,M<=10000
对于70%的数据:N<=1000,M<=100000
对于100%的数据:N<=10000,M<=500000
代码
#include<cstdio>
#include<queue>
using namespace std;
int n,m,s,u,v,d,cnt,head[10005],to[500005],nxt[500005],dd[500005],dis[10005];
bool vis[10005];
struct data{
int v,d;
data(){}
data(int v,int d):v(v),d(d){}
bool operator < (const data &b) const{
return d>b.d;
}
}now;
priority_queue<data> q;
void adde(int u,int v,int d){
to[++cnt]=v;
nxt[cnt]=head[u];
dd[cnt]=d;
head[u]=cnt;
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&d);
adde(u,v,d);
}
for(int i=1;i<=n;i++){
if(i!=s){
dis[i]=0x7fffffff;
}
}
q.push(data(s,0));
while(!q.empty()){
now=q.top();
q.pop();
if(vis[now.v]){
continue;
}
vis[now.v]=true;
for(int i=head[now.v];i;i=nxt[i]){
if(!vis[to[i]]&&dis[now.v]+dd[i]<dis[to[i]]){
dis[to[i]]=dis[now.v]+dd[i];
q.push(data(to[i],dis[to[i]]));
}
}
}
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}
2.各种Tarjan算法
1.tarjan算法求强联通分量+缩点+拓扑排序
题目背景
缩点+DP
题目描述
给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
输入格式
第一行,n,m
第二行,n个整数,依次代表点权
第三至m+2行,每行两个整数u,v,表示u->v有一条有向边
输出格式
共一行,最大的点权之和。
输入样例
2 2
1 1
1 2
2 1
输出样例
2
说明
n<=10^4,m<=10^5,点权<=1000
算法
Tarjan缩点+DAGdp
代码
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cassert>
using namespace std;
const int N=10005,M=100005,inf=0x3f3f3f3f;
int n,m,u,v,idx,tot,a[N],val[N],dfn[N],low[N],stk[N],scc[N],ans[N];
queue<int> q;
struct Graph{
int cnt,head[N],in[N],to[M],nxt[M];
Graph(){cnt=0;}
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
in[v]++;
}
}g1,g2;
void tarjan(int u){
low[u]=dfn[u]=++idx;
stk[++stk[0]]=u;
int v;
for(int i=g1.head[u];i;i=g1.nxt[i]){
v=g1.to[i];
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(!scc[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
tot++;
do{
val[tot]+=a[stk[stk[0]]];
scc[stk[stk[0]]]=tot;
}while(stk[stk[0]--]!=u);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
g1.adde(u,v);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(i);
}
}
for(int i=1;i<=n;i++){
for(int j=g1.head[i];j;j=g1.nxt[j]){
v=g1.to[j];
if(scc[i]!=scc[v]){
g2.adde(scc[i],scc[v]);
}
}
}
for(int i=1;i<=tot;i++){
if(!g2.in[i]){
ans[i]=val[i];
q.push(i);
}else{
ans[i]=-inf;
}
}
while(!q.empty()){
u=q.front();
q.pop();
for(int i=g2.head[u];i;i=g2.nxt[i]){
v=g2.to[i];
if(g2.in[v]>0){
g2.in[v]--;
ans[v]=max(ans[v],ans[u]+val[v]);
if(!g2.in[v]){
q.push(v);
}
}
}
}
int maxn=-inf;
for(int i=1;i<=tot;i++){
maxn=max(maxn,ans[i]);
}
printf("%d
",maxn);
return 0;
}
2.边双联通分量
描述
在基本的网络搭建完成后,学校为了方便管理还需要对所有的服务器进行编组,网络所的老师找到了小Hi和小Ho,希望他俩帮忙。
老师告诉小Hi和小Ho:根据现在网络的情况,我们要将服务器进行分组,对于同一个组的服务器,应当满足:当组内任意一个连接断开之后,不会影响组内服务器的连通性。在满足以上条件下,每个组内的服务器数量越多越好。
比如下面这个例子,一共有6个服务器和7条连接:
其中包含2个组,分别为{1,2,3},{4,5,6}。对{1,2,3}而言,当1-2断开后,仍然有1-3-2可以连接1和2;当2-3断开后,仍然有2-1-3可以连接2和3;当1-3断开后,仍然有1-2-3可以连接1和3。{4,5,6}这组也是一样。
老师把整个网络的情况告诉了小Hi和小Ho,小Hi和小Ho要计算出每一台服务器的分组信息。
输入
第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000
第2..M+1行:2个正整数,u,v。表示存在一条边(u,v),连接了u,v两台服务器。1≤u
输出
第1行:1个整数,表示该网络的服务器组数。
第2行:N个整数,第i个数表示第i个服务器所属组内,编号最小的服务器的编号。比如分为{1,2,3},{4,5,6},则输出{1,1,1,4,4,4};若分为{1,4,5},{2,3,6}则输出{1,2,2,1,1,2}
样例输入
6 7
1 2
1 3
2 3
3 4
4 5
4 6
5 6
样例输出
2
1 1 1 4 4 4
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20005,M=200005;
int n,m,u,v,cnt=0,idx=0,tot=0,stk[N],scc[N],minn[N],dfn[N],low[N],head[N],nxt[M],to[M];
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void tarjan(int pre,int u){
dfn[u]=low[u]=++idx;
stk[++stk[0]]=u;
int v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!dfn[v]){
tarjan(u,v);
low[u]=min(low[u],low[v]);
}
else if(v!=pre){
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]){
tot++;
do{
scc[stk[stk[0]]]=tot;
minn[tot]=min(minn[tot],stk[stk[0]]);
}
while(u!=stk[stk[0]--]);
}
}
int main(){
memset(minn,127,sizeof(minn));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
adde(u,v);
adde(v,u);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(0,i);
}
printf("%d
",tot);
for(int i=1;i<=n;i++)
printf("%d ",minn[scc[i]]);
puts("");
return 0;
}
3.点双联通分量
描述
小Hi和小Ho从约翰家回到学校时,网络所的老师又找到了小Hi和小Ho。
老师告诉小Hi和小Ho:之前的分组出了点问题,当服务器(上次是连接)发生宕机的时候,在同一组的服务器有可能连接不上,所以他们希望重新进行一次分组。这一次老师希望对连接进行分组,并把一个组内的所有连接关联的服务器也视为这个组内的服务器(注意一个服务器可能属于多个组)。
这一次的条件是对于同一个组满足:当组内任意一个服务器宕机之后,不会影响组内其他服务器的连通性。在满足以上条件下,每个组内的边数量越多越好。
比如下面这个例子,一共有6个服务器和7条连接:
其中包含3个组,分别为{(1,2),(2,3),(3,1)},{(4,5),(5,6),(4,6)},{(3,4)}。对{(1,2),(2,3),(3,1)}而言,和该组边相关联的有{1,2,3}三个服务器:当1宕机后,仍然有2-3可以连接2和3;当2宕机后,仍然有1-3可以连接1和3;当3宕机后,仍然有1-2可以连接1和2。
老师把整个网络的情况告诉了小Hi和小Ho,希望小Hi和小Ho统计一下一共有多少个分组。
输入
第1行:2个正整数,N,M。表示点的数量N,边的数量M。1≤N≤20,000, 1≤M≤100,000
第2..M+1行:2个正整数,u,v。第i+1行表示存在一条边(u,v),编号为i,连接了u,v两台服务器。1≤u
输出
第1行:1个整数,表示该网络的连接组数。
第2行:M个整数,第i个数表示第i条连接所属组内,编号最小的连接的编号。比如分为{(1,2)[1],(2,3)[3],(3,1)[2]},{(4,5)[5],(5,6)[7],(4,6)[6]},{(3,4)[4]},方括号内表示编号,则输出{1,1,1,4,5,5,5}。
样例输入
6 7
1 2
1 3
2 3
3 4
4 5
4 6
5 6
样例输出
3
1 1 1 4 5 5 5
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=20005,M=200005;
int n,m,u,v,cnt,idx,tot,id[M],minn[M],stk[M],bel[M],dfn[N],low[N],head[N],nxt[M],to[M];
bool ck[M];
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;}
void tarjan(int pre,int u){
dfn[u]=low[u]=++idx;
for(int v=head[u];v;v=nxt[v]){
if(to[v]!=pre&&!ck[id[v]]){
stk[++stk[0]]=id[v];
ck[id[v]]=true;
}
if(!dfn[to[v]]){
tarjan(u,to[v]);
low[u]=min(low[u],low[to[v]]);
if(low[to[v]]>=dfn[u]){
tot++;
do{
bel[stk[stk[0]]]=tot;
minn[tot]=min(minn[tot],stk[stk[0]]);
}
while(id[v]!=stk[stk[0]--]);
}
}
else{
low[u]=min(low[u],dfn[to[v]]);
}
}
}
int main(){
memset(minn,127,sizeof(minn));
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
adde(u,v);
id[cnt]=i;
adde(v,u);
id[cnt]=i;
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(0,i);
}
}
printf("%d
",tot);
for(int i=1;i<=m;i++){
printf("%d ",minn[bel[i]]);
}
puts("");
return 0;
}
4.割点
题目背景
割点
题目描述
给出一个n个点,m条边的无向图,求图的割点。
输入格式
第一行输入n,m
下面m行每行输入x,y表示x到y有一条边
输出格式
第一行输出割点个数
第二行按照节点编号从小到大输出节点,用空格隔开
输入样例
6 7
1 2
1 3
1 4
2 5
3 5
4 5
5 6
输出样例
1
5
说明
n,m均为100000
tarjan 图不一定联通!!!
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,u,v,cnt,tot,head[N],to[N*2],nxt[N*2];
int idx,dfn[N],low[N];
bool iscp[N];
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void tarjan(int pre,int u){
dfn[u]=low[u]=++idx;
int ch=0,v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!dfn[v]){
ch++;
tarjan(u,v);
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v]){
iscp[u]=true;
}
}else if(v!=pre&&dfn[v]<dfn[u]){
low[u]=min(low[u],dfn[v]);
}
}
if(pre<0&&ch==1){
iscp[u]=false;
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
adde(u,v);
adde(v,u);
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
tarjan(-1,i);
}
if(iscp[i]){
tot++;
}
}
printf("%d
",tot);
for(int i=1;i<=n;i++){
if(iscp[i]){
printf("%d ",i);
}
}
puts("");
return 0;
}
5.桥
求一遍边双联通分量,任意一条连接两个不同边双联通分量的边都是桥。
3.欧拉筛
const int N=1000005;
int p[N],phi[N];
bool ck[N];
void getprime(int n){
for(int i=2;i<=n;i++){
if(!ck[i]){
p[++p[0]]=i;
phi[i]=i-1;
}
for(int j=1;j<=p[0]&&i*p[j]<=n;j++){
ck[i*p[j]]=true;
if(i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];
break;
}else{
phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
}
}
4.拓展欧几里得算法
已知整数a,b,求一组满足的整数解。
typedef long long ll;
ll x,y;
ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1;
y=0;
return a;
}
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
调用exgcd(a,b,x,y)后,x,y即为一组解。
5.树链剖分维护边权
POJ3237
Description
You are given a tree with N nodes. The tree’s nodes are numbered 1 through N and its edges are numbered 1 through N − 1. Each edge is associated with a weight. Then you are to execute a series of instructions on the tree. The instructions can be one of the following forms:
CHANGE i v Change the weight of the ith edge to v
NEGATE a b Negate the weight of every edge on the path from a to b
QUERY a b Find the maximum weight of edges on the path from a to b
Input
The input contains multiple test cases. The first line of input contains an integer t (t ≤ 20), the number of test cases. Then follow the test cases.
Each test case is preceded by an empty line. The first nonempty line of its contains N (N ≤ 10,000). The next N − 1 lines each contains three integers a, b and c, describing an edge connecting nodes a and b with weight c. The edges are numbered in the order they appear in the input. Below them are the instructions, each sticking to the specification above. A lines with the word “DONE” ends the test case.
Output
For each “QUERY” instruction, output the result on a separate line.
Sample Input
1
3
1 2 1
2 3 2
QUERY 1 2
CHANGE 1 3
QUERY 1 2
DONE
Sample Output
1
3
给你一颗树,要求支持下列3个操作:
1.询问两点之间路径的边中边权最大的;
2.把两点之间的路径的边权全部取反;
3.修改某条边的边权。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100005,M=200005,inf=0x7fffffff;
int t,n,u,v,w,cnt,tot,head[N],nxt[M],to[M],fa[N],dep[N],siz[N],son[N],pos[N],top[N],maxv[N*4],minv[N*4],e[N][3];
int add[N*4];
char s[10];
void init()
{
cnt=tot=0;
memset(son,-1,sizeof(son));
memset(add,0,sizeof(add));
memset(head,0,sizeof(head));
}
void adde(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void dfs1(int pre,int u,int d)
{
fa[u]=pre;
dep[u]=d;
siz[u]=1;
for(int v=head[u];v;v=nxt[v])
if(to[v]^pre)
{
dfs1(u,to[v],d+1);
if(son[u]==-1||siz[to[v]]>siz[son[u]])
son[u]=to[v];
siz[u]+=siz[to[v]];
}
}
void dfs2(int u,int tp)
{
pos[u]=++tot;
top[u]=tp;
if(son[u]==-1) return;
dfs2(son[u],tp);
for(int v=head[u];v;v=nxt[v])
if(fa[u]^to[v]&&son[u]^to[v])
dfs2(to[v],to[v]);
}
void build(int o,int l,int r)
{
if(l==r)
{
maxv[o]=minv[o]=0;
return;
}
int mid=(l+r)/2;
build(o*2,l,mid);
build(o*2+1,mid+1,r);
}
void pushDown(int o)
{
if(!add[o]) return;
int tmp=maxv[o*2];
maxv[o*2]=-minv[o*2];
minv[o*2]=-tmp;
add[o*2]^=1;
tmp=maxv[o*2+1];
maxv[o*2+1]=-minv[o*2+1];
minv[o*2+1]=-tmp;
add[o*2+1]^=1;
add[o]=0;
}
void update(int o,int x,int l,int r,int k)
{
if(l==r)
{
maxv[o]=minv[o]=x;
add[o]=0;
return;
}
pushDown(o);
int mid=(l+r)/2;
if(k<=mid) update(o*2,x,l,mid,k);
else update(o*2+1,x,mid+1,r,k);
maxv[o]=max(maxv[o*2],maxv[o*2+1]);
minv[o]=min(minv[o*2],minv[o*2+1]);
}
void change(int o,int l,int r,int L,int R)
{
if(l==L&&r==R)
{
int tmp=maxv[o];
maxv[o]=-minv[o];
minv[o]=-tmp;
add[o]^=1;
return;
}
pushDown(o);
int mid=(l+r)/2;
if(R<=mid) change(o*2,l,mid,L,R);
else if(L>mid) change(o*2+1,mid+1,r,L,R);
else
{
change(o*2,l,mid,L,mid);
change(o*2+1,mid+1,r,mid+1,R);
}
maxv[o]=max(maxv[o*2],maxv[o*2+1]);
minv[o]=min(minv[o*2],minv[o*2+1]);
}
int query(int o,int l,int r,int L,int R)
{
if(l==L&&r==R) return maxv[o];
pushDown(o);
int mid=(l+r)/2;
if(R<=mid) return query(o*2,l,mid,L,R);
else if(L>mid) return query(o*2+1,mid+1,r,L,R);
else return max(query(o*2,l,mid,L,mid),query(o*2+1,mid+1,r,mid+1,R));
}
int Query(int u,int v)
{
int ans=-inf,f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
{
swap(f1,f2);
swap(u,v);
}
ans=max(ans,query(1,1,n,pos[f1],pos[u]));
u=fa[f1];
f1=top[u];
}
if(dep[u]>dep[v]) swap(u,v);
if(u!=v) ans=max(ans,query(1,1,n,pos[son[u]],pos[v]));
return ans;
}
void Negate(int u,int v)
{
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
{
swap(u,v);
swap(f1,f2);
}
change(1,1,n,pos[f1],pos[u]);
u=fa[f1];
f1=top[u];
}
if(dep[u]>dep[v]) swap(u,v);
if(u!=v) change(1,1,n,pos[son[u]],pos[v]);
}
int main()
{
scanf("%d",&t);
while(t--)
{
init();
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
adde(u,v);
adde(v,u);
e[i][0]=u,e[i][1]=v,e[i][2]=w;
}
dfs1(0,1,0);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<n;i++)
{
if(dep[e[i][0]]<dep[e[i][1]]) swap(e[i][0],e[i][1]);
update(1,e[i][2],1,n,pos[e[i][0]]);
}
while(~scanf("%s",s)&&s[0]!='D')
{
scanf("%d%d",&u,&v);
if(s[0]=='Q')
printf("%d
",Query(u,v));
else if(s[0]=='N') Negate(u,v);
else update(1,v,1,n,pos[e[u][0]]);
}
}
return 0;
}
6.点分治
POJ1741
Description
Give a tree with n vertices,each edge has a length(positive integer less than 1001).
Define dist(u,v)=The min distance between node u and v.
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k.
Write a program that will count how many pairs which are valid for a given tree.
Input
The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l.
The last test case is followed by two zeros.
Output
For each test case output the answer on a single line.
Sample Input
5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0
Sample Output
8
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
inline int rd(){char ch;int ret=0;bool f=false;while((ch=getchar())<'0'||ch>'9')if(ch=='-')f=true;while(ch>='0'&&ch<='9'){ret=ret*10+(ch-'0');ch=getchar();}if(f)ret=-ret;return ret;}
const int N=10005;
int n,k,cnt,ans,u,v,w,head[N],to[N*2],nxt[N*2],dd[N*2],mx[N],size[N],mi,dis[N],root,num;
bool vis[N];
inline int max(int a,int b)
{
return a>b?a:b;
}
void init()
{
cnt=ans=0;
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
}
void adde(int u,int v,int w)
{
to[++cnt]=v;
nxt[cnt]=head[u];
dd[cnt]=w;
head[u]=cnt;
}
void dfssize(int fa,int u)
{
mx[u]=0;
size[u]=1;
for(int v=head[u];v;v=nxt[v])
if(to[v]!=fa&&!vis[to[v]])
{
dfssize(u,to[v]);
size[u]+=size[to[v]];
mx[u]=max(mx[u],size[to[v]]);
}
}
void dfsroot(int rt,int fa,int u)
{
mx[u]=max(mx[u],size[rt]-size[u]);
if(mx[u]<mi) mi=mx[u],root=u;
for(int v=head[u];v;v=nxt[v])
if(to[v]!=fa&&!vis[to[v]])
dfsroot(rt,u,to[v]);
}
void dfsdis(int fa,int u,int d)
{
dis[num++]=d;
for(int v=head[u];v;v=nxt[v])
if(to[v]!=fa&&!vis[to[v]])
dfsdis(u,to[v],d+dd[v]);
}
int calc(int u,int d)
{
int ret=0;
num=0;
dfsdis(0,u,d);
std::sort(dis,dis+num);
int i=0,j=num-1;
while(i<j)
{
while(dis[i]+dis[j]>k&&i<j) j--;
ret+=j-i;
i++;
}
return ret;
}
void dfs(int u)
{
mi=n;
dfssize(0,u);
dfsroot(u,0,u);
ans+=calc(root,0);
vis[root]=true;
for(int v=head[root];v;v=nxt[v])
if(!vis[to[v]])
{
ans-=calc(to[v],dd[v]);
dfs(to[v]);
}
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF)
{
if(n==0&&k==0) break;
init();
for(int i=1;i<n;i++)
{
u=rd(),v=rd(),w=rd();
adde(u,v,w);
adde(v,u,w);
}
dfs(1);
printf("%d
",ans);
}
return 0;
}
7.动态点分治
震波
Description
在一片土地上有N个城市,通过N-1条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为1,其中第i个城市的价值为value[i]。
不幸的是,这片土地常常发生地震,并且随着时代的发展,城市的价值也往往会发生变动。
接下来你需要在线处理M次操作:
0 x k 表示发生了一次地震,震中城市为x,影响范围为k,所有与x距离不超过k的城市都将受到影响,该次地震造成的经济损失为所有受影响城市的价值和。
1 x y 表示第x个城市的价值变成了y。
为了体现程序的在线性,操作中的x、y、k都需要异或你程序上一次的输出来解密,如果之前没有输出,则默认上一次的输出为0。
Input
第一行包含两个正整数N和M。
第二行包含N个正整数,第i个数表示value[i]。
接下来N-1行,每行包含两个正整数u、v,表示u和v之间有一条无向边。
接下来M行,每行包含三个数,表示M次操作。
Output
包含若干行,对于每个询问输出一行一个正整数表示答案。
这道题是动态树分治的模板题。
简要的思路:
先建出分治树,每次修改和查询都在这个节点在分治树到根的路径上的所有祖先上面进行操作。
具体实现:
对每一个节点开2棵线段树,保存分治树上这个节点子树的信息。
第一棵线段树:以子树中所有点到这个点的距离为下标,点权为权值,维护区间的和。
第二棵线段树:以子树中所有点到这个点的分治树上的父亲的距离为下标,点权为权值,维护区间的和。
修改:在到根的路径上的所有线段树中删除这个点原来的点权,加入新的点权。
查询:在分治树上从查询点往根爬的时候,统计答案。设查询距离为d1,当前节点的父亲到查询点的距离为d2。答案加上当前节点的父亲维护的子树内第一棵线段树下标为0~d1-d2的点权总和,再减去当前节点维护的子树内第二棵线段树下标为0~d1-d2点权总和,因为从下面爬上来时,当前点的贡献已经计算过了,需要减掉,不然会重复。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200005;
int n,m,op,u,v,cnt,idx,tot,ans,Log2[N*2],a[N],head[N],to[N*2],nxt[N*2],dfn[N],dep[N],siz[N],pos[N*2],f[N*2][25];
int rt,mi,size,fa[N],root[N][2],sumv[N*50],ch[N*50][2],dd[N];
bool vis[N];
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void dfs(int pre,int u){
dfn[u]=++idx;
pos[idx]=u;
siz[u]=1;
int v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(v!=pre){
dep[v]=dep[u]+1;
dfs(u,v);
siz[u]+=siz[v];
pos[++idx]=u;
}
}
}
void st(){
for(int i=1;i<=idx;i++){
f[i][0]=pos[i];
}
for(int j=1;j<=20;j++){
for(int i=1;i+(1<<j)-1<=idx;i++){
if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]]){
f[i][j]=f[i][j-1];
}else{
f[i][j]=f[i+(1<<(j-1))][j-1];
}
}
}
}
int lca(int u,int v){
if(dfn[u]>dfn[v]){
swap(u,v);
}
int k=Log2[dfn[v]-dfn[u]];
if(dep[f[dfn[u]][k]]<dep[f[dfn[v]-(1<<k)+1][k]]){
return f[dfn[u]][k];
}else{
return f[dfn[v]-(1<<k)+1][k];
}
}
int dis(int u,int v){
return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void upd(int &o,int l,int r,int k,int v){
if(k<l||k>r){
return;
}
if(!o){
o=++tot;
}
sumv[o]+=v;
if(l==r){
return;
}
int mid=(l+r)/2;
if(k<=mid){
upd(ch[o][0],l,mid,k,v);
}else{
upd(ch[o][1],mid+1,r,k,v);
}
}
int qry(int o,int l,int r,int L,int R){
if(!o||L>r){
return 0;
}
if(L==l&&R==r){
return sumv[o];
}
int mid=(l+r)/2;
if(R<=mid){
return qry(ch[o][0],l,mid,L,R);
}else if(L>mid){
return qry(ch[o][1],mid+1,r,L,R);
}else{
return qry(ch[o][0],l,mid,L,mid)+qry(ch[o][1],mid+1,r,mid+1,R);
}
}
void dfsroot(int pre,int u){
siz[u]=1;
int v,mx=0;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!vis[v]&&v!=pre){
dfsroot(u,v);
siz[u]+=siz[v];
mx=max(mx,siz[v]);
}
}
mx=max(mx,size-siz[u]);
if(mx<mi){
rt=u;
mi=mx;
}
}
void init(int rt,int md,int pre,int u){
upd(root[rt][md],0,n,dd[u],a[u]);
int v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!vis[v]&&v!=pre){
dd[v]=dd[u]+1;
init(rt,md,u,v);
}
}
}
void dfstree(int u){
vis[u]=true;
int v;
dd[u]=0;
init(u,0,0,u);
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!vis[v]){
mi=size=siz[v];
dd[v]=1;
dfsroot(u,v);
init(rt,1,u,v);
fa[rt]=u;
dfstree(rt);
}
}
}
int query(int u,int d){
int ans=qry(root[u][0],0,n,0,d),tmp;
for(int i=u;fa[i];i=fa[i]){
tmp=dis(fa[i],u);
ans+=qry(root[fa[i]][0],0,n,0,d-tmp)-qry(root[i][1],0,n,0,d-tmp);
}
return ans;
}
void update(int u,int k){
int tmp;
upd(root[u][0],0,n,0,k-a[u]);
for(int i=u;fa[i];i=fa[i]){
tmp=dis(fa[i],u);
upd(root[fa[i]][0],0,n,tmp,k-a[u]);
upd(root[i][1],0,n,tmp,k-a[u]);
}
a[u]=k;
}
int main(){
for(int i=2;i<=200000;i++){
Log2[i]=Log2[i/2]+1;
}
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
adde(u,v);
adde(v,u);
}
dfs(0,1);
st();
size=mi=siz[1];
dfsroot(0,1);
dfstree(rt);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&op,&u,&v);
u^=ans;
v^=ans;
if(op==0){
printf("%d
",ans=query(u,v));
}else{
update(u,v);
}
}
return 0;
}
烁烁的游戏
Description
背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠。
题意:
给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠。
烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮鼠。皮皮鼠会被烁烁吸引,所以会一直待在节点上不动。
烁烁很好奇,在当前时刻,节点u有多少个他的好朋友—皮皮鼠。
大意:
给一颗n个节点的树,边权均为1,初始点权均为0,m次操作:
Q x:询问x的点权。
M x d w:将树上与节点x距离不超过d的节点的点权均加上w。
Input
第一行两个正整数:n,m
接下来的n-1行,每行三个正整数u,v,代表u,v之间有一条边。
接下来的m行,每行给出上述两种操作中的一种。
Output
对于每个Q操作,输出当前x节点的皮皮鼠数量。
解法:
先考虑暴力。对于每一次查询,把整棵树遍历一遍,对于每个遍历到的节点,加上当前节点到查询点的距离的所有的修改值。最后得到的就是这个点的点权。
有了思路,就上树分治啦!每个点开2棵可区间修改的李超线段树。
第一棵线段树:以子树中的每一个点到自己的距离为下标,维护每一种距离的修改值。
第二棵线段树:以子树中的每一个点到自己的父亲的距离为下标,维护每一种距离的修改值。
修改:设要修改的点为u,要与u距离不超过d1的所有点加上w。在向上爬的过程中,设当前爬到节点为i,u到fa[i]的距离为d2。则将fa[i]的第一棵线段树下标为0~d1-d2都加上w,再将用于去掉重复的i的第二棵线段树下标为0~d1-d2都加上w。
查询:其实统计答案的原理和暴力是一样的。在向上爬的过程中,设当前点的父亲到查询点的距离为d。对于每个祖先节点,答案加上这个点的父亲的第一棵线段树下标为d的修改值,再减去这个点的第二棵线段树下标为d的修改值,目的仍然是减去重复的。这大概就是动态树分治的一种套路吧。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,m,u,v,d,cnt,head[N],to[N*2],nxt[N*2];
int idx,lg2[N*2],dfn[N],dep[N],siz[N],pos[N*2],f[N*2][20];
int mi,size,rt,fa[N];
int tot,root[N][2],tag[20000005],ch[20000005][2];
bool vis[N];
char s[5];
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
void dfs(int pre,int u){
dfn[u]=++idx;
pos[idx]=u;
int v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(v!=pre){
dep[v]=dep[u]+1;
dfs(u,v);
pos[++idx]=u;
}
}
}
void st(){
for(int i=1;i<=idx;i++){
f[i][0]=pos[i];
}
for(int j=1;j<=20;j++){
for(int i=1;i+(1<<j)-1<=idx;i++){
if(dep[f[i][j-1]]<dep[f[i+(1<<(j-1))][j-1]]){
f[i][j]=f[i][j-1];
}else{
f[i][j]=f[i+(1<<(j-1))][j-1];
}
}
}
}
int lca(int u,int v){
if(dfn[u]>dfn[v]){
swap(u,v);
}
int k=lg2[dfn[v]-dfn[u]];
if(dep[f[dfn[u]][k]]<dep[f[dfn[v]-(1<<k)+1][k]]){
return f[dfn[u]][k];
}else{
return f[dfn[v]-(1<<k)+1][k];
}
}
void dfsroot(int pre,int u){
siz[u]=1;
int v,mx=0;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(v!=pre&&!vis[v]){
dfsroot(u,v);
siz[u]+=siz[v];
mx=max(mx,siz[v]);
}
}
mx=max(mx,size-siz[u]);
if(mx<mi){
mi=mx;
rt=u;
}
}
int dis(int u,int v){
return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void build(int pre,int u){
vis[u]=true;
fa[u]=pre;
int v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!vis[v]){
mi=size=siz[v];
dfsroot(u,v);
build(u,rt);
}
}
}
void upd(int &o,int l,int r,int L,int R,int v){
if(!o){
o=++tot;
}
if(L==l&&R==r){
tag[o]+=v;
return;
}
int mid=(l+r)/2;
if(R<=mid){
upd(ch[o][0],l,mid,L,R,v);
}else if(L>mid){
upd(ch[o][1],mid+1,r,L,R,v);
}else{
upd(ch[o][0],l,mid,L,mid,v);
upd(ch[o][1],mid+1,r,mid+1,R,v);
}
}
int qry(int o,int l,int r,int k){
if(!o){
return 0;
}
if(l==r){
return tag[o];
}
int mid=(l+r)/2;
if(k<=mid){
return tag[o]+qry(ch[o][0],l,mid,k);
}{
return tag[o]+qry(ch[o][1],mid+1,r,k);
}
}
int query(int u){
int ret=qry(root[u][0],0,n,0),tmp;
for(int i=u;fa[i];i=fa[i]){
tmp=dis(fa[i],u);
ret+=qry(root[fa[i]][0],0,n,tmp)-qry(root[i][1],0,n,tmp);
}
return ret;
}
void update(int u,int d,int w){
upd(root[u][0],0,n,0,d,w);
int tmp;
for(int i=u;fa[i];i=fa[i]){
tmp=dis(fa[i],u);
if(tmp>d){
continue;
}
upd(root[fa[i]][0],0,n,0,d-tmp,w);
upd(root[i][1],0,n,0,d-tmp,w);
}
}
int main(){
for(int i=2;i<=200000;i++){
lg2[i]=lg2[i/2]+1;
}
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d",&u,&v);
adde(u,v);
adde(v,u);
}
dfs(0,1);
st();
mi=size=n;
dfsroot(0,1);
build(0,rt);
for(int i=1;i<=m;i++){
scanf("%s%d",s,&u);
if(s[0]=='Q'){
printf("%d
",query(u));
}else{
scanf("%d%d",&d,&v);
update(u,d,v);
}
}
return 0;
}
8.各种平衡树
Description
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)
Input
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6)
Output
对于操作3,4,5,6每行输出一个数,表示对应答案
Sample Input
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
Sample Output
106465
84185
492737
HINT
1.n的数据范围:n<=100000
2.每个数的数据范围:[-2e9,2e9]
代码
1 Treap
#include<cstdio>
#include<cstdlib>
const int N=100005;
int n,op,x,rt,cnt,val[N],rnd[N],siz[N],w[N],ch[N][2];
void maintain(int k){
siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+w[k];
}
void rotate(int &y,int md){
int x=ch[y][md];
ch[y][md]=ch[x][!md];
ch[x][!md]=y;
maintain(y);
maintain(x);
y=x;
}
void insert(int &k,int x){
if(!k){
k=++cnt;
val[k]=x;
rnd[k]=rand();
siz[k]=w[k]=1;
return;
}
siz[k]++;
if(x==val[k]){
w[k]++;
}else if(x<val[k]){
insert(ch[k][0],x);
if(rnd[ch[k][0]]>rnd[k]){
rotate(k,0);
}
}else{
insert(ch[k][1],x);
if(rnd[ch[k][1]]>rnd[k]){
rotate(k,1);
}
}
}
void remove(int &k,int x){
if(!k){
return;
}
if(x==val[k]){
if(w[k]>1){
w[k]--;
siz[k]--;
}else if(ch[k][0]*ch[k][1]==0){
k=ch[k][0]+ch[k][1];
}else if(rnd[ch[k][0]]>rnd[ch[k][1]]){
rotate(k,0);
remove(k,x);
}else{
rotate(k,1);
remove(k,x);
}
}else if(x<val[k]){
siz[k]--;
remove(ch[k][0],x);
}else{
siz[k]--;
remove(ch[k][1],x);
}
}
int rnk(int x){
int k=rt,ret=1;
while(k){
if(x<=val[k]){
k=ch[k][0];
}else{
ret+=siz[ch[k][0]]+w[k];
k=ch[k][1];
}
}
return ret;
}
int kth(int x){
int k=rt;
while(k){
if(x>siz[ch[k][0]]&&x<=siz[ch[k][0]]+w[k]){
return val[k];
}else if(x<=siz[ch[k][0]]){
k=ch[k][0];
}else{
x-=siz[ch[k][0]]+w[k];
k=ch[k][1];
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&op,&x);
if(op==1){
insert(rt,x);
}else if(op==2){
remove(rt,x);
}else if(op==3){
printf("%d
",rnk(x));
}else if(op==4){
printf("%d
",kth(x));
}else if(op==5){
printf("%d
",kth(rnk(x)-1));
}else{
printf("%d
",kth(rnk(x+1)));
}
}
return 0;
}
2 Splay Tree
#include<cstdio>
const int N=100005;
int n,op,x,rt,cnt,tmp,val[N],ch[N][2],fa[N],siz[N],w[N];
int which(int k){
return ch[fa[k]][1]==k;
}
void maintain(int k){
siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+w[k];
}
void rotate(int x){
int y=fa[x],md=which(x);
if(fa[y]){
ch[fa[y]][which(y)]=x;
}
fa[x]=fa[y];
ch[y][md]=ch[x][!md];
fa[ch[y][md]]=y;
ch[x][!md]=y;
fa[y]=x;
maintain(y);
maintain(x);
}
void splay(int k,int f){
while(fa[k]!=f){
if(fa[fa[k]]!=f){
rotate(which(k)==which(fa[k])?fa[k]:k);
}
rotate(k);
}
if(!f){
rt=k;
}
}
void insert(int pre,int &k,int x){
if(!k){
tmp=k=++cnt;
val[k]=x;
w[k]=siz[k]=1;
fa[k]=pre;
return;
}
siz[k]++;
if(x==val[k]){
w[k]++;
}else if(x<val[k]){
insert(k,ch[k][0],x);
}else{
insert(k,ch[k][1],x);
}
}
void insert(int x){
tmp=0;
insert(0,rt,x);
if(tmp){
splay(tmp,0);
}
}
void remove(int x){
int k=rt;
while(k){
if(x==val[k]){
break;
}else if(x<val[k]){
k=ch[k][0];
}else{
k=ch[k][1];
}
}
splay(k,0);
if(w[k]>1){
w[k]--;
siz[k]--;
}else if(ch[rt][0]*ch[rt][1]==0){
fa[ch[rt][0]+ch[rt][1]]=0;
rt=ch[rt][0]+ch[rt][1];
}else{
k=ch[rt][0];
while(ch[k][1]){
k=ch[k][1];
}
splay(k,rt);
ch[k][1]=ch[rt][1];
fa[ch[rt][1]]=k;
fa[k]=0;
rt=k;
maintain(rt);
}
}
int rnk(int x){
int k=rt,ret=1;
while(k){
if(x<=val[k]){
k=ch[k][0];
}else{
ret+=siz[ch[k][0]]+w[k];
k=ch[k][1];
}
}
return ret;
}
int kth(int x){
int k=rt;
while(k){
if(x>siz[ch[k][0]]&&x<=siz[ch[k][0]]+w[k]){
return val[k];
}else if(x<=siz[ch[k][0]]){
k=ch[k][0];
}else{
x-=siz[ch[k][0]]+w[k];
k=ch[k][1];
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&op,&x);
if(op==1){
insert(x);
}else if(op==2){
remove(x);
}else if(op==3){
printf("%d
",rnk(x));
}else if(op==4){
printf("%d
",kth(x));
}else if(op==5){
printf("%d
",kth(rnk(x)-1));
}else{
printf("%d
",kth(rnk(x+1)));
}
}
return 0;
}
3 替罪羊树
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
const double alpha=0.75;
int n,op,x,rt,cnt,*goat,mmp[N],val[N],ch[N][2],del[N],siz[N],tot[N],pos[N];
int rnk(int x){
int k=rt,ret=1;
while(k){
if(x<=val[k]){
k=ch[k][0];
}else{
ret+=siz[ch[k][0]]+del[k];
k=ch[k][1];
}
}
return ret;
}
int kth(int x){
int k=rt;
while(k){
if(del[k]&&x==siz[ch[k][0]]+1){
return val[k];
}else if(x<=siz[ch[k][0]]+del[k]){
k=ch[k][0];
}else{
x-=siz[ch[k][0]]+del[k];
k=ch[k][1];
}
}
}
void dfs(int k){
if(!k){
return;
}
dfs(ch[k][0]);
if(del[k]){
pos[++pos[0]]=k;
}else{
mmp[++mmp[0]]=k;
}
dfs(ch[k][1]);
}
void build(int &k,int l,int r){
if(l>r){
k=0;
return;
}
int mid=(l+r)/2;
k=pos[mid];
build(ch[k][0],l,mid-1);
build(ch[k][1],mid+1,r);
siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+1;
tot[k]=tot[ch[k][0]]+tot[ch[k][1]]+1;
}
void rebuild(int &k){
pos[0]=0;
dfs(k);
build(k,1,pos[0]);
}
void insert(int &k,int x){
if(!k){
if(mmp[0]){
k=mmp[mmp[0]--];
}else{
k=++cnt;
}
val[k]=x;
siz[k]=tot[k]=del[k]=1;
ch[k][0]=ch[k][1]=0;
return;
}
siz[k]++;
tot[k]++;
if(x<=val[k]){
insert(ch[k][0],x);
}else{
insert(ch[k][1],x);
}
if(tot[k]*alpha<max(tot[ch[k][0]],tot[ch[k][1]])){
goat=&k;
}
}
void insert(int x){
goat=NULL;
insert(rt,x);
if(goat){
rebuild(*goat);
}
}
void remove(int k,int x){
if(!k){
return;
}
siz[k]--;
if(del[k]&&x==siz[ch[k][0]]+1){
del[k]=0;
return;
}
if(x<=siz[ch[k][0]]+del[k]){
remove(ch[k][0],x);
}else{
remove(ch[k][1],x-siz[ch[k][0]]-del[k]);
}
}
void remove(int x){
remove(rt,rnk(x));
if(siz[rt]<tot[rt]*alpha){
rebuild(rt);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&op,&x);
if(op==1){
insert(x);
}else if(op==2){
remove(x);
}else if(op==3){
printf("%d
",rnk(x));
}else if(op==4){
printf("%d
",kth(x));
}else if(op==5){
printf("%d
",kth(rnk(x)-1));
}else{
printf("%d
",kth(rnk(x+1)));
}
}
return 0;
}
4 离散化+权值线段树
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,cnt,rt,Hash[N],sum[N*20],ch[N*20][2];
struct query{
int op,x;
}q[N];
void update(int &o,int l,int r,int k,int v){
if(!o){
o=++cnt;
}
sum[o]+=v;
if(l==r){
return;
}
int mid=(l+r)/2;
if(k<=mid){
update(ch[o][0],l,mid,k,v);
}else{
update(ch[o][1],mid+1,r,k,v);
}
}
int rnk(int o,int l,int r,int k){
if(l==r){
return 1;
}
int mid=(l+r)/2;
if(k<=mid){
return rnk(ch[o][0],l,mid,k);
}else{
return sum[ch[o][0]]+rnk(ch[o][1],mid+1,r,k);
}
}
int kth(int o,int l,int r,int k){
if(l==r){
return l;
}
int mid=(l+r)/2;
if(k<=sum[ch[o][0]]){
return kth(ch[o][0],l,mid,k);
}else{
return kth(ch[o][1],mid+1,r,k-sum[ch[o][0]]);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&q[i].op,&q[i].x);
if(q[i].op!=4){
Hash[++Hash[0]]=q[i].x;
}
}
sort(Hash+1,Hash+Hash[0]+1);
Hash[0]=unique(Hash+1,Hash+Hash[0]+1)-Hash-1;
for(int i=1;i<=n;i++){
if(q[i].op!=4){
q[i].x=lower_bound(Hash+1,Hash+Hash[0]+1,q[i].x)-Hash;
}
if(q[i].op==1){
update(rt,1,n,q[i].x,1);
}else if(q[i].op==2){
update(rt,1,n,q[i].x,-1);
}else if(q[i].op==3){
printf("%d
",rnk(rt,1,n,q[i].x));
}else if(q[i].op==4){
printf("%d
",Hash[kth(rt,1,n,q[i].x)]);
}else if(q[i].op==5){
printf("%d
",Hash[kth(rt,1,n,rnk(rt,1,n,q[i].x)-1)]);
}else{
printf("%d
",Hash[kth(rt,1,n,rnk(rt,1,n,q[i].x+1))]);
}
}
return 0;
}
9.Link-Cut Tree
[Noi2014]魔法森林
Description
为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小E可以借助它们的力量,达到自己的目的。
只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边Ei包含两个权值Ai与Bi。若身上携带的A型守护精灵个数不少于Ai,且B型守护精灵个数不少于Bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。
Input
第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。
Output
输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。
题解
首先我们按a的大小对所有边从小到大排序,然后依次加边。如果这条边连接的u,v不连通,就直接加上。否则,用求出u到v路径中的b值最大的一条边,与当前边进行比较。如果当前边b值更小,就删去那条边,加入当前边。每次操作后,判断1和n的连通性,更新答案。很显然,这件事情可以用Link-Cut Tree实现。但是LCT怎么维护边权呢?具体实现时,把原来的点的权值设成0,边设成一个权值为原边权的点,然后LCT维护路径最大值即可。其实这就是动态维护最小生成树的思路。注意点的标号的问题。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=150005,M=150005;
int n,m,ans=0x7f7f7f7f,fa[N],ch[N][2],rev[N];
struct edge{
int u,v,a,b,id;
bool operator < (const edge &a) const{
return b<a.b;
}
}e[M],val[N],maxv[N];
bool cmp(edge a,edge b){
return a.a<b.a;
}
bool isroot(int u){
return ch[fa[u]][0]!=u&&ch[fa[u]][1]!=u;
}
int which(int u){
return u==ch[fa[u]][1];
}
void pushup(int u){
maxv[u]=val[u];
if(ch[u][0]){
maxv[u]=max(maxv[u],maxv[ch[u][0]]);
}
if(ch[u][1]){
maxv[u]=max(maxv[u],maxv[ch[u][1]]);
}
}
void reverse(int u){
rev[u]^=1;
swap(ch[u][0],ch[u][1]);
}
void downtag(int u){
if(rev[u]){
if(ch[u][0]){
reverse(ch[u][0]);
}
if(ch[u][1]){
reverse(ch[u][1]);
}
rev[u]=0;
}
}
void pushdown(int u){
if(!isroot(u)){
pushdown(fa[u]);
}
downtag(u);
}
void rotate(int x){
int y=fa[x],z=fa[y],md=which(x);
if(!isroot(y)){
ch[z][which(y)]=x;
}
fa[x]=z;
ch[y][md]=ch[x][!md];
fa[ch[y][md]]=y;
ch[x][!md]=y;
fa[y]=x;
pushup(y);
pushup(x);
}
void splay(int u){
pushdown(u);
while(!isroot(u)){
if(!isroot(fa[u])){
rotate(which(fa[u])==which(u)?fa[u]:u);
}
rotate(u);
}
}
void access(int u){
for(int v=0;u;v=u,u=fa[u]){
splay(u);
ch[u][1]=v;
pushup(u);
}
}
void makeroot(int u){
access(u);
splay(u);
reverse(u);
}
void link(int u,int v){
makeroot(u);
fa[u]=v;
}
void cut(int u,int v){
makeroot(u);
access(v);
splay(v);
fa[u]=ch[v][0]=0;
pushup(v);
}
bool isconnect(int u,int v){
if(u==v){
return true;
}
makeroot(u);
access(v);
splay(v);
return fa[u]!=0;
}
int main(){
scanf("%d%d",&n,&m);
if(n==1){
puts("0");
return 0;
}
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
}
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;i++){
e[i].id=i;
val[i+n]=maxv[i+n]=e[i];
}
for(int i=1;i<=m;i++){
if(!isconnect(e[i].u,e[i].v)){
link(e[i].u,i+n);
link(i+n,e[i].v);
}else if(e[i].u!=e[i].v){
makeroot(e[i].u);
access(e[i].v);
splay(e[i].v);
edge tmp=maxv[e[i].v];
if(e[i].b<maxv[e[i].v].b){
cut(tmp.u,tmp.id+n);
cut(tmp.id+n,tmp.v);
link(e[i].u,i+n);
link(i+n,e[i].v);
}
}
if(isconnect(1,n)){
makeroot(1);
access(n);
splay(n);
ans=min(ans,e[i].a+maxv[n].b);
}
}
printf("%d
",ans==0x7f7f7f7f?-1:ans);
return 0;
}
bzoj4530 [Bjoi2014]大融合
Description
小强要在N个孤立的星球上建立起一套通信系统。这套通信系统就是连接N个点的一个树。
这个树的边是一条一条添加上去的。在某个时刻,一条边的负载就是它所在的当前能够
联通的树上路过它的简单路径的数量。
例如,在上图中,现在一共有了5条边。其中,(3,8)这条边的负载是6,因
为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8)。
现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的
询问。
Input
第一行包含两个整数N,Q,表示星球的数量和操作的数量。星球从1开始编号。
接下来的Q行,每行是如下两种格式之一:
A x y 表示在x和y之间连一条边。保证之前x和y是不联通的。
Q x y 表示询问(x,y)这条边上的负载。保证x和y之间有一条边。
1≤N,Q≤100000
Output
对每个查询操作,输出被查询的边的负载。
Sample Input
8 6
A 2 3
A 3 4
A 3 8
A 8 7
A 6 5
Q 3 8
Sample Output
6
LCT维护子树信息,在access和link的时候更新一下即可。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005;
int n,q,u,v,ch[N][2],fa[N],siz[N],sx[N],rev[N];
char s[10];
bool isroot(int u){
return ch[fa[u]][0]!=u&&ch[fa[u]][1]!=u;
}
int which(int u){
return u==ch[fa[u]][1];
}
void pushup(int u){
siz[u]=sx[u]+1;
if(ch[u][0]){
siz[u]+=siz[ch[u][0]];
}
if(ch[u][1]){
siz[u]+=siz[ch[u][1]];
}
}
void reverse(int u){
rev[u]^=1;
swap(ch[u][0],ch[u][1]);
}
void downtag(int u){
if(rev[u]){
if(ch[u][0]){
reverse(ch[u][0]);
}
if(ch[u][1]){
reverse(ch[u][1]);
}
rev[u]=0;
}
}
void pushdown(int u){
if(!isroot(u)){
pushdown(fa[u]);
}
downtag(u);
}
void rotate(int x){
int y=fa[x],z=fa[y],md=which(x);
if(!isroot(y)){
ch[z][which(y)]=x;
}
fa[x]=z;
ch[y][md]=ch[x][!md];
fa[ch[y][md]]=y;
ch[x][!md]=y;
fa[y]=x;
pushup(y);
pushup(x);
}
void splay(int u){
pushdown(u);
while(!isroot(u)){
if(!isroot(fa[u])){
rotate(which(fa[u])==which(u)?fa[u]:u);
}
rotate(u);
}
}
void access(int u){
for(int v=0;u;v=u,u=fa[u]){
splay(u);
sx[u]+=siz[ch[u][1]]-siz[v];
ch[u][1]=v;
pushup(u);
}
}
void makeroot(int u){
access(u);
splay(u);
reverse(u);
}
void link(int u,int v){
makeroot(u);
makeroot(v);
fa[u]=v;
sx[v]+=siz[u];
pushup(v);
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=q;i++){
scanf("%s%d%d",s,&u,&v);
if(s[0]=='A'){
link(u,v);
}else{
makeroot(u);
access(v);
splay(v);
printf("%lld
",(long long)(siz[u])*(siz[v]-siz[u]));
}
}
return 0;
}
10.KMP
题目描述
Horace用积木搭了一堵墙,长度为w,Menshykov和Uslada也搭了一堵墙,长度为n。Horace能够在这堵墙的一段长度为w的部分上“see an elephant”当且仅当这段墙中每个位置的高度都和他造的墙每个位置的高度相同。为了能够更多的“see an elephant”,Horace可以把墙整体抬高或整体压低(他可以把墙压到地下)。
输入格式
第一行两个整数:n、w; 第二行n个整数:Menshykov和Uslada搭的墙的每一段的高度; 第三行w个整数:Horace搭的墙每一段的高度;
输出格式
一个整数:Horace能“see an elephant”的次数
Input
13 5
2 4 5 5 4 3 2 2 2 3 3 2 1
3 4 4 3 2
Output
2
HINT
(1 ≤ n, w ≤ 2·10^5)
(1 ≤ bi ≤ 10^9)
(1 ≤ ai ≤ 10^9)
代码:
#include<cstdio>
const int N=200005;
int n,m,ans,a[N],b[N],f[N];
int main(){
scanf("%d%d",&n,&m);
if(m==1){
printf("%d
",n);
return 0;
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d",&b[i]);
}
for(int i=1;i<n;i++){
a[i]=a[i+1]-a[i];
}
for(int i=1;i<m;i++){
b[i]=b[i+1]-b[i];
}
int j=0;
for(int i=2;i<m;i++){
while(j&&b[i]!=b[j+1]){
j=f[j];
}
if(b[i]==b[j+1]){
j++;
}
f[i]=j;
}
j=0;
for(int i=1;i<n;i++){
while(j&&a[i]!=b[j+1]){
j=f[j];
}
if(a[i]==b[j+1]){
j++;
}
if(j==m-1){
ans++;
}
}
printf("%d
",ans);
return 0;
}
11.拓展KMP
题目描述
给你两个字符串a和b,告诉所有你b在a中一定匹配的位置,求有多少种不同的字符串a。a的长度为n,b的长度为m,一定匹配的位置有p个。若b在a中的一定匹配的位置为x,说明a[x…x+m-1]=b[1…m]。a和b中只有小写字母。
输入格式
第一行两个字符串n、p;(1<=n<=100000, 0<=p<=n-m+1)
第二行有一个字符串b;(1<=m<=n)
第三行有p个数:这p个一定匹配的位置,保证这些数升序排列。
输出格式
一个数:答案。模10^9+7。
Sample Input 1
6 2
ioi
1 3
Sample Output 1
26
Sample Input 2
5 2
ioi
1 2
Sample Output 2
0
样例解释
第一个样例中a的前5个字符为”ioioi”,最后一个字符可以是任意的字符,所以答案为26.
第二个样例中a的第二个字符不可能既是i又是o。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,p,tot,a[100005],nxt[100005],sum[100005];
char s[100005];
bool flag=true;
void exkmp(){
nxt[0]=n;
int i=0,j,pos=1;
while(i+1<m&&s[i]==s[i+1]){
i++;
}
nxt[1]=i;
for(i=2;i<m;i++){
if(i+nxt[i-pos]<pos+nxt[pos]){
nxt[i]=nxt[i-pos];
}else{
int j=pos+nxt[pos]-i;
if(j<0){
j=0;
}
while(j+i<m&&s[j]==s[j+i]){
j++;
}
nxt[i]=j;
pos=i;
}
}
}
int ksm(int x,long long mod){
long long ans=1,a=26;
while(x){
if(x&1){
ans=ans*a%mod;
}
a=a*a%mod;
x>>=1;
}
return ans;
}
int main(){
scanf("%d%d%s",&n,&p,s);
m=strlen(s);
exkmp();
for(int i=1;i<=p;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=p;i++){
sum[a[i]]++;
sum[a[i]+m]--;
}
for(int i=1;i<=n;i++){
sum[i]+=sum[i-1];
}
for(int i=1;i<p;i++){
if(a[i]+m-1>=a[i+1]){
if(nxt[a[i+1]-a[i]]<m-a[i+1]+a[i]){
flag=false;
break;
}
}
}
if(!flag){
puts("0");
}else{
for(int i=1;i<=n;i++){
if(!sum[i]){
tot++;
}
}
printf("%d
",ksm(tot,1000000007));
}
return 0;
}
12.AC自动机
AC自动机简单版
题目背景
这是一道简单的AC自动机模板题。
用于检测正确性以及算法常数。
为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交。
管理员提示:本题数据内有重复的单词,且重复单词应该计算多次,请各位注意
题目描述
给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。
输入格式
第一行一个n,表示模式串个数;
下面n行每行一个模式串;
下面一行一个文本串。
输出格式
一个数表示答案
输入样例
2
a
aa
aa
输出样例
2
说明
subtask1[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6,n=1;
subtask2[50pts]:∑length(模式串)<=10^6,length(文本串)<=10^6;
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int T,n,rt,cnt,ans;
char s[1000005];
struct node{
int cnt,fail,ch[26];
}t[5000005];
void insert(char *s){
int k=rt;
while(*s!=' '){
if(!t[k].ch[*s-'a']){
t[k].ch[*s-'a']=++cnt;
t[t[k].ch[*s-'a']].cnt=0;
memset(t[t[k].ch[*s-'a']].ch,0,sizeof(t[t[k].ch[*s-'a']].ch));
}
k=t[k].ch[*s-'a'];
s++;
}
t[k].cnt++;
}
queue<int> q;
void build(){
q.push(rt);
int tmp,p;
while(!q.empty()){
tmp=q.front();
q.pop();
for(int i=0;i<26;i++){
if(tmp==rt){
p=rt;
}else{
p=t[t[tmp].fail].ch[i];
}
if(t[tmp].ch[i]){
q.push(t[tmp].ch[i]);
t[t[tmp].ch[i]].fail=p;
}else{
t[tmp].ch[i]=p;
}
}
}
}
void ac_automation(char *s){
int p=rt;
while(*s!=' '){
int x=*s-'a';
p=t[p].ch[x];
int tmp=p;
while(tmp!=rt&&t[tmp].cnt>=0){
ans+=t[tmp].cnt;
t[tmp].cnt=-1;
tmp=t[tmp].fail;
}
s++;
}
}
int main(){
T=1;
for(int i=1;i<=T;i++){
rt=cnt=1;
t[rt].cnt=0;
memset(t[rt].ch,0,sizeof(t[rt].ch));
scanf("%d",&n);
for(int j=1;j<=n;j++){
scanf("%s",s);
insert(s);
}
scanf("%s",s);
ans=0;
build();
ac_automation(s);
printf("%d
",ans);
}
return 0;
}
AC自动机加强版
题目描述
有N个由小写字母组成的模式串以及一个文本串T。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串T中出现的次数最多。
输入格式
输入含多组数据。
每组数据的第一行为一个正整数N,表示共有N个模式串,1≤N≤150 。
接下去N行,每行一个长度小于等于70的模式串。下一行是一个长度小于等于10^6的文本串T。
输入结束标志为N=0。
输出格式
对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。
输入样例
2
aba
bab
ababababac
6
beta
alpha
haha
delta
dede
tata
dedeltalphahahahototatalpha
0
输出样例
4
aba
2
alpha
haha
代码
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int n,rt,cnt,tot[1000005];
char s[1000005],ss[150][100];
struct node{
int val,fail,last,ch[26];
}t[11000];
void insert(char *s,int id){
int k=rt;
while(*s!=' '){
if(!t[k].ch[*s-'a']){
t[k].ch[*s-'a']=++cnt;
t[t[k].ch[*s-'a']].val=t[t[k].ch[*s-'a']].last=0;
memset(t[t[k].ch[*s-'a']].ch,0,sizeof(t[t[k].ch[*s-'a']].ch));
}
k=t[k].ch[*s-'a'];
s++;
}
t[k].val=id;
}
queue<int> q;
void build(){
q.push(rt);
int tmp,p;
while(!q.empty()){
tmp=q.front();
q.pop();
for(int i=0;i<26;i++){
if(tmp==rt){
p=rt;
}else{
p=t[t[tmp].fail].ch[i];
}
if(t[tmp].ch[i]){
q.push(t[tmp].ch[i]);
t[t[tmp].ch[i]].fail=p;
t[t[tmp].ch[i]].last=t[p].val?p:t[p].last;
}else{
t[tmp].ch[i]=p;
}
}
}
}
void ac_automation(char *s){
int p=rt;
while(*s!=' '){
int x=*s-'a';
p=t[p].ch[x];
if(t[p].val){
tot[t[p].val]++;
}
int tmp=t[p].last;
while(tmp){
if(t[tmp].val){
tot[t[tmp].val]++;
}
tmp=t[tmp].last;
}
s++;
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,tot[i]);
}
printf("%d
",ans);
for(int i=1;i<=n;i++){
if(tot[i]==ans){
puts(ss[i]);
}
}
}
int main(){
while(scanf("%d",&n)&&n){
rt=cnt=1;
t[rt].val=t[rt].last=0;
memset(t[rt].ch,0,sizeof(t[rt].ch));
memset(tot,0,sizeof(tot));
for(int i=1;i<=n;i++){
scanf("%s",ss[i]);
insert(ss[i],i);
}
scanf("%s",s);
build();
ac_automation(s);
}
return 0;
}
13.Manacher
最长回文串
Description
给一个字符串,输出它的最长回文子串长度。
Input
一行一个字符串。
Output
一行一个整数表示长度。
Sample Input
abbabbaabbabaab
Sample Output
10
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int len,n,pos,maxr,ans,p[22000005];
char str[22000005],s[22000005];
int main(){
while(~scanf("%s",str)){
len=strlen(str);
n=0;
s[0]='#';
for(int i=0;i<len;i++){
s[++n]=str[i];
s[++n]='#';
}
p[0]=1;
ans=maxr=pos=0;
for(int i=1;i<=n;i++){
p[i]=i<maxr?min(p[2*pos-i],maxr-i):1;
while(i-p[i]>=0&&i+p[i]<=n&&s[i-p[i]]==s[i+p[i]]){
p[i]++;
}
if(i+p[i]>maxr){
pos=i;
maxr=i+p[i];
}
ans=max(ans,p[i]-1);
}
printf("%d
",ans);
}
return 0;
}
Interesting
Description
Alice得到了一个字符串S。她觉得回文子串十分的interesting。现在他想要知道有多少个三元组(i,j,k)满足1<=i<=k<=length(S),S[i..j]和S[j+1..k]都是回文子串。她觉得这个是一个很简单的问题。于是她开始想知道把所有满足要求的三元组中的(i,j,k)把i*k全部加起来的答案是多少。但是她又不能解决这个问题了,于是她开始向你寻求帮助。所以你能帮助她吗?这个ans可能会十分的大,请把答案对1000000007取模。
Input
输入只包含一个字符串。字符串只包含小写字母。
Output
输出答案对1000000007取模的值。
Sample Input
aaa
Sample Output
14
HINT
设字符串长度为N,1≤N≤1000000
【样例解释】
所有的三元组为(1, 1, 2), (1, 1, 3), (1, 2, 3), (2, 2, 3),因此它们i*k的和为14。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int len,n,p[2000005];
char str[1000005],s[2000005];
long long ans,s1[2000005],s2[2000005],s3[2000005],s4[2000005];
const long long mod=1000000007;
int main(){
while(~scanf("%s",str+1)){
len=strlen(str+1);
for(int i=1;i<=len;i++){
s1[i]=s2[i]=s3[i]=s4[i]=0;
s[2*i-1]='#';
s[2*i]=str[i];
}
s[n=2*len+1]='#';
int pos=0,maxr=0;
for(int i=1;i<=n;i++){
p[i]=i<maxr?min(p[2*pos-i],maxr-i):1;
while(i-p[i]>=1&&i+p[i]<=n&&s[i-p[i]]==s[i+p[i]]){
p[i]++;
}
if(i+p[i]>maxr){
maxr=i+p[i];
pos=i;
}
if(s[i]=='#'&&p[i]>1){
s1[(i+1)/2]+=i;
s1[(i+p[i]-2)/2+1]-=i;
s2[(i+1)/2]++;
s2[(i+p[i]-2)/2+1]--;
s3[(i-p[i]+2)/2]+=i;
s3[(i-1)/2+1]-=i;
s4[(i-p[i]+2)/2]++;
s4[(i-1)/2+1]--;
}else if(s[i]!='#'){
s1[i/2]+=i;
s1[(i+p[i]-2)/2+1]-=i;
s2[i/2]++;
s2[(i+p[i]-2)/2+1]--;
s3[(i-p[i]+2)/2]+=i;
s3[i/2+1]-=i;
s4[(i-p[i]+2)/2]++;
s4[i/2+1]--;
}
}
for(int i=1;i<=len;i++){
s1[i]+=s1[i-1];
s2[i]+=s2[i-1];
s3[i]+=s3[i-1];
s4[i]+=s4[i-1];
}
for(int i=1;i<=len;i++){
s1[i]-=s2[i]*i;
s3[i]-=s4[i]*i;
s1[i]%=mod;
s3[i]%=mod;
}
ans=0;
for(int i=1;i<len;i++){
ans+=s1[i]*s3[i+1]%mod;
ans%=mod;
}
printf("%lld
",ans);
}
return 0;
}
14.回文自动机
bzoj3676 [Apio2014]回文串
Description
考虑一个只包含小写拉丁字母的字符串s。我们定义s的一个子串t的“出
现值”为t在s中的出现次数乘以t的长度。请你求出s的所有回文子串中的最
大出现值。
Input
输入只有一行,为一个只包含小写字母(a -z)的非空字符串s。
Output
输出一个整数,为逝查回文子串的最大出现值。
Sample Input
【样例输入l】
abacaba
【样例输入2]
www
Sample Output
【样例输出l】
7
【样例输出2]
4
HINT
一个串是回文的,当且仅当它从左到右读和从右到左读完全一样。
在第一个样例中,回文子串有7个:a,b,c,aba,aca,bacab,abacaba,最大回文子串出现值为7。
【数据规模与评分】
数据满足1≤字符串长度≤300000。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300005;
int n;
char s[N];
long long ans;
struct Pam{
int tot,n,last,now,tmp,s[N],ch[N][26],fail[N],cnt[N],len[N];
int newnode(int l){
memset(ch[tot],0,sizeof(ch[tot]));
cnt[tot]=0;
len[tot]=l;
return tot++;
}
void init(){
tot=0;
n=last=now=0;
s[0]=-1;
newnode(0);
newnode(-1);
fail[0]=1;
}
int getfail(int x){
while(s[n-len[x]-1]!=s[n]){
x=fail[x];
}
return x;
}
void insert(char c){
int x=c-'a';
s[++n]=x;
tmp=getfail(last);
if(!ch[tmp][x]){
now=newnode(len[tmp]+2);
fail[now]=ch[getfail(fail[tmp])][x];
ch[tmp][x]=now;
}
last=ch[tmp][x];
cnt[last]++;
}
void calc(){
for(int i=tot-1;i>=0;i--){
cnt[fail[i]]+=cnt[i];
ans=max(ans,1LL*cnt[i]*len[i]);
}
}
}pam;
int main(){
pam.init();
scanf("%s",s);
n=strlen(s);
for(int i=0;i<n;i++){
pam.insert(s[i]);
}
pam.calc();
printf("%lld
",ans);
return 0;
}
15.后缀数组
代码
inline bool cmp(int *y,int p,int q,int k){
int a=p+k>=n?-1:y[p+k];
int b=q+k>=n?-1:y[q+k];
return a==b&&y[p]==y[q];
}
void build_sa(){
int *x=t1,*y=t2;
for(int i=0;i<m;i++){
c[i]=0;
}
for(int i=0;i<n;i++){
c[x[i]=s[i]]++;
}
for(int i=1;i<m;i++){
c[i]+=c[i-1];
}
for(int i=n-1;i>=0;i--){
sa[--c[x[i]]]=i;
}
for(int k=1;k<=n;k<<=1){
int p=0;
for(int i=n-k;i<n;i++){
y[p++]=i;
}
for(int i=0;i<n;i++){
if(sa[i]>=k){
y[p++]=sa[i]-k;
}
}
for(int i=0;i<m;i++){
c[i]=0;
}
for(int i=0;i<n;i++){
c[x[y[i]]]++;
}
for(int i=1;i<m;i++){
c[i]+=c[i-1];
}
for(int i=n-1;i>=0;i--){
sa[--c[x[y[i]]]]=y[i];
}
swap(x,y);
x[sa[0]]=0;
p=1;
for(int i=1;i<n;i++){
x[sa[i]]=cmp(y,sa[i],sa[i-1],k)?p-1:p++;
}
if(p>=n){
break;
}
m=p;
}
for(int i=0;i<n;i++){
rnk[sa[i]]=i;
}
for(int i=0,j,k=0;i<n;i++){
if(k){
k--;
}
if(!rnk[i]){
continue;
}
j=sa[rnk[i]-1];
while(s[i+k]==s[j+k]){
k++;
}
h[rnk[i]]=k;
}
}
16.后缀自动机
代码
struct suffix_automation{
int last,cnt,ch[N*2][26],fa[N*2],len[N*2],siz[N*2],a[N*2],c[N*2];
suffix_automation(){
last=cnt=1;
}
void insert(int x){
int p=last,np=++cnt;
last=np;
len[np]=len[p]+1;
for(;p&&!ch[p][x];p=fa[p]){
ch[p][x]=np;
}
if(!p){
fa[np]=1;
}else{
int q=ch[p][x];
if(len[q]==len[p]+1){
fa[np]=q;
}else{
int nq=++cnt;
len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p]){
ch[p][x]=nq;
}
}
}
siz[np]=1;
}
void solve(){
for(int i=1;i<=cnt;i++){
c[len[i]]++;
}
for(int i=1;i<=n;i++){
c[i]+=c[i-1];
}
for(int i=1;i<=cnt;i++){
a[c[len[i]]--]=i;
}
for(int i=cnt;i>=1;i--){
siz[fa[a[i]]]+=siz[a[i]];
}
}
}sam;
17.最大流
题目描述
如题,给出一个网络图,以及其源点和汇点,求出其网络最大流。
输入格式
第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。
接下来M行每行包含三个正整数ui、vi、wi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi)
输出格式
一行,包含一个正整数,即为该网络的最大流。
输入样例
4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40
输出样例
50
数据规模
对于30%的数据:N<=10,M<=25
对于70%的数据:N<=200,M<=1000
对于100%的数据:N<=10000,M<=100000
代码
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N=10005,M=100005;
int n,m,s,t,u,v,d,cnt,head[N],work[N],to[M*2],nxt[M*2],dd[M*2];
void adde(int u,int v,int d){
to[++cnt]=v;
nxt[cnt]=head[u];
dd[cnt]=d;
head[u]=cnt;
to[++cnt]=u;
nxt[cnt]=head[v];
dd[cnt]=0;
head[v]=cnt;
}
queue<int> q;
int dep[N];
bool vis[N];
bool bfs(){
memset(vis,0,sizeof(vis));
q.push(s);
vis[s]=true;
dep[s]=0;
while(!q.empty()){
int u=q.front(),v;
q.pop();
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!vis[v]&&dd[i]){
dep[v]=dep[u]+1;
vis[v]=true;
q.push(v);
}
}
}
return vis[t];
}
int dfs(int u,int f){
if(!f||u==t){
return f;
}
int v,tmp,flow=0;
for(int &i=work[u];i&&f;i=nxt[i]){
v=to[i];
if(dep[v]==dep[u]+1&&(tmp=dfs(v,min(f,dd[i])))){
dd[i]-=tmp;
dd[i^1]+=tmp;
f-=tmp;
flow+=tmp;
}
}
return flow;
}
int dinic(){
int maxn=0;
while(bfs()){
memcpy(work,head,sizeof(head));
maxn+=dfs(s,0x7fffffff);
}
return maxn;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
cnt=1;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&d);
adde(u,v,d);
}
printf("%d
",dinic());
return 0;
}
18.二分图匹配
题目描述
给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数
输入格式
第一行,n,m,e
第二至e+1行,每行两个正整数u,v,表示u,v有一条连边
输出格式
共一行,二分图最大匹配
输入样例
1 1 1
1 1
输出样例
1
说明
因为数据有坑,可能会遇到 的情况。请把的数据自行过滤掉。
代码
#include<cstdio>
#include<cstring>
const int N=1000005;
int n,m,e,u,v,ans,cnt,link[N],head[N],to[N],nxt[N];
bool vis[N];
void adde(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
bool check(int u){
int v;
for(int i=head[u];i;i=nxt[i]){
v=to[i];
if(!vis[v]){
vis[v]=true;
if(!link[v]||check(link[v])){
link[v]=u;
return true;
}
}
}
return false;
}
int main(){
scanf("%d%d%d",&n,&m,&e);
for(int i=1;i<=e;i++){
scanf("%d%d",&u,&v);
if(u>n||v>m){
continue;
}
adde(u,v);
}
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(check(i)){
ans++;
}
}
printf("%d
",ans);
return 0;
}
19.RMQ
代码
void st(){
for(int i=1;i<=n;i++){
f[i][0]=h[i];
}
for(int j=1;j<=20;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
int query(int l,int r){
int k=lg2[r-l+1];
return min(f[l][k],f[r-(1<<k)+1][k]);
}
20.多项式乘法
题目描述
给定一个n次多项式F(x),和一个m次多项式G(x)。
请求出F(x)和G(x)的卷积。
输入格式
第一行2个正整数n,m。
接下来一行n+1个数字,从低到高表示F(x)的系数。
接下来一行m+1个数字,从低到高表示G(x))的系数。
输出格式
一行n+m+1个数字,从低到高表示F(x)∗G(x)的系数。
输入样例
1 2
1 2
1 2 1
输出样例
1 4 5 2
说明
保证输入中的系数大于等于 0 且小于等于9。
对于100%的数据: n,m≤10^6 , 共计20个数据点,2s。
FFT代码
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2100005;
const double pi=3.141592653589793;
int n,m,rev[N];
struct complex{
double x,y;
complex(){}
complex(double x,double y):x(x),y(y){}
friend complex operator + (const complex &a,const complex &b){
return complex(a.x+b.x,a.y+b.y);
}
friend complex operator - (const complex &a,const complex &b){
return complex(a.x-b.x,a.y-b.y);
}
friend complex operator * (const complex &a,const complex &b){
return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
}a[N],b[N];
void fft(complex *a,int md){
for(int i=0;i<=n;i++){
if(i<rev[i]){
swap(a[i],a[rev[i]]);
}
}
for(int i=1;i<n;i<<=1){
complex wn=complex(cos(pi/i),md*sin(pi/i));
for(int j=0;j<n;j+=(i<<1)){
complex w=complex(1,0);
for(int k=j;k<j+i;k++,w=w*wn){
complex x=a[k];
complex y=w*a[k+i];
a[k]=x+y;
a[k+i]=x-y;
}
}
}
if(md==-1){
for(int i=0;i<=n;i++){
a[i].x/=n;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++){
scanf("%lf",&a[i].x);
}
for(int i=0;i<=m;i++){
scanf("%lf",&b[i].x);
}
m+=n;
for(n=1;n<=m;n<<=1);
for(int i=0;i<=n;i++){
rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
}
fft(a,1);
fft(b,1);
for(int i=0;i<=n;i++){
a[i]=a[i]*b[i];
}
fft(a,-1);
for(int i=0;i<=m;i++){
printf("%d ",int(a[i].x+0.5));
}
puts("");
return 0;
}
NTT代码
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2100005;
const ll mod=998244353;
int n,m,rev[N];
ll a[N],b[N];
ll fastpow(ll x,ll y){
ll res=1;
while(y){
if(y&1){
res*=x;
res%=mod;
}
y>>=1;
x*=x;
x%=mod;
}
return res;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
if(!b){
x=1;
y=0;
return a;
}
ll d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
ll x,y;
ll getinv(ll a,ll mod){
exgcd(a,mod,x,y);
x=(x%mod+mod)%mod;
return x;
}
void ntt(ll *a,int dft){
for(int i=0;i<n;i++){
if(i<rev[i]){
swap(a[i],a[rev[i]]);
}
}
for(int i=1;i<n;i<<=1){
ll wn=fastpow(3,(mod-1)/i/2);
if(dft==-1){
wn=getinv(wn,mod);
}
for(int j=0;j<n;j+=i<<1){
ll w=1,x,y;
for(int k=j;k<j+i;k++,w=w*wn%mod){
x=a[k];
y=w*a[k+i]%mod;
a[k]=x+y;
a[k]=a[k]>=mod?a[k]-mod:a[k];
a[k+i]=x-y;
a[k+i]=a[k+i]<0?a[k+i]+mod:a[k+i];
}
}
}
if(dft==-1){
ll inv=getinv(n,mod);
for(int i=0;i<n;i++){
a[i]=a[i]*inv%mod;
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=0;i<=m;i++){
scanf("%lld",&b[i]);
}
m=n+m+1;
for(n=1;n<=m;n<<=1);
for(int i=0;i<n;i++){
rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
}
ntt(a,1);
ntt(b,1);
for(int i=0;i<n;i++){
a[i]=a[i]*b[i]%mod;
}
ntt(a,-1);
for(int i=0;i<m;i++){
printf("%lld ",a[i]);
}
puts("");
return 0;
}
NTT模数
1. 原根:3
2. 原根:3
3. 原根:3
4. 原根:3
5. 原根:26
中国剩余定理
long long O(1)快速乘法以及任意模数NTT的CRT
typedef long long ll;
ll mul(ll a,ll b,ll mod){
ll d=(ll)floor(a*(double)b/mod+0.5);
ll res=a*b-d*mod;
if(res<0){
res+=mod;
}
return res;
}
ll CRT(ll a1,ll a2,ll a3,ll m1,ll m2,ll m3){
ll M=m1*m2;
ll inv1=getinv(m1,m2),inv2=getinv(m2,m1),inv12=getinv(M,m3);
ll A=(mul(a1*m2%M,inv2,M)+mul(a2*m1%M,inv1,M))%M;
ll k=((ll)a3+m3-A%m3)*inv12%m3;
return (k*(M%mod)+A)%mod;
}
丢个新写的板子。
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=270005;
int n,m,rev[N];
ll p;/*
void op(__int128 a){
if(a==0){
return;
}
op(a/10);
printf("%d",(int)a%10);
}*/
ll fastpow(ll a,ll x,ll mod){
ll res=1;
a%=mod;
while(x){
if(x&1){
res=res*a%mod;
}
x>>=1;
a=a*a%mod;
}
return res;
}
ll getinv(ll a,ll mod){
return fastpow(a,mod-2,mod);
}
struct NTT{
ll mod,a[N],b[N];
}a[3];
void ntt(ll *a,ll mod,int dft){
for(int i=0;i<n;i++){
if(i<rev[i]){
swap(a[i],a[rev[i]]);
}
}
for(int i=1;i<n;i<<=1){
ll wn=fastpow(3,(mod-1)/i/2,mod);
if(dft==-1){
wn=getinv(wn,mod);
}
for(int j=0;j<n;j+=i<<1){
ll w=1,x,y;
for(int k=j;k<j+i;k++,w=w*wn%mod){
x=a[k];
y=w*a[k+i]%mod;
a[k]=(x+y)%mod;
a[k+i]=(x-y+mod)%mod;
}
}
}
if(dft==-1){
ll inv=getinv(n,mod);
for(int i=0;i<n;i++){
a[i]=a[i]*inv%mod;
}
}
}
void fft(NTT &a){
ntt(a.a,a.mod,1);
ntt(a.b,a.mod,1);
for(int i=0;i<n;i++){
a.a[i]=a.a[i]*a.b[i]%a.mod;
}
ntt(a.a,a.mod,-1);
}
ll mul(ll a,ll b,ll mod){
ll c=(ll)(a*(long double)b/mod+0.5);
ll res=a*b-c*mod;
if(res<0){
res+=mod;
}
return res;
}
int main(){
a[0].mod=998244353;
a[1].mod=1004535809;
a[2].mod=1998585857;
scanf("%d%d%lld",&n,&m,&p);
for(int i=0;i<=n;i++){
scanf("%lld",&a[0].a[i]);
a[1].a[i]=a[2].a[i]=a[0].a[i];
}
for(int i=0;i<=m;i++){
scanf("%lld",&a[0].b[i]);
a[1].b[i]=a[2].b[i]=a[0].b[i];
}
m=n+m;
for(n=1;n<=m;n<<=1);
for(int i=0;i<n;i++){
rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
}
fft(a[0]);
fft(a[1]);
fft(a[2]);/*
__int128 tmp,m0=a[0].mod,m1=a[1].mod,m2=a[2].mod,m3=m0*m1*m2;
__int128 p0=m1*m2,p1=m0*m2,p2=m0*m1;
__int128 i0=getinv(p0%m0,m0),i1=getinv(p1%m1,m1),i2=getinv(p2%m2,m2);
for(int i=0;i<=m;i++){
tmp=0;
tmp+=p0*i0%m3*a[0].a[i]%m3;
tmp%=m3;
tmp+=p1*i1%m3*a[1].a[i]%m3;
tmp%=m3;
tmp+=p2*i2%m3*a[2].a[i]%m3;
tmp%=m3;
a[0].a[i]=tmp%p;
printf("%lld ",a[0].a[i]);
}*///水法,使用__int128,可能会CE。
ll M=a[0].mod*a[1].mod,inv1=getinv(a[1].mod,a[0].mod),inv2=getinv(a[0].mod,a[1].mod);
ll inv12=getinv(M,a[2].mod),tmp,k;
for(int i=0;i<=m;i++){
tmp=0;
tmp+=mul(a[0].a[i]*a[1].mod,inv1,M);
tmp%=M;
tmp+=mul(a[1].a[i]*a[0].mod,inv2,M);
tmp%=M;
k=mul(a[2].a[i]-tmp%a[2].mod+a[2].mod,inv12,a[2].mod);
printf("%lld ",(M%p*k+tmp)%p);
}
return 0;
}
多项式求逆
代码
void inverse(ll *a,ll *b,ll *c,ll *d,ll n){
b[0]=getinv(a[0]);
for(int k=2;k<=n;k<<=1){
for(register int i=0;i<(k<<1);i++){
if(i<k){
c[i]=a[i];
}else{
c[i]=0;
}
if(i<(k>>1)){
d[i]=b[i];
}else{
d[i]=0;
}
}
ntt(c,k<<1,1);
ntt(d,k<<1,1);
for(register int i=0;i<(k<<1);i++){
c[i]=(2*d[i]%mod-d[i]*d[i]%mod*c[i]%mod+mod)%mod;
}
ntt(c,k<<1,-1);
for(register int i=0;i<k;i++){
b[i]=c[i];
}
}
}
多项式开根
最后这一步可以减少进行DFT和IDFT的次数,以免被卡常数。
代码
void sqrt(ll *a,ll *b,ll *c,ll *d,ll *e,ll *f){
b[0]=1;
for(int k=1;k<=n;k<<=1){
for(register int i=0;i<k;i++){
c[i]=2*b[i]%mod;
}
inverse(c,d,e,f,k);
for(register int i=0;i<(k<<1);i++){
if(i<k){
c[i]=a[i];
}else{
c[i]=d[i]=0;
}
}
ntt(c,k<<1,1);
ntt(d,k<<1,1);
for(int i=0;i<(k<<1);i++){
c[i]=c[i]*d[i]%mod;
}
ntt(c,k<<1,-1);
for(register int i=0;i<(k<<1);i++){
b[i]=(b[i]*499122177+c[i])%mod;
}
}
}
21.虚树
代码
void solve(){
stk[stk[0]=1]=1;
for(int i=1;i<=k;i++){
int tmp=lca(stk[stk[0]],p[i]);
if(tmp!=stk[stk[0]]){
while(stk[0]>1){
if(dfn[stk[stk[0]-1]]>dfn[tmp]){
adde(stk[stk[0]-1],stk[stk[0]]);
stk[0]--;
}else if(stk[stk[0]-1]==tmp){
adde(tmp,stk[stk[0]]);
stk[0]--;
break;
}else if(dfn[stk[stk[0]-1]]<dfn[tmp]){
adde(tmp,stk[stk[0]]);
stk[stk[0]]=tmp;
break;
}
}
}
if(stk[stk[0]]!=p[i]){
stk[++stk[0]]=p[i];
}
}
while(stk[0]>1){
adde(stk[stk[0]-1],stk[stk[0]]);
stk[0]--;
}
}
22.斯特林数
一堆公式。
第一类
第二类
斯特林公式
对数运算法则
1.
2.
3.
4.