小tips
CF头
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
#define ll long long
#define ull unsigned long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define mkp make_pair
#define ft first
#define sd second
#define log(x) (31-__builtin_clz(x))
#define INF 0x3f3f3f3f
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff
集合操作
//并交集
vector<int> ANS;
set_union(s1.begin(),s1.end(),s2.begin(),s2.end(),inserter(ANS,ANS.begin()));//set_intersection()
//通过迭代器遍历集合
set<char>::iterator iter = temp1.begin();
while (iter!=temp1.end()){
cout<<*iter;
iter++;
}
快读快写
template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
template<class T>
void wt(T x){//快写
if(x < 0) putchar('-'), x = -x;
if(x >= 10) wt(x / 10);
putchar('0' + x % 10);
}
__int128
inline __int128 read(){
__int128 x=0,f=1;
char ch=getchar();
while(ch<‘0‘||ch>‘9‘){
if(ch==‘-‘)
f=-1;
ch=getchar();
}
while(ch>=‘0‘&&ch<=‘9‘){
x=x*10+ch-‘0‘;
ch=getchar();
}
return x*f;
}
inline void print(__int128 x){
if(x<0){
putchar(‘-‘);
x=-x;
}
if(x>9)
print(x/10);
putchar(x%10+‘0‘);
}
数据结构
单调队列
普通单调队列
struct node{//结构体,存储数值和位置
int data;
int order;//原本数列里的位置
};
const int MAXN = 1e6+5;
deque<node>dq_min;
deque<node>dq_max;
int n,k;
int a[MAXN];
int res_min[MAXN];
int res_max[MAXN];
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
//如果队列头超过这k的范围,则出队
if(!dq_max.empty()&&dq_max.front().order<i-k+1) dq_max.pop_front();
if(!dq_min.empty()&&dq_min.front().order<i-k+1) dq_min.pop_front();
//新元素从队尾插进来,队尾没用的元素在这里出队
while(!dq_max.empty()&&dq_max.back().data<=a[i]) dq_max.pop_back();
dq_max.push_back(node{a[i],i});
while(!dq_min.empty()&&dq_min.back().data>=a[i]) dq_min.pop_back();
dq_min.push_back(node{a[i],i});
//存储区间最大最小值
res_max[i] = dq_max.front().data;
res_min[i] = dq_min.front().data;
}
for(int i=k;i<=n;i++){
cout<<res_min[i];
if(i!=n) cout<<' ';
}
cout<<endl;
for(int i=k;i<=n;i++){
cout<<res_max[i];
if(i!=n) cout<<' ';
}
cout<<endl;
}
//洛谷 P1886 滑动窗口 /【模板】单调队列 https://www.luogu.com.cn/problem/P1886
矩阵上的单调队列
deque<node> dq;//每行进行单调队列
//本来想建maxn个deque的,但是这里发现可以滚动
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(!dq.empty()&&dq.front().order<j-k+1) dq.pop_front();
while(!dq.empty()&&a[i][j]>=dq.back().data) dq.pop_back();
dq.push_back(node{a[i][j],j});
res[i][j] = dq.front().data;
}
dq.clear();
}
//对k开始的每列进行单调队列
for(int j=k;j<=m;j++){
for(int i=1;i<=n;i++){
if(!dq.empty()&&dq.front().order<i-k+1) dq.pop_front();
while(!dq.empty()&&dq.back().data<=res[i][j]) dq.pop_back();
dq.push_back(node{res[i][j],i});
res[i][j] = dq.front().data;
}
dq.clear();
}
ll msum = 0;
for(int i=k;i<=n;i++){
for(int j=k;j<=m;j++){
msum+=res[i][j];
}
}
cout<<msum<<endl;
主席树
#define mid ((l+r)>>1)
const int MAXN = 2e5+5;
int n,q,m,cnt=0;
int a[MAXN],b[MAXN];
int T[MAXN];//记录每个时间上树的根节点
int sum[MAXN<<5];//记录节点覆盖区间内数字的数量
int L[MAXN<<5],R[MAXN<<5];//记录左右儿子
int build(int l,int r){//一开始通过递归建树
int pos = ++cnt;//建立新的节点,节点数cnt自增
sum[pos]=0;//时间0时是没有任何数字的
if(l<r){
L[pos]=build(l,mid);
R[pos]=build(mid+1,r);
}
return pos;
}
int update(int pre,int l,int r,int x){//pre是在pos位置上一个时间的节点编号
int pos = ++cnt;
L[pos]=L[pre];R[pos]=R[pre];//pre是上个时间点在如今pos位置上的节点
//这里相当于是把这个新的节点接到相应的位置
sum[pos] = sum[pre]+1;//这一整条链上各点的权值都+1(叶子上加了1,上面的都受影响)
if(l<r){//找到长度为一个数的节点就结束递归了
if(x<=mid) L[pos] = update(L[pre],l,mid,x);
else R[pos] = update(R[pre],mid+1,r,x);
//判断数值x的这个节点在当前节点的左儿子还是右儿子
//这样一直沿着这条链往下,建立新的节点。
//因为pos节点的儿子中新建立的节点需要返回编号
}
return pos;
}
int query(int u,int v,int l,int r,int k){
if(l>=r) return l;
int x = sum[L[v]]-sum[L[u]];//计算左子树中数的数量x
if(x>=k) return query(L[u],L[v],l,mid,k);//x比k大,说明第k大的数在左子树中
else return query(R[u],R[v],mid+1,r,k-x);
}
int main(){
cin>>n>>q;
rep(i,1,n){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);
m = unique(b+1,b+1+n)-b-1;//排序后可以用unique函数去重
T[0] = build(1,m);
rep(i,1,n){
int t = lower_bound(b+1,b+1+m,a[i]) - b;//找到对应的离散化后的数值
T[i] = update(T[i-1],1,m,t);//建立新时间的根节点,并且建新的链并接上去
}
while(q--){
int u,v,k;
cin>>u>>v>>k;//找u到v区间内第k大的数
int t = query(T[u-1],T[v],1,m,k);
cout<<b[t]<<endl;//输出对应的原值
}
}
//洛谷P3834 【模板】可持久化线段树 2(主席树)
//https://www.luogu.com.cn/problem/P3834
//其实就是拿的大爹板子然后自己加了很多注释:
//https://www.luogu.com.cn/blog/bestFy0731/solution-p3834
LCA
#define log(x) (31-__builtin_clz(x))//谢谢hjt
constexpr int LOGN = log(MAXN)/log(2)+5;
int z,m,n;
int fa[MAXN],p[MAXN][LOGN];
int L[MAXN];//记录深度
int lg[MAXN];
vector<int> e[MAXN];
void init_lg(){//预处理lg,给之后计算省时间
lg[1]=0;
for(int i=2;i<MAXN;i++) lg[i]=lg[i-1]+( (1<<(lg[i-1]+1))==i );
}
void dfs(int x){
for(auto u:e[x]){
L[u]=L[x]+1;
dfs(u);
}
}
void preprocess(int n){
for(int i=1;i<=n;i++)
for(int j=0;1<<j<=n;j++) p[i][j]=-1;//有些倍增后出界的,用-1标记
for(int i=1;i<=n;i++)
p[i][0]=fa[i];//倍增长度为1的时候指向格子的父节点
for(int j=1;1<<j<=n;j++)
for(int i=1;i<=n;i++)
if(p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1];//两端长度加起来正好是两倍即次数+1
}
int LCA(int u,int v){
if(L[u]<L[v]) swap(u,v);//u成为离根节点更远的
int log = lg[L[u]];//找到L[u]的二进制最高位
for(int i=log;i>=0;i--)
if(L[u]-(1<<i)>=L[v]) u=p[u][i];//u往上爬到与v同高
if(u==v) return u;
for(int i=log;i>=0;i--)
if(p[u][i]!=-1&&p[u][i]!=p[v][i]){//找公共祖先,逼近它但是又不超过它
u=p[u][i]; v=p[v][i];
}
return fa[u];
}
int main(){
init_lg();//预处理log2x,给之后的计算省时间
int z,q,u,v;
string aa,bb;
cin>>z;
while(z--){
map<string,int>mp;
cin>>n>>q;
if(n==1){
while(q--){
cin>>aa>>bb;cout<<0<<endl;
}
continue;
}
int countt = 0;
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<n;i++){
cin>>aa>>bb;
int &a = mp[aa];int &b = mp[bb];
if(!a) a=++countt;
if(!b) b=++countt;
fa[a]=b;
e[b].push_back(a);
}
int rt;
for(int i=1;i<=n;i++){
if(fa[i]==i) {
rt=i;break;
}
}
L[rt]=1;
dfs(rt);
preprocess(n);
int res;
while(q--){
cin>>aa>>bb;
int &a = mp[aa];int &b = mp[bb];
int lc = LCA(a,b);
res = L[a]-L[lc];
if(lc!=b) res++;
cout<<res<<endl;
}
for(int i=1;i<=n;i++) e[i].clear();
}
}
//HDOJ4547 http://acm.hdu.edu.cn/showproblem.php?pid=4547
树链剖分
ll ans;
int da[MAXN];//记录dfn序上的各点数值用来初始化线段树
int n,m,rt,p;//节点数,询问数,根,模数
struct tree{
int sum;
int lazy;
};
struct St{//线段树
tree t[MAXN<<2];
void pushup(int pos){
t[pos].sum=(t[pos<<1].sum+t[pos<<1|1].sum) %p;
return;
}
void pushdown(int l,int r,int pos){
if(!t[pos].lazy) return;
int mid = (l+r)>>1;
t[pos<<1].sum += t[pos].lazy*(mid-l+1);
t[pos<<1].sum %= p;//取模
t[pos<<1|1].sum += t[pos].lazy*(r-(mid+1)+1);
t[pos<<1|1].sum %= p;//取模
t[pos<<1].lazy += t[pos].lazy;
t[pos<<1].lazy %= p;//取模
t[pos<<1|1].lazy += t[pos].lazy;
t[pos<<1|1].lazy %= p;//取模
t[pos].lazy = 0;
}
void build(int l,int r,int pos){
t[pos].sum = t[pos].lazy = 0;
if(l==r){
t[pos].sum = da[l];
return;
}
int mid = (l+r)>>1;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
pushup(pos);
}
void update(int L,int R,int l,int r,int pos,int v){
if(L<=l&&r<=R){
t[pos].sum += v*(r-l+1);
t[pos].lazy += v;
t[pos].lazy %= p;//取模
return;
}
if(r<L||l>R) return;
pushdown(l,r,pos);
int mid = (l+r)>>1;
update(L,R,l,mid,pos<<1,v);
update(L,R,mid+1,r,pos<<1|1,v);
pushup(pos);
}
void query(int L,int R,int l,int r,int pos){
if(L<=l&&r<=R){
ans += t[pos].sum;
ans%=p;//取模
return;
}
if(r<L||R<l) return;
pushdown(l,r,pos);
int mid = (l+r)>>1;
query(L,R,l,mid,pos<<1);
query(L,R,mid+1,r,pos<<1|1);
return;
}
//查询和修改,为了简化参数,我又写了两个
ll tquery(int L,int R){
ans = 0;
query(L,R,1,n,1);
return ans;
}
void tupdate(int L,int R,int v){
update(L,R,1,n,1,v);
}
};
//树结构
vector<int> e[MAXN];//记录边
int a[MAXN];//记录编号对应节点的初始数值
//树剖部分
St segt;
int si[MAXN],dep[MAXN],fa[MAXN],rem[MAXN],dfn[MAXN],top[MAXN];
int dfn_num;
void dfs1(int x,int faa){//预处理出fa,dep,si,rem
int ma = 0;//用来x的重儿子,记录最大的size
si[x] = 1;
for(auto v:e[x]){
if(v==faa) continue;//跳过父亲节点
dep[v] = dep[x]+1;//更新儿子的dep
dfs1(v,x);
si[x] += si[v];//x的size加上当前儿子的size
fa[v] = x;//标记v的父节点为x
if(si[v]>ma){
ma = si[v];
rem[x] = v;//记录重儿子
}
}
}
void dfs2(int x,int faa){//预处理出dfn,top
if(rem[faa]==x) top[x] = top[faa];//同一条重链同一个top
else top[x] = x;//否则为重链头
dfn[x] = ++dfn_num;//更新树剖序,同时下标自增
da[dfn_num] = a[x];
if(rem[x]) dfs2(rem[x],x);//优先遍历重儿子
for(auto v:e[x]){
if(v==faa) continue;
if(v==rem[x]) continue;//重儿子之前已经遍历过了
dfs2(v,x);
}
}
inline ll cal(int L,int R){
return segt.tquery(L,R);
}
void init(){//初始化
dfn_num=0;
dfs1(rt,0);
dfs2(rt,0);
segt.build(1,n,1);//这里通过da来初始化线段树
}
ll query(int x,int y){
ll res=0;
while(top[x]!=top[y]){//跳到同一条重链
if(dep[top[x]]<dep[top[y]])swap(x,y);
//重链深度更大的点优先往上跳
res += cal(dfn[top[x]],dfn[x]);//算上这条重链的
res%=p;//取模
x = fa[top[x]];//跳到重链头的父节点上
}
//跳出这个循环时,xy已经在同一个重链上了
res += cal(min(dfn[x],dfn[y]),max(dfn[x],dfn[y]));
res%=p;//取模
//xy不确定顺序对不对,所以取minmax
return res;
}
void update(int x,int y,int v){
ll res=0;
while(top[x]!=top[y]){//跳到同一条重链
if(dep[top[x]]<dep[top[y]])swap(x,y);
//重链深度更大的点优先往上跳
segt.tupdate(dfn[top[x]],dfn[x],v);//更新数值
x = fa[top[x]];//跳到重链头的父节点上
}
//跳出这个循环时,xy已经在同一个重链上了
segt.tupdate(min(dfn[x],dfn[y]),max(dfn[x],dfn[y]),v);
//xy不确定顺序对不对,所以取minmax
}
int main(){
cin>>n>>m>>rt>>p;//节点数,操作数,根节点序号,模数
for(int i=1;i<=n;i++){
cin>>a[i];//记录初始数值
a[i] = a[i]%p;
}
int x,y;
for(int i=1;i<=n;i++) e[i].clear();
for(int i=1;i<n;i++){
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
init();
int v;
int typ;
while(m--){
cin>>typ;
if(typ==1){//简单路径上修改
cin>>x>>y>>v;
update(x,y,v);
}
else if(typ==2){//简单路径上查询
cin>>x>>y;
cout<<query(x,y)<<endl;
}
else if(typ==3){//子树上修改
cin>>x>>v;
segt.tupdate(dfn[x],dfn[x]+si[x]-1,v);
}
else if(typ==4){//子树上查询
cin>>x;
cout<<segt.tquery(dfn[x],dfn[x]+si[x]-1)<<endl;
}
}
}
//洛谷 P3384 【模板】轻重链剖分 https://www.luogu.com.cn/problem/P3384
splay
int root,tot,n;
struct Node{
int ch[2];//左右儿子
int val;//值
int fa;//父节点
int size;//子树大小
int cnt;//计数
}t[MAXN];
inline void pushup(int x){//维护节点的size
t[x].size = t[x].cnt;//自己重复的次数先累计
if(t[x].ch[0]) t[x].size+=t[t[x].ch[0]].size;
if(t[x].ch[1]) t[x].size+=t[t[x].ch[1]].size;
//如果有儿子,把儿子的size加到自己
//同线段树的pushup必须先处理儿子再处理父节点
}
void rotate(int x){//旋转操作
int y=t[x].fa, z=t[y].fa;
int k=(t[y].ch[1]==x);//x是y的左还是右儿子
t[z].ch[t[z].ch[1]==y] = x;//x换到y原来在的位置
t[x].fa = z;
t[y].ch[k] = t[x].ch[k^1];//y顶下来x的儿子,放在x原来对y的位置
t[t[x].ch[k^1]].fa = y;
t[x].ch[k^1] = y;//y换到 x原来在y的 相对的 位置
t[y].fa = x;
pushup(y),pushup(x);//更新size,因为y是儿子,所以先y
}//这里的异或是用来调整0和1(左右儿子)
inline void splay(int x,int goal){//找到x并把x旋转为goal的儿子根节点
while(t[x].fa!=goal){//x一直旋转到成为goal的儿子
int y=t[x].fa,z=t[y].fa;
if(z!=goal)//如果y不是根节点,分两类讨论
(t[z].ch[0]==x)^(t[y].ch[0]==y)?rotate(x):rotate(y);
//x和y分别是y和z的同一侧儿子,先转y再转x;不同则先转x转两次
rotate(x);//无论三种情况中的哪一种都要最后转x
}
if(goal==0) root=x;//若goal是0,根节点更新为x
}
inline void find(int x){//查找x的位置,并旋转x到根节点,类似二分,O(logn)
int u = root;
if(!u) return;//树是空的情况
while(t[u].ch[x>t[u].val]&&x!=t[u].val)//找到x,不一定找得到
u = t[u].ch[x>t[u].val];//左儿子小,右儿子大
splay(u,0);//当前位置旋转到根节点.根节点的fa存0
}
inline void insert(int x){//类似find,如果插入的数已经存在,可以在找到的节点计数
int u = root,fu = 0;//当前位置u,u的爸爸fu
while(u&&t[u].val!=x){//找合适位置,u找到空的地方也会停止
fu = u;
u = t[u].ch[x>t[u].val];
}
if(u) t[u].cnt++;//已有这个数字的情况,计数
else{
u = ++tot;//节点总数tot+1
if(fu)//这时候fu是上一个u即新插入u的父节点,如果父节点不是0
t[fu].ch[x>t[fu].val]=u;
t[u].ch[0] = t[u].ch[1] = 0;//这个新节点没儿子
t[u].fa = fu;//父亲
t[u].val = x;//数值
t[u].cnt = 1;//计数
t[u].size = 1;//大小
}
splay(u,0);
}
inline int Next(int x,int f){
find(x);
int u=root;//根节点,此时x的父节点(存在的话)就是根节点
//这里sls回答了我的疑问
//"splay中不一定有x这个节点,那么它splay到根的就直接可以满足了"
//"如果有这个点的话就要在splay上再找一波(因为当前的根就是x这个点)"
if(t[u].val>x&&f)return u;//如果当前节点的值大于x并且要查找的是后继
if(t[u].val<x&&!f)return u;//如果当前节点的值小于x并且要查找的是前驱
//上面两个是x不在splay中的情况
u = t[u].ch[f];//后继在根右边找,前驱在左边找
while(t[u].ch[f^1]) u = t[u].ch[f^1];//左半边要往右跳找最大的,右半边往左跳
return u;
}
inline void Delete(int x){//删除x
int last = Next(x,0);//查找x的前驱
int next = Next(x,1);//找x的后继
splay(last,0);splay(next,last);//前驱节点转到根节点,后继转到根的儿子上
//操作完之后.后继是前驱的右儿子,x是前驱的左儿子,而且x是叶子节点
int del = t[next].ch[0];//后继的左儿子x
if(t[del].cnt>1){
t[del].cnt--;//如果有多个x,则x的计数减少一个
splay(del,0);//这个splay还重新pushup计算了del的子树
}
else
t[next].ch[0]=0;//因为是左儿子是叶子节点,直接丢掉
}
inline int kth(int x){//找第k小,改一下也可以找第k大
int u=root;
return 0;
while(1){
int y = t[u].ch[0];//左儿子
if(x>t[y].size+t[u].cnt){//如果左儿子和当前点的size比要找的排名数小
x-=t[y].size+t[u].cnt;//数量减少,相当于把这个寻找排名的起点变成了当前节点
u=t[u].ch[1];//那么当前排名的数一定要往右儿子上找
}
else if(t[y].size>=x) u=y;//左儿子的size足够,儿子在左侧上找
else return t[u].val;//左儿子的size比x小,加上当前点u的size则比x大,说明第kA大的就是x
}
}
int main(){
tot=0;
read(n);
insert(+2147483647);insert(-2147483647);//博客作者在这里先加了正负INF
int typ,x;
while(n--){
read(typ);
if(typ==1){read(x);insert(x);}//插入
else if(typ==2){read(x);Delete(x);}//删除
else if(typ==3){//查找
read(x);find(x);
printf("%d\n",t[t[root].ch[0]].size);
}
else if(typ==4){//第k小
read(x);printf("%d\n",kth(x+1));//之前插进去正负INF,所以要+1;
}
else if(typ==5){//前驱
read(x);printf("%d\n",t[Next(x,0)].val);
}
else if(typ==6){//后继
read(x);printf("%d\n",t[Next(x,1)].val);
}
}
return 0;
}
//基本是抄的这个大爹的博客https://www.cnblogs.com/cjyyb/p/7499020.html
//再自己加了一点注解
//全部代码https://paste.ubuntu.com/p/gH5mWpz669/
多项式
FFT
递归FFT
inline int read(){
char c=getchar();int x=0,f=1;
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;
}
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
const int MAXN = 3e6+5;
const double Pi = acos(-1.0);
struct cplx{//手写负数complex用起来会更快
double x,y;
cplx(double x=0,double y=0):x(x),y(y){}
friend cplx operator + (cplx a,cplx b){return cplx(a.x+b.x,a.y+b.y);}
friend cplx operator - (cplx a,cplx b){return cplx(a.x-b.x,a.y-b.y);}
friend cplx operator * (cplx a,cplx b){return cplx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
}a[MAXN],b[MAXN];
void FFT(int limit,cplx *a,int type){//limit记录项数
if(limit==1) return;//只有一个常数项结束递归(即只剩下0次的)
cplx a1[limit>>1],a2[limit>>1];//按照奇偶分组
for(int i=0;i<limit;i+=2)
a1[i>>1] = a[i],a2[i>>1] = a[i+1];
FFT(limit>>1,a1,type);
FFT(limit>>1,a2,type);
cplx Wn(cos(2*Pi/limit),type*sin(2*Pi/limit));//单位根
//这里type挺重要的,反变换的时候用-1,因为是-k
cplx w(1,0);//一会儿算单位根幂的时候用w存
cplx tmp;
for(int i=0;i<(limit>>1);i++,w=w*Wn){//w相当于公式中的w_n^k
tmp = w*a2[i];//蝴蝶操作
a[i]=a1[i]+tmp;//偶
a[i+(limit>>1)]=a1[i]-tmp;//O(1)算另外一部分
}
}
int main(){
int n=read(),m=read();
rep(i,0,n) a[i].x=read();
rep(i,0,m) b[i].x=read();
int limit = 1;
while(limit<=n+m) limit<<=1;//这里非常精髓
//把长度补到2的幂,这样就不用考虑%2余数的情况
//而且不必担心高次项的系数,因为默认为0
FFT(limit,a,1);
FFT(limit,b,1);
//1表示FFT,-1则是反变换
rep(i,0,limit) a[i] = a[i]*b[i];//转换为点值后直接相乘
FFT(limit,a,-1);//现在变回去
rep(i,0,n+m) printf("%d ",(int)(a[i].x/limit+0.5));//还要除以n的
printf("\n");
}
//P3803 【模板】多项式乘法(FFT)
//https://www.luogu.com.cn/problem/P3803
迭代FFT
const double Pi = acos(-1.0);
inline int read(){
char c=getchar();int x=0,f=1;
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;
}
struct cplx{
double x,y;
cplx(double x=0,double y=0):x(x),y(y){}
friend cplx operator + (cplx a,cplx b){return cplx(a.x+b.x,a.y+b.y);}
friend cplx operator - (cplx a,cplx b){return cplx(a.x-b.x,a.y-b.y);}
friend cplx operator * (cplx a,cplx b){return cplx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
}a[MAXN],b[MAXN];
int la,lb;
int rev[MAXN],lim,len;//len存二进制位数
//和递归FFT的limit不一样,这里的lim在FFT执行中是不变的
inline void FFT(cplx a[],int type){
rep(i,0,lim-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
//按照位逆序后的数字大小排好序,因为是置换所以On就行
rep(dep,1,log2(lim)){//第一层枚举层数
int m = 1<<dep;//得到dep层每组的元素个数
cplx Wn(cos(2.0*Pi/m),type*sin(2.0*Pi/m));//单位根
for(int k=0;k<lim;k+=m){//第二层k表示每个m元组开始的点
cplx w(1,0);
rep(j,0,(m>>1)-1){
cplx t = w*a[k+j+m/2];//蝴蝶操作,两两进行运算
cplx u = a[k+j];
//之前因为这句卡了好久,没这句的话a[k+j]的值改变了,a[k+j+m/2]就错了
a[k+j] = a[k+j]+t;
a[k+j+m/2] = a[k+j]-t;
w = w*Wn;
}
}
}
}
inline void init_rev(){//预处理rev位逆序置换数组
rep(i,0,lim-1) rev[i] = (rev[i>>1]>>1) | ((i&1)<<(len-1));//|和+作用是一样的
//后面几位是上一个子问题的解向右移动一位,第一位即最底层按奇偶得出
}
int main(){
la = read(),lb = read();
rep(i,0,la) a[i].x = read();
rep(i,0,lb) b[i].x = read();
lim = 1,len = 0;
while(lim<=la+lb) lim<<=1,len++;//一定要是2的幂
init_rev();//计算位逆序顺序
FFT(a,1);
FFT(b,1);
rep(i,0,lim-1) a[i] = a[i]*b[i];//点值表示法直接相乘
FFT(a,-1);//现在变回去
rep(i,0,la+lb) printf("%d ",(int)(a[i].x/lim+0.5));//还要除以n的
printf("\n");
}
//待改
图论
优先队列优化Dijkstra
#define LINF 1ll<<60
const int MAXN = 2e5+10;
typedef pair<int,ll> pli;
vector<pli> e[MAXN];//first是目标的点,second是距离dis
bool vis[MAXN];
ll d[MAXN];
int n,m,s,t;
void dijkstra(){
rep(i,1,n){
d[i] = LINF;
vis[i] = 0;
}
d[s] = 0;
priority_queue< pli,vector<pli>,greater<pli> >q;//first存d[x],second存x的编号
q.push(make_pair(0,s));
while(!q.empty()){//进行类似bfs的操作
int now = q.top().second;
q.pop();
if(vis[now])continue;//可以看到下面的操作是都先推进去的,所以可能重复遇到now点
vis[now] = 1;
int siz = e[now].size();
for(auto x:e[now]){//遍历now的所有边
int v = x.second;//到达的点
if(d[v]>d[now]+x.first){
d[v] = d[now] + x.first;
q.push(make_pair(d[v],v));//推入优先队列
}
}
}
}
int main(){
cin>>n>>m>>s;
int u,v;
ll dis;
rep(i,1,m){
cin>>u>>v>>dis;
e[u].push_back(make_pair(dis,v));
//e[v].push_back(make_pair(u,dis));//双向通行的情况
}
dijkstra();
rep(i,1,n){
cout<<d[i];//输出到各个点的最短路径
if(i!=n) cout<<' ';
}
}
Dinic
struct Edge{
int from,to;
ll cap,flow;
};
int n,m,s,t;//节点数,边数(含反向弧),源点与汇点编号
vector<Edge> edges;//边长,edges[e]和edges[e^1]互为反向弧
vector<int> G[MAXN];//邻接表,G[i][j]表示节点i的第j条边在edges中的编号
int d[MAXN];//dist层数,即d[i]为起点到i的距离(层数),同时也起到vis的作用防止重复走
int cur[MAXN];//这个cur是dfs的时候用来省略重复步骤的
void add_edge(int from,int to,ll cap){
edges.push_back((Edge){from,to,cap,0});
edges.push_back((Edge){to,from,0,0});
int siz = edges.size();
G[from].push_back(siz-2);//正向边
G[to].push_back(siz-1);//反向边
}
bool bfs(){
memset(d,-1,sizeof(d));
queue<int> q;
q.push(s);
d[s]=0;//dist(s) = 0
while(!q.empty()){
int x=q.front();
q.pop();
rep(i,0,G[x].size()-1){
Edge &e = edges[G[x][i]];
if(d[e.to]==-1&&e.cap>e.flow){//只考虑残量网络中的弧即这个边还没被填满
d[e.to]=d[x]+1;//标记层次dist(x)
q.push(e.to);
}
}
}
return d[t]!=-1;//找到到t节点的路径则回1,找不到则回0
}
ll dfs(int x,ll a){//DFS除了当前节点x外,还要传入"目前为止所有边的最小残量"即"水流到这里还剩多少"
if(x==t||a==0) return a;//也是很简洁一句话 ,结束增广
ll flow = 0,f;
for(int &i=cur[x];i<G[x].size();i++){//上一次阻塞流已经把cur[x]之前的弧都排除了
Edge &e = edges[G[x][i]];
if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.cap-e.flow)))>0){
e.flow+=f;
edges[G[x][i]^1].flow-=f;
flow+=f;
a-=f;
if(a==0) break;//这句及时退出很影响效率
}
}
return flow;
}
ll Maxflow(){
ll flow=0;
while(bfs()){
memset(cur,0,sizeof(cur));//因为有新的反向边引入,即"正向边"更新了 ,有些实际上是反,但dfs里面当正的用
flow+=dfs(s,INF);//对当前阻塞流dfs;
}
return flow;
}
int main(){
int u,v;
ll cap;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++){
scanf("%d%d%lld",&u,&v,&cap);
add_edge(u,v,cap);
}
cout<<Maxflow()<<endl;
}
//P3376 【模板】网络最大流 https://www.luogu.com.cn/problem/P3376
二分匹配匈牙利
int uN,vN;//u,v的数目
bool g[MAXN][MAXN];//邻接矩阵,一般情况下uv最大范围相同
int linker[MAXN];//存右节点的对象
bool used[MAXN]; //右点是否访问过
bool dfs(int u){
for(int v=0;v<vN;v++){
if(g[u][v]&&!used[v]){//判断是否有边,是否被用过
used[v]=true;//标记访问
if(linker[v]==-1||dfs(linker[v])){
linker[v]=u;//很精妙,记录新配对,在进行dfs后,连接左右节点的边其实方向反了一反
return true;
}
}
}
return false;
}
int hungary(){//匈牙利算法
int res=0;
memset(linker,-1,sizeof(linker));//初始化,因为节点uv从0开始,所以linker初始化为-1
for(int u=0;u<uN;u++){
memset(used,false,sizeof(used));
if(dfs(u)) res++;
}
return res;
}
int main(){
int e;
cin>>uN>>vN>>e;
int u,v;
memset(g,0,sizeof(g));
while(e--){
scanf("%d%d",&u,&v);
g[u][v]=1;
}
cout<<hungary()<<endl;
}
/*匈牙利算法可以解决的问题:
1.二分图的最大匹配数(如婚配问题)
2.最小顶点覆盖------用最少的点覆盖所有的边(如HDOJ禁止早恋,任务安排)
结论:最小顶点覆盖数==最大匹配数量
3. DAG(有向无环图)的最小路径覆盖
(如HDOJ空袭,所有路单行,并且所有街是两个路口相连,已知不会形成回路。问最少空降几个伞兵可以访问所有路口)
拆点法:1拆成1和1',数字x放左节点,x'放右节点
DAG图的最小路径覆盖数 == 节点数(n) - 最大匹配数
*/
kruskal最小生成树
struct Edge{
int u,v,k;
friend bool operator < (Edge a,Edge b){
return a.k<b.k;
}
}edges[MAXM];
int fa[MAXN];
int rankk[MAXN];//树高优化并查集
int find(int x){
if(fa[x]==x) return x;
return fa[x] = find(fa[x]);
}
void merge(int a,int b){
a = find(a);b = find(b);
if(a==b) return;
if(rankk[a]>rankk[b]) fa[b] = a;
else{
fa[a] = b;
if(rankk[a]==rankk[b])rankk[b]++;
}
}
int n,m;
int main(){
cin>>n>>m;
rep(i,1,m) cin>>edges[i].u>>edges[i].v>>edges[i].k;
rep(i,1,n) fa[i] = i,rankk[i] = 0;
sort(edges+1,edges+1+m);
ll res=0;
rep(i,1,m){
if(find(edges[i].u)!=find(edges[i].v)){//检查两点是否在同一集合内
res+=edges[i].k;
merge(edges[i].u,edges[i].v);
}
}
bool flag = 1;
rep(i,2,n){
if(find(n)!=find(n-1)) {flag = 0;break;}
}
if(!flag) cout<<"orz"<<endl;
else cout<<res<<endl;
}
//P3366 【模板】最小生成树 https://www.luogu.com.cn/problem/P3366
tarjan求强连通分量
int n;
vector<int> e[MAXN];//存图
int dfn[MAXN],low[MAXN];
int dfncnt;//dfn自增下标
int s[MAXN],tp;//数组模拟栈,tp记录大小
bool in[MAXN];//记录该点是否在栈中
int scc[MAXN],sc;//节点i所在scc的编号,sc记录有几个强连通
int sz[MAXN];//强连通i的大小
int indg[MAXN];//记录缩点后的入度(这题才有的
void tarjan(int u){
low[u]=dfn[u]=++dfncnt;//low初始值为自身dfn
s[++tp]=u;//推u入栈,从1开始
in[u]=1;//记录u点在栈中
for(auto v:e[u]){//访问到新点的情况
if(!dfn[v]){
tarjan(v);
low[u] = min(low[u],low[v]);//用low[v}更新low[u]
}
else if(in[v])//v被访问过,但是在栈中
low[u] = min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){//u是连通分量的根节点
sc++;//强连通数量++
sz[sc] = 0;
while(s[tp]!=u){//u和u之后的点全部出栈
scc[s[tp]] = sc;//这个点包含于第几个强连通
sz[sc]++;//u为根的这个强连通的大小
in[s[tp]] = 0;//出栈
tp--;
}
scc[u] = sc;//给根节点标,属于第sc个强连通
sz[sc]++;
in[u] = 0;
tp--;
}
}
void reset(){
tp = sc = dfncnt =0;
rep(i,1,n){
in[i] = dfn[i] = 0;//low不用清空,sz在之后用到再清空
e[i].clear();
}
}
int main(){
cin>>n;
reset();
int v;
rep(u,1,n){
while(cin>>v&&v!=0) e[u].push_back(v);
}
rep(u,1,n)
if(!dfn[u]) tarjan(u);
rep(i,1,sc) indg[i] = 0;//这个不包含在tarjan里面,是这题记录入度的
rep(u,1,n){
for(auto v:e[u]){
if(scc[u]!=scc[v]) indg[scc[v]]++;
}
}
int res = 0;
rep(i,1,sc){
if(indg[i]==0) res++;
}
cout<<res<<endl;
}
//洛谷P2835 刻录光盘 https://www.luogu.com.cn/problem/P2835#submit
拓扑排序
#define LINF 1ll<<60
const int MAXN = 2e5+10;
typedef pair<int,ll> pli;
vector<pli> e[MAXN];//first是目标的点,second是距离dis
bool vis[MAXN];
ll d[MAXN];
int n,m,s,t;
void dijkstra(){
rep(i,1,n){
d[i] = LINF;
vis[i] = 0;
}
d[s] = 0;
priority_queue< pli,vector<pli>,greater<pli> >q;//first存d[x],second存x的编号
q.push(make_pair(0,s));
while(!q.empty()){//进行类似bfs的操作
int now = q.top().second;
q.pop();
if(vis[now])continue;//可以看到下面的操作是都先推进去的,所以可能重复遇到now点
vis[now] = 1;
int siz = e[now].size();
for(auto x:e[now]){//遍历now的所有边
int v = x.second;//到达的点
if(d[v]>d[now]+x.first){
d[v] = d[now] + x.first;
q.push(make_pair(d[v],v));//推入优先队列
}
}
}
}
int main(){
cin>>n>>m>>s;
int u,v;
ll dis;
rep(i,1,m){
cin>>u>>v>>dis;
e[u].push_back(make_pair(dis,v));
//e[v].push_back(make_pair(u,dis));//双向通行的情况
}
dijkstra();
rep(i,1,n){
cout<<d[i];//输出到各个点的最短路径
if(i!=n) cout<<' ';
}
}
//Uva10305 https://vjudge.net/problem/UVA-10305
HASH
拉链法
const int med = 1000007;//模数
const int MAXN = 2e6+5;//数据总量
struct Hash_table{
struct Node{
int next,value,key;
}data[MAXN];//数据的总量,从1开始
int head[med],size;//head记录每个哈希值的链表的第一个节点
//size记录节点总数
int f(int key){ return key%med; }//求哈希值
int find(int key){
for(int p = head[f(key)];p;p=data[p].next)//遍历这个哈希值上的链表
if(data[p].key==key) return data[p].value;
return -1;
}
int update(int key,int value){//更新value
for(int p = head[f(key)];p;p=data[p].next)
if(data[p].key==key) return data[p].value = value;
return -1;
}
int add(int key,int value){
if(find(key)!=-1) return -1;//这个值已经被插入了
data[++size] = (Node){head[f(key)],value,key};//从链表头部插入
head[f(key)] = size;//标记该链表的第一个节点
return value;
}
};
几何
三个点求圆心
struct point{
double x;
double y;
};
point cal(point a,point b,point c){
double x1 = a.x;double y1 = a.y;
double x2 = b.x;double y2 = b.y;
double x3 = c.x; double y3 = c.y;
double a1 = 2*(x2-x1); double a2 = 2*(x3-x2);
double b1 = 2*(y2-y1); double b2 = 2*(y3-y2);
double c1 = x2*x2 + y2*y2 - x1*x1 - y1*y1;
double c2 = x3*x3 + y3*y3 - x2*x2 - y2*y2;
double rx = (c1*b2-c2*b1)/(a1*b2-a2*b1);
double ry = (c2*a1-c1*a2)/(a1*b2-a2*b1);
return point{rx,ry};
}
数学
组合数板子
const int MAXN = 3e5+5;
const int med = 998244353;
ll jc[MAXN];
ll qpow(ll d,ll c){//快速幂
ll res = 1;
while(c){
if(c&1) res=res*d%med;
d=d*d%med;c>>=1;
}return res;
}
inline ll niyuan(ll x){return qpow(x,med-2);}
void initjc(){//初始化阶乘
jc[0] = 1;
rep(i,1,MAXN-1) jc[i] = jc[i-1]*i%med;
}
inline int C(int n,int m){//n是下面的
if(n<m) return 0;
return jc[n]*niyuan(jc[n-m])%med*niyuan(jc[m])%med;
}
int main(){
initjc();
int n,m;
while(cin>>n>>m) cout<<C(n,m)<<endl;
}
高斯消元
double a[MAXN][MAXN];
int n,pl;
int main(){
scanf("%d",&n);
rep(i,1,n)
rep(j,1,n+1) scanf("%lf",&a[i][j]);
rep(i,1,n){
pl = i;
while(a[pl][i]==0&&pl<=n) pl++;//找到每列的第一个非0元素
if(pl==n+1){//无解的情况(存在空列
cout<<"No Solution"<<endl;return 0;
}
rep(j,1,n+1) swap(a[i][j],a[pl][j]);//保证i行i列必不是0
double k = a[i][i];//第二步,使a[i][i]变成1
rep(j,1,n+1) a[i][j]/=k;//i行所有元素除a[i][i]
rep(ii,1,n){
if(ii==i) continue;//枚举不同的两行
double ki = a[ii][i];
rep(m,1,n+1) a[ii][m]-=ki*a[i][m];
}
}
rep(i,1,n) printf("%.2lf\n",a[i][n+1]);
}
//洛谷P3389 【模板】高斯消元法 https://www.luogu.com.cn/problem/P3389
矩阵快速幂
int n;
const ll mod = 998;
struct Matrix{
ll a[MAXN][MAXN];
Matrix(ll x=0){//感觉是特别好的初始化,从hjt那里学(抄)来的
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
a[i][j]=x*(i==j);//这句特简洁
}
}
}
Matrix operator *(const Matrix &b)const{//通过重载运算符实现矩阵乘法
Matrix res(0);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
ll &ma = res.a[i][j];
ma = (ma+a[i][k]*b.a[k][j])%mod;
}
}
}
return res;
}
};
Matrix qpow(Matrix d,ll m){//底数和幂次数
Matrix res(1);//构造E单位矩阵
while(m){
if(m&1){
m--;//其实这句是可以不要的
res=res*d;
}
d=d*d;
m>>=1;
}
return res;
}
int main(){
int p;
Matrix inp;
cin>>n>>p;
for(int i=-0;i<n;i++){
for(int j=0;j<n;j++){
scanf("%lld",&inp.a[i][j]);
}
}
Matrix res = qpow(inp,p);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cout<<res.a[i][j];
if(j!=n-1) cout<<' ';
}
cout<<endl;
}
}
数论
线性求逆元
void init(int p){
inv[1] = 1;
for(int i=2;i<=n;i++){
inv[i] = (ll)(p-p/i)*inv[p%i]%p;
}
}
单个欧拉函数
void init(int p){
inv[1] = 1;
for(int i=2;i<=n;i++){
inv[i] = (ll)(p-p/i)*inv[p%i]%p;
}
}
EXCRT扩展中国剩余
template<class T>
void wt(T x){//快写
if(x < 0) putchar('-'), x = -x;
if(x >= 10) wt(x / 10);
putchar('0' + x % 10);
}
void Exgcd(ll a,ll b,ll &d,ll &x,ll &y){//扩展欧几里得
if(!b){d=a;x=1;y=0;}
else{Exgcd(b,a%b,d,y,x);y-=x*(a/b);}//先交换了xy的位置,实现y1=x2-(a/b)*x2
}
inline ll mul(ll a,ll b,ll mo){//取模乘法
return ((a%mo)*(b%mo)%mo+mo)%mo;
}
const int MAXN = 1e5+5;
ll c[MAXN],m[MAXN];//每组同余式的余数和模数
int n;
ll Excrt(ll m[],ll c[],int n){
ll mnow = m[1],cnow = c[1];//记录每次合成后的模数余数
rep(i,2,n){
ll p1,p2,gcdd;
ll m1 = mnow,m2 = m[i];//m1p1+c1 = m2p2+c2
ll dc = (c[i]-cnow%m2+m2)%m2;//dc在保证同余的情况下变成最小的正数
Exgcd(m1,m2,gcdd,p1,p2);
if(dc%gcdd) {cout<<i<<endl;return -1;}
p1 = mul(p1,dc/gcdd,m2/gcdd);//p1存的实际是p1*m1,这里的模数比较讲究
//一会儿要对lcm(m1,m2)取模,最终结果是[p1*m1*(dc/gcdd)] % [m2/gcdd*m1]
//m1还没乘上去,这时候先对m2/gcdd取模
cnow += p1*m1;//更新cnow和mnow
mnow = m1/gcdd*m2;
cnow = (cnow%mnow+mnow)%mnow;
}
return cnow;
}
int main(){
cin>>n;
rep(i,1,n) read(m[i]),read(c[i]);
wt(Excrt(m,c,n));
}
//洛谷P4777 【模板】扩展中国剩余定理(EXCRT)
//https://www.luogu.com.cn/problem/P4777
EXGCD扩展欧几里得
ll Exgcd(ll a,ll b,ll &x,ll &y){
if(!b){x=1;y=0;return a;}//边界条件结束递归
ll d = Exgcd(b,a%b,x,y);//gcd
ll t = x;
x=y;y=t-(a/b)*y;//通过x2y2求得x1y1,层层返回
return d;
}
//紫书里面刘汝佳的简短的版本
void Exgcd(ll a,ll b,ll &d,ll &x,ll &y){//不同的是,这里的d使用引用来实现
if(!b){d=a;x=1;y=0;}
else{Exgcd(b,a%b,d,y,x);y-=x*(a/b);}//先交换了xy的位置,实现y1=x2-(a/b)*x2
}
字符串
AC自动机
统计是否出现
const int MAXN = 1e6+5;
inline int idx(char c){return c-'a';}
struct Node{
int son[26],flag,fail;
}trie[MAXN*10];
int n,cntt;
string s;
queue<int> q;
void insert(string &s){//字典树插入模式串
int siz = s.size(),v,u = 1;//根节点开始
rep(i,0,siz-1){
v = idx(s[i]);
if(!trie[u].son[v]) trie[u].son[v] = ++cntt;
u = trie[u].son[v];
}
trie[u].flag++;//记数
}
void getfail(){//处理失配指针
rep(i,0,25) trie[0].son[i] = 1;//虚节点0
q.push(1);trie[1].fail = 0;
//建一个虚节点0号节点,将1的所有儿子指向1,然后1的fail指向0就OK了
int u,v,ufail;
while(!q.empty()){
u = q.front();q.pop();
rep(i,0,25){
v = trie[u].son[i];
ufail = trie[u].fail;
if(!v){trie[u].son[i]=trie[ufail].son[i];continue;}
//如果这个分支不满足,则会和失配的情况类似去跳转
trie[v].fail = trie[ufail].son[i];
//((他父亲节点)的失配指针指向的节点)的(和这个节点字母相同的儿子)
q.push(v);
}
}
}
int query(string &s){//匹配
int siz = s.size(),u = 1,v,k,ans = 0;
rep(i,0,siz-1){
v = idx(s[i]);
k = trie[u].son[v];//k用来跳fail
while(k&&trie[k].flag!=-1){//找到了没标记的单词
ans += trie[k].flag;trie[k].flag = -1;//计数,并标记走过
k = trie[k].fail;//跳fail,如果一个串匹配成功,那它的fail一定也能匹配
}
u = trie[u].son[v];
}
return ans;
}
int main(){
cntt = 1;//初始化cnt
cin>>n;
string hc;
rep(i,1,n){
cin>>s;insert(s);
}
getfail();
cin>>s;
cout<<query(s)<<endl;
}
//P3808 【模板】AC自动机(简单版)https://www.luogu.com.cn/problem/P3808
//https://www.luogu.com.cn/blog/juruohyfhaha/solution-p3808
统计出现次数(比较慢一点)
const int MAXN = 1e5+5;
int jdbh[MAXN];//记录第i个模式串对应的节点编号
int cntcx[MAXN];//记录第i个模式串出现的次数
inline int idx(char c){return c-'a';}
struct Node{
int son[26],flag,fail;//cnt记录次数,flag记录编号
void clr(){
memset(son,0,sizeof(son));
flag=0;
}
}trie[MAXN*10];
int n,cntt;//cntt记录总点数
string s,ms[166];
int maxx;
queue<int>q;
inline void insert(string &s,int num){
int siz = s.size(),v,u=1;
rep(i,0,siz-1){
v = idx(s[i]);
if(!trie[u].son[v]){trie[u].son[v] = ++cntt;trie[cntt].clr();}
u = trie[u].son[v];
}
trie[u].flag = num;//标记为单词,flag记录编号
//保证每个模式串只出现一次
cntcx[num] = 0;
jdbh[num] = u;//记录当前单词对应的节点编号
}
inline void getfail(){
rep(i,0,25) trie[0].son[i] = 1;
trie[0].flag = 0;
q.push(1);
trie[1].fail = 0;
int u,v,ufail;
while(!q.empty()){
u = q.front();q.pop();
rep(i,0,25){
v = trie[u].son[i];
ufail = trie[u].fail;
if(!v){trie[u].son[i]=trie[ufail].son[i];continue;}//画好一条跳fail的路
trie[v].fail = trie[ufail].son[i];
q.push(v);
}
}
}
inline void query(string &s){
int siz = s.size(),u = 1,v,k;
rep(i,0,siz-1){
v = idx(s[i]);
k = trie[u].son[v];
while(k){
if(trie[k].flag){
cntcx[trie[k].flag]++;//计数
maxx = max(maxx,cntcx[trie[k].flag]);
}
k = trie[k].fail;//跳fail
}
u = trie[u].son[v];//这一句其实也有跳fail的功能,很精妙
}
}
inline void solve(){
cntt = 1;
trie[0].clr();
trie[1].clr();
rep(i,1,n){
cin>>ms[i];
insert(ms[i],i);
}
getfail();
cin>>s;
maxx = 0;
query(s);
cout<<maxx<<endl;
rep(i,1,n){
if(cntcx[i]==maxx) cout<<ms[i]<<endl;
}
}
int main(){
while(cin>>n&&n!=0) solve();
}
//洛谷 P3796 【模板】AC自动机(加强版)
//https://www.luogu.com.cn/problem/P3796
KMP字符串匹配
const int MAXN = 2e6+5;
int pi[MAXN];//MAXN记得开大一点,因为这里要存到m+n+1长度的
vector<int> res;//储存答案
void getpi(const string &s){ //求s的前缀函数
pi[0]=0;
int j=0;
rep(i,1,s.length()-1){
while(j>0&&s[i]!=s[j]) j=pi[j-1];//找到合适且最长的j
if(s[i]==s[j])j++;//能成功匹配的情况
pi[i]=j;
}
}
void kmp(string s,string t){ //在主串t中找模式串s
getpi(s+'#'+t);
int n=(int)s.length(),m=(int)t.length();
rep(i,n+1,m+n+1-1)
if(pi[i]==n) res.push_back(i-2*s.size()); //i-2n计算得左端点
}
字符串哈希
双哈希判断是否相等
typedef pair<ull,ull> pll;
const int MAXN = 1e4+5;
const int M1 = 1e9+7;//第一个模数
const int M2 = 1e9+9;//第二个模数
const int b = 131;
int n;
pll a[MAXN];
pll gethash(string s){
ull res1=0,res2=0;
int siz = s.length();
rep(i,0,siz-1){
res1=(res1*b%M1+s[i])%M1;//i位乘以b^i
res2=(res2*b%M2+s[i])%M2;//f(s)=Σ s[i] * b^i;
}
return make_pair(res1,res2);
}
int main(){
cin>>n;
string s;
rep(i,1,n){
cin>>s;a[i]=gethash(s);
}
sort(a+1,a+n+1);
int res = 1;
rep(i,2,n){
if(a[i]!=a[i-1]) res++;
}
cout<<res<<endl;
}
配合快速幂取子串的哈希值
const int b = 131;
const int MAXN = 1e5 + 5;
typedef unsigned long long ull;
ull h[MAXN], pw[MAXN]; // h[k]存储字符串前k个字母的哈希值, pw[k]存储 b^k mod 2^64
//这里的模数M取的就是ull的上限2^64
char str[MAXN];
void init(int n){//初始化
pw[0] = 1;
for (int i = 1; i <= n; i ++ ) {
h[i] = h[i-1]*b + str[i];//做每个前缀的哈希值
pw[i] = pw[i-1]*b;//预处理b^k的值
}
}
// 计算子串 str[l ~ r] 的哈希值
ull get(int l, int r) {
return h[r] - h[l-1]*pw[r-l+1];
}
int main() {
int n, m;
scanf("%d%d%s",&n,&m,str+1);//这样读入字符串第一位从1开始
init(n);
while (m--) {
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1)!=get(l2,r2))
printf("No\n");
else printf("Yes\n");
}
return 0;
}
//ACWing 841 给两个区间查询这两个子串是否相同
Trie字典树
#define MAXN 10000
const int maxnode=2e6+5;
const int sigma_size=26;//字母开26,如果是数字之类的则开10
char s[MAXN];
struct Trie{
int ch[maxnode][sigma_size];
int val[maxnode];
int sz;
Trie(){sz=1;memset(ch[0],0,sizeof(ch[0]));}//初始化,此时只有根节点
int idx(char c){
return c-'a';//同理如果是数字则换成c-'0'
}
void insert(char *s,int v){
int u=0,n=strlen(s);
for(int i=0;i<n;i++){
int c=idx(s[i]);
if(!ch[u][c]){//节点不存在(u没有子节点字母为c)这个u和c的意义全然不同,u是节点编号,c是字符编号
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;//中间节点,附加信息为0,当然也可以用其他的来标记比如-1
ch[u][c]=sz++;//画好边,并且总结点数++
}
u=ch[u][c];//光标往下走
}//这里出来就是u已经到单词节点(即一个单词的末尾字符)上了
val[u] = v;//insert时的第二个参数v,可以附加信息
}
bool find(char *s,int len){//查找串s长度不超过len的前缀,改一改成int也可以返回附加值
int u=0;
for(int i=0;i<len;i++){
if(s[i]=='\0') break;
int c=idx(s[i]);
if(!ch[u][c]) break;
u=ch[u][c];
if(val[u]!=0) return true;
}
return false;
}
}T;//之前因为T定义在main里面所以一直运行不了,发现这个结构体和数组有点类似
//定义为全局函数可以开得更大!
int main(){
int n;
scanf("%d",&n);
getchar();
Trie T;
while(n--){
scanf("%s",s);
getchar();
T.insert(s,1);
}
scanf("%d",&n);
getchar();
while(n--){
scanf("%s",s);
getchar();
if(T.find(s,strlen(s))) cout<<"yes!"<<endl;
else cout<<"no!"<<endl;
}
}
动态规划DP
数位DP
int l,r;
const int MAXN = 25;
int dp[MAXN][2];//第二维记录前面一个是否为2
int a[MAXN];
int n,m;
int dfs(int pos,bool stat,int pre,bool limit){
//limit记录之前是否都在上界,state记录之前的是不是6(记忆化作第二维),pre记录上一个数字
if(pos==-1) return 1;
if(!limit&&dp[pos][stat]!=-1) return dp[pos][stat];
int up = limit?a[pos]:9;//如果前面的都在上界则只能到a[pos]
int ans = 0;
for(int i=0;i<=up;i++){
if(pre==6&&i==2) continue;//62的情况
if(i==4) continue;
ans += dfs(pos-1,i==6,i,limit&&i==a[pos]);
}
if(!limit) dp[pos][stat] = ans;
return ans;
}
int solve(int x){
//先按位转化到数组
int px = 0;
while(x){
a[px++] = x%10;
x/=10;
}
memset(dp,-1,sizeof(dp));//清空记忆化数组
return dfs(px-1,0,-1,1);
}
int main(){
while(cin>>l>>r){
if(l==0&&r==0) break;
//cout<<solve(r)<<' '<<solve(l-1)<<endl;
cout<<solve(r)-solve(l-1)<<endl;
}
}
//HDOJ2089 不要62
其他
母函数
#include<iostream>
using namespace std;
#define rep(i,a,b) for(int (i)=a;i<=b;i++)
const int MAXN = 1e4;
int c1[MAXN+1];
int c2[MAXN+1];
int main(){
int n;
while(cin>>n){
for(int i=0;i<=n;i++){//初始化
c1[i]=0;
c2[i]=0;
}
for(int i=0;i<=n;i++){
c1[i]=1;//面值为一元的
}
for(int i=2;i<=n;i++){//枚举邮票的面值(一共有几组括号
for(int j=0;j<=n;j++){//枚举左边次数为0到次数为n的项
for(int k=0;j+k<=n;k+=i){//右边的乘过来,枚举放几枚
//次数大于n的就不用管了
c2[j+k]+=c1[j];
}
}
for(int i=0;i<=n;i++){//最左边两个括号完成处理
c1[i]=c2[i];//把c2算出来的值挪到左边作为下一次的左边
c2[i]=0;//清空c2记录下一次括号相乘的结果
}
}
cout<<c1[n]<<endl;
}
}
并查集
int par[MAXN];//记录父亲节点
int rank[MAXN];//记录树高,根节点(祖先节点)那层是不算进去的
void init(int n){
for(int i=0;i<n;i++){
par[i]=i;
rank[i]=0;
}
}
int find(int x){//查询根操作,同时进行路径压缩
if(par[x]==x) return x;
else return par[x]=find(par[x]);//这句很精简,在递归查询根节点的同时路i压缩
}
//虽然路径压缩的操作会对树高产生影响,导致rank的数值不准确,但这样还是能有效地提高运行效率的
void merge(int x,int y){
x=find(x);y=find(y);
if(x==y) return;///如果已经具有相同的祖先,则不进行合并操作
if(rank[x]<rank[y]) par[x]=y;//y树比x树高的情况,把x并为y的儿子节点
else{//y比x高,或同高的情况下,把y并为x的儿子节点
par[y]=x;
if(rank[x]==rank[y]) rank[x]++;//如果合并的两树同高,则合并后树高+1;
}
}
int main(){
int n,m;
cin>>n>>m;
init(n);
int a,b;
while(m--){
cin>>a>>b;
merge(a,b);
}
cin>>m;
while(m--){
cin>>a>>b;
if(find(a)==find(b)) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
}
//路径压缩+树高优化
莫队
普通莫队
const int MAXN = 5e4+5;
int cnt[MAXN];//记录数字在区间[l,r]内出现的次数
int pos[MAXN],a[MAXN];
ll ans[MAXN];
int n,m,k,res;
struct Q{
int l,r,k;//k记录原来的编号
friend bool operator < (Q x,Q y){//同一个分块内r小的排前面;不同分块则按分块靠前的
return pos[x.l]==pos[y.l]?x.r<y.r:pos[x.l]<pos[y.l];
//return (pos[a.l]^pos[b.l])?pos[a.l]<pos[b.l]:((pos[a.l]&1)?a.r<b.r:a.r>b.r);
//这条第一个和==是一样的,后面的是对于左端点在同一奇数块的区间,右端点按升序排列,反之降序
}
}q[MAXN];
void Add(int pos){
res -= cnt[a[pos]]*cnt[a[pos]];
cnt[a[pos]]++;
res += cnt[a[pos]]*cnt[a[pos]];
}
void Sub(int pos){
res -= cnt[a[pos]]*cnt[a[pos]];
cnt[a[pos]]--;
res += cnt[a[pos]]*cnt[a[pos]];
}
int main(){
cin>>n>>m>>k;//k为数字范围
memset(cnt,0,sizeof(cnt));
int siz = sqrt(n);//每个分块的大小
rep(i,1,n){
cin>>a[i];
pos[i] = i/siz;//分块
}
rep(i,1,m){
cin>>q[i].l>>q[i].r;
q[i].k = i;//记录原来的编号,用于打乱顺序后的还原
}
sort(q+1,q+1+m);
res = 0;//初始化res
int l = 1,r = 0;//当前知道的区间
//因为是闭区间,如果是[1,1]的话则一开始就包含一个元素了
rep(i,1,m){//莫队的核心,注意加减的顺序
while(q[i].l<l) Add(--l);
while(q[i].l>l) Sub(l++);
while(q[i].r<r) Sub(r--);
while(q[i].r>r) Add(++r);
ans[q[i].k] = res;
}
rep(i,1,m) cout<<ans[i]<<endl;
}
//洛谷P2709 小B的询问
//https://www.luogu.com.cn/problem/P2709
带修莫队
int cnt[1000010] = {0};
const int MAXN = 2e5+5;
int a[MAXN],b[MAXN];//a读入一开始的序列,b记录修改后的
int pos[MAXN];//分块
int cq,cr;//统计查询修改次数
int R[MAXN][3];//0记位置,1记原本的值,2记修改后的值
ll res;
int ans[MAXN];//记录结果
int n,m;
void Add(int x){if(cnt[x]==0)res++;cnt[x]++;}//带修莫队的add和sub有区别
void Sub(int x){if(cnt[x]==1)res--;cnt[x]--;}
struct Q{
int l,r,k,t;
friend bool operator < (Q a,Q b){
return (pos[a.l]^pos[b.l])?pos[a.l]<pos[b.l]:((pos[a.r]^pos[b.r])?a.r<b.r:a.t<b.t);
//增加第三关键字,询问的先后顺序,用t或者k应该都行
}
}q[MAXN];
int main(){
cin>>n>>m;
cq = cr = 0;
int siz = pow(n,2.0/3.0);//这么分块最好,别问
rep(i,1,n){
cin>>a[i];
b[i]=a[i];
pos[i] = i/siz;
}
char hc;
rep(i,1,m){//读入修改和询问
cin>>hc;
if(hc=='Q'){
cin>>q[cq].l>>q[cq].r;
q[cq].k=cq;q[cq].t=cr;//注意这时候R[cr]还是没有的,这次询问是在R[cr-1]之后的
cq++;
}
else{
cin>>R[cr][0]>>R[cr][2];
R[cr][1] = b[R[cr][0]];
b[R[cr][0]] = R[cr][2];//在b数组中记录更改
cr++;
}
}
sort(q,q+cq);
int l=1,r=0,sjc=0;//时间戳
res = 0;
rep(i,0,cq-1){
while(sjc<q[i].t){
if(l<=R[sjc][0]&&R[sjc][0]<=r)//判断修改是否在该区间内
Sub(R[sjc][1]),Add(R[sjc][2]);
a[R[sjc][0]] = R[sjc][2];//在a上也进行更改
sjc++;
}
while(sjc>q[i].t){
sjc--;
if(l<=R[sjc][0]&&R[sjc][0]<=r)//判断修改是否在该区间内
Sub(R[sjc][2]),Add(R[sjc][1]);
a[R[sjc][0]] = R[sjc][1];//在a上也进行更改
}
while(l>q[i].l) Add(a[--l]);
while(l<q[i].l) Sub(a[l++]);
while(r<q[i].r) Add(a[++r]);
while(r>q[i].r) Sub(a[r--]);
ans[q[i].k] = res;
}
rep(i,0,cq-1) cout<<ans[i]<<endl;
}
//洛谷P1903 [国家集训队]数颜色 / 维护队列
//https://www.luogu.com.cn/problem/P1903
扫描线Scanline
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define ll long long
#define ls (x<<1)
#define rs (x<<1|1)//这种方法感觉还挺好的
const int MAXN = 2e5+5;//这里要开n的两倍
//线结构体
struct Line{
ll l,r,h;
int qz;//记录位置和权值
bool operator < (Line &rhs){
return h < rhs.h;
}
}line[MAXN];
int n;
ll x1,y1,x2,y2;
ll X[MAXN];
//线段树
struct Segt{
int l,r;//是X的下标,即离散化后的
int sum;//sum是被完全覆盖的次数
ll len;//len是区间内被盖住的长度
//因为每次查询都是查询根节点,所以这边不需要懒惰标记
}t[MAXN<<3];//一个边有两个点,所以这里要开8倍
void build(int x,int l,int r){
t[x].l = l;t[x].r = r;
t[x].len = t[x].sum = 0;
if(l==r) return;//到了叶子节点
int mid = (l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void push_up(int x){
int l = t[x].l,r = t[x].r;
if(t[x].sum) t[x].len = X[r+1]-X[l];//x的区间是X[l]到X[r+1]-1
else t[x].len = t[ls].len + t[rs].len;//合并儿子的信息
}
void update(int x,int L,int R,int v){//这里的LR存的是实际值
//这里如果是线段L,R,线段树上是L到R-1的部分维护
int l = t[x].l,r = t[x].r;
if(X[r+1]<=L||R<=X[l]) return;//加等于,不然会搞到无辜的线
if(L<=X[l]&&X[r+1]<=R){
t[x].sum += v;//修改覆盖次数
push_up(x);
return;
}
update(ls,L,R,v);
update(rs,L,R,v);
push_up(x);
}
int main(){
cin>>n;
rep(i,1,n){
cin>>x1>>y1>>x2>>y2;
X[2*i-1] = x1,X[2*i] = x2;//一会儿离散化要用的,这里存实际值
line[2*i-1] = Line{x1,x2,y1,1};//开始的线
line[2*i] = Line{x1,x2,y2,-1};//结束的线
}
n<<=1;//line的数量是四边形数量的2倍
sort(line+1,line+1+n);
sort(X+1,X+1+n);
int tot = unique(X+1,X+n+1)-(X+1);//去除重复相邻元素,并且tot记录总数
build(1,1,tot-1);//为什么是tot-1?
//因为线段树只需要维护X[1]到X[tot]-1这一段的,实际长度是向右贴的
ll res = 0;
rep(i,1,n-1){//每次高度是line[i+1].h-line[i].h,所以是到n-1就行
update(1,line[i].l,line[i].r,line[i].qz);//扫描线加入线段树
res += t[1].len*(line[i+1].h-line[i].h);
}
cout<<res<<endl;
}
//洛谷P5490 【模板】扫描线
//https://www.luogu.com.cn/problem/P5490
CDQ分治
处理三位偏序问题,以luogu陌上花开为例:https://www.luogu.com.cn/problem/P3810
序列上每个node有a,b,c三个属性,定义函数f(i)为对位置i的序列,存在多少个$j\leq i$使得$a_j\leq a_i,b_j\leq b_i,c_j\leq c_i$。统计完成后问对每个$0<d<n$,有多少个$i$使得$f(i)=d$.
方法是先对第一维度a进行排序,之后第二维满足$b_j\leq b_i$的每一对i,j,我们分成三种情况:ij都在左半边,ij都在右半边,i在左半边j在右半边。
前两个通过递归求解,求解完左右子区间后,根据第二关键字对两个子区间分别进行排序,这样一来左半边的a一定小于右半边的a,而且两个区间内的b都呈递增。我们用双指针的方法,右边的pxi每往右移动一次,左边的pxj对应移动,并且把这些j都推入树状数组中,之后根据pxi指向的节点的c属性,查询线段树中c比c[pxi]小的数量即可。
#include<iostream>
#include<algorithm>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
const int MAXN = 214514;
struct node{
int a,b,c,cnt,ans;
}s1[MAXN],s2[MAXN];
int ans[MAXN];//存对每个d,f[i]==d的i数量
int n,k,m;
//树状数组
int mx;
int treec[MAXN];
inline int lowbit(int x){return x&-x;}
inline void add(int x,int t){
while(x<=mx) treec[x]+=t,x+=lowbit(x);
}
inline int query(int x){
int ret = 0;
while(x) ret+=treec[x],x-=lowbit(x);
return ret;
}
//cdq分治
bool cmp1(node x,node y){//以a为关键字从小打到排序
if(x.a==y.a){
if(x.b==y.b) return x.c<y.c;
return x.b<y.b;
}
return x.a<y.a;
}
bool cmp2(node x,node y){//根据b排序,便只剩下c的顺序没有满足
if(x.b==y.b) return x.c<y.c;
return x.b<y.b;
}
void cdq(int l,int r){
if(l==r) return;
int mid = (l+r)>>1;
//处理前两种情况:ij同在左/右
cdq(l,mid);cdq(mid+1,r);
//处理第三种情况:i在左j在右
sort(s2+l,s2+mid+1,cmp2);
sort(s2+mid+1,s2+r+1,cmp2);
int pxj=l,pxi=mid+1;//双指针,对应pxi的移动把pxj插入树状数组中
for(pxi=mid+1;pxi<=r;pxi++){
while(s2[pxj].b<=s2[pxi].b&&pxj<=mid){
add(s2[pxj].c,s2[pxj].cnt);//带次数插入树状数组中
pxj++;
}
s2[pxi].ans+=query(s2[pxi].c);
//查询树状数组中c比他小的
}
rep(j,l,pxj-1) add(s2[j].c,-s2[j].cnt);//之前错在这里,注意只有pxj前面的才被加过,pxj没有
//遍历pxj走过的区间,清空树状数组
}
int main(){
cin>>n>>k;
m=0,mx=k;//树状数组的最大数字(类似权值线段树)
rep(i,1,n) cin>>s1[i].a>>s1[i].b>>s1[i].c;
sort(s1+1,s1+1+n,cmp1);//以a为关键字从小到大排序
int tmp=0;//当前这个项出现了几次
rep(i,1,n){//合并相同的项并且记录出现次数进cnt
tmp++;//当前项出现了几次
if(s1[i].a!=s1[i+1].a||s1[i].b!=s1[i+1].b||s1[i].c!=s1[i+1].c){//判断两个node是否不同
s2[++m]=s1[i];//m统计元素个数
s2[m].cnt = tmp;
tmp = 0;
}
}
cdq(1,m);//递归计算
//题目问的是f(x)=满足条件ij对数,对应每个x有几个,把结果存到ans[x]中
rep(i,1,m) ans[s2[i].ans+s2[i].cnt-1]+=s2[i].cnt;
//s2[i].cnt-1是这些数字自己之间满足的对数
rep(i,0,n-1) cout<<ans[i]<<endl;
}
//洛谷P3810陌上花开
//https://www.luogu.com.cn/problem/P3810