数据结构
栈
int strack[maxn];
int head;
bool b[maxn];
void push(int x)
{
strack[++head]=x;
b[x]=true;
};
int pop()
{
int ret;
ret=strack[head--];
b[ret]=false;
return ret;
};
bool empty()
{
return head>0;
}
队列
int queue[2*maxn];
int tail,head;
bool b[maxn];
void push(int x)
{
queue[++tail]=x;
bool[x]=true;
};
int pop()
{
int ret;
ret=queue[++head];
b[ret]=false;
return ret;
};
bool empty()
{
return head>=tail;
};
当然有的时候你手写的数据结构需要比较大的空间,这样队列就会造成很多损失,所以相应的就有两种解决方法:
一:STL;
二:循环队列,只需改两个地方(代码如下);
head=(head+1)%n+1;//把head++改
tail=(tail+1)%n+1;//把tail++改
树状数组
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
int sum[500005];
inline int lowbit (int x)
{
return x&-x;
}
int add (int k,int a)
{
while (k<=n)
{
sum[k]+=a;
k+=lowbit(k);
}
}
int out (int k)
{
int ans=0;
while(k)
{
ans+=sum[k];
k-=lowbit(k);
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
int a,b;
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
add(i,a);
}
for(int i=1;i<=m;i++)
{
scanf("%d",&a);
if (a==1)
{
scanf("%d%d",&a,&b);
add (a,b);
}
else
{
scanf("%d%d",&a,&b);
printf("%d\n",out(b)-out(a-1));
}
}
return 0;
}
应用模型:树状数组求逆序对
void update(int n)
{
while(n<=maxn)
{
c[n]+=1;
n+=lowbit(n);
};
};
main():
for(int i = 1; i <= n; ++i) //主程序里面加上这个
{
update(reflect[i]);
ans += i - getsum(reflect[i]);//reflect是离散化后的数组
}
STL
关于每个STL我只会写一下是什么,怎么用(举例子的形式),不会说的太细
Vector
不定长度数组
#include <vector>
vector<int> first; //第一种定义方法
int myints[]={16,2,77,29};
vector<int> second(myints,myints+4);//第二种定义方法
sort(second.begin(),second.end());//对vector排序
a=second[i];//可以这么使用
//以下是对vector的操作
Vector<int> opt;
opt.begin(); //返回起始地址
opt.end(); //返回结束地址
opt.size(); //返回大小
opt.empty(); //返回是否vector为空
opt.back(); //返回最后一个push进的数
opt.pop_back(); //把最后一个数弹出(不返回)
opt.push_back(int x);//把x从后面push进去
opt.erase(opt.begin(),opt.begin()+3);//删掉前三个元素
opt.erase(opt.begin()+5);//删掉第6个元素
Queue
队列,操作与Stack一样。
Priority_queue
相当于堆
#include <queue>
priority_queue<int> Bigheap;//定义一个大根堆
priority_queue<int,vector<int>,greater<int> > Smallheap;//定义一个小根对(注意两个尖括号间有空格)
//以下是操作
priority_queue<int> opt;
opt.top();//返回堆顶元素的值(不弹出)
opt.pop();//弹出堆顶元素(无返回值)
opt.push(x);
Stack
stack<int> opt;
opt.front();//返回
opt.size();
opt.empty();
opt.push();
opt.pop();//弹出
Deque
双向队,操作与Stack一样
Bitset
压位神器,只普及一下,不会用。
Set
set<int> first;
int myints[]= {10,20,30,40,50};
set<int> second (myints,myints+5);
set<int> third (second);
set<int> fourth (second.begin(), second.end());
third.rbegin(); third.rend();//rend相当于begin,rbegin相当于end
third.size();//返回大小
third.insert(60);
third.erase(it);
third.erase(50);//删除元素'50'
third.find(10);//找元素'10'
third.lower_bound(30); third.upper_bound(30);//'30'出现的第一个位置/最后一个位置
third.clear();//清除
Multiset
与Set用法一样,只是允许重复元素。
Map
map<char,int> first;
first[‘a’] = 10;
first.insert(make_pair(‘b’,20));
it++; ++it; it--; --it;
first.erase(1);//删除元素
firstt.count(1);//看有没有关系
Algorithm里其他好用的函数
Next_permutation
int a[]={1,2,3,4};
next_permutation(a,a+3);//下一个全排列
//现在a数组变成了:1 2 4 3
Lower_bound与Upper_bound
a=lower_bound(first,last,val)-a;//有返回值
upper_bound(first,last,val);
Merge
merge (first,first+5,second,second+5,v.begin(),compare);
sort
bool compare(int a,int b)
{
return a<b;
};//compare函数的例子
sort(起始地址,结束地址,compare函数)
;
Reverse
Reverse(myvector.begin(),myvector.end());
Unique
bool myfunction (int i, int j)
{
return (i==j);
}
unique(起始地址,结束地址,去重条件函数);//按照函数里面编写的规则去重,当然也可以没有第三个参数
数论
快速幂
计算 basen mod p
long long fast(long long base,long long n,long long p)
{
long long s=1;
while(n)
{
if(n&1)
s=(s*base)%p;
base=(base*base)%p;
n=n>>1;
}
return s;
}
矩阵快速幂
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define mod=1e9+7;
using namespace std;
long long n,k;
struct sq
{
long long a[110][110];
sq(){memset(a,0,sizeof(a));}
};
void pre(sq &x)
{
for (long long i=1;i<=n;i++)
x.a[i][i]=1;
}
sq operator * (const sq &x,const sq &y){
sq c;
for (long long i=1;i<=n;i++)
for (long long j=1;j<=n;j++)
for (long long p=1;p<=n;p++)
c.a[i][j]=(c.a[i][j]+x.a[i][p]*y.a[p][j])%mod;
return c;
}
int main(){
cin>>n>>k;
sq base;
for (long long i=1;i<=n;i++){
for (long long j=1;j<=n;j++){
scanf("%lld",&base.a[i][j]);
}
}
sq ans;
pre(ans);
while (k)
{
if (k&1) ans=ans*base;
k>>=1;
base=base*base;
}
for (long long i=1;i<=n;i++)
{
for (long long j=1;j<=n;j++)
printf("%lld ",ans.a[i][j]);
printf("\n");
}
return 0;
}
筛法求素数 :欧拉筛法
f[1]=1;
for(int i=2;i<=n;i++)
{
if(!f[i])
p[++num]=i;
for(int j=1;j<=num;j++)
{
if(p[j]*i>n)break;
f[p[j]*i]=1;
if(i%p[j]==0)break;
}
}
最大公约数和最小公倍数
gcd为最大公约数,lcm为最小公倍数
a∗b=gcd∗lcm;
int gcd(int a,int b)//注意此处a要大于b
{
return b==0?a:gcd(b,a%b);
}
int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
扩展欧几里德
求ax+by=c的特解<=>求 a∗x≡c(modb)
int x,y;
int exgcd(int a,int b)
{
int ret,t;
if(b==0)
{
x=1;
y=0;
return a;
}
ret=gcd(b,a%b);
t=y; y=x-(a%b)*y; x=t;
return ret;
}
欧拉函数(PHI φ)
高效算法同时打出prime表与phi表,比单独用筛法打出phi表与prime表快3倍左右
void prime_phi_table()
{
int i,j;
phi[1] = 1;
for(i = 2;i <= MAXN;i++)
{
if(!vis[i])
{
prime[++cnt] = i;
phi[i] = i-1;
}
for(j = 1;j <= cnt && i * prime[j] <= MAXN;j++)
{
vis[i * prime[j]] = true;
if(i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
求n的欧拉函数值
int phi(int n)
{
int t = sqrt(n + 0.5);
int ans = 1;
for(int i = 2;i <= t;i++)
{
if(n % i == 0)
{
ans *= (i-1);
n /= i;
while(n % i == 0)
{
ans *= i;
n /= i;
}
}
if(n == 1) break;
}
if(n > 1) ans *= (n-1);
return ans;
}
逆元
1. 扩展欧几里德求解
ab≡1(modp) ⇒ ab−1≡0(modp) ⇒ ab+pt=1 故可以用扩展欧几里德求解
#include <iostream>
#include <cstdio>
using namespace std;
long long x,y,a,b,ans;
long long gcd(long long a,long long b)
{
long long t,ret;
if(b==0)
{
x=1;
y=0;
return a;
}
ret=gcd(b,a%b);
t=y;
y=x-(a/b)*y;
x=t;
return ret;
}
int main()
{
cin>>a>>b;
ans=gcd(a,b);
while(x>b)x-=b;
while(x<0)x+=b;
cout<<x<<endl;
return 0;
}
2. 递推
for(int i=1;i<=maxn;i++)
inv[i]=(long long)(p-(p/i))*inv[p%i]%p;
Catalan数
原理理解:(两个应用模板)
括号化
矩阵连乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?(h(n-1)种)
出栈次序
一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?
详情请参照百度百科
int main() // 求第n个卡特兰数
{
cin>>n;
h[0]=1;h[1]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<=i-1;j++)
h[i]=h[i]+h[j]*h[i-j-1];
cout<<h[n];
return 0;
}
高精
读入、储存与输出
以下代码块均包含以下语句 :
int s[255];//255位的数
//读入与储存 :
void read()
{
char ss[255];
scanf("%s",ss);
for(int i=1;i<=strlen(ss);i++)
s[i]=ss[strlen(ss)-i+1]-'0';
s[0]=strlen(ss);//存长度
}
//输出 :
void print()
{
for(int i=s[0];i>=1;i++)
cout<<s[i];
}
高精度加法
高精加单精
void add(int *s,int x)//s存高精度数,x是要加的单精度
{
int k=1;
s[k]+=x;
while(s[k]>=10)
{
s[k+1]+=s[k]/10;
s[k++]%=10;
}
s[0]=k;
}
高精加高精
void add(int *s1,int *s2,int *ans)
{
int len;
len=max(s1[0],s2[0]);
for(int i=1;i<=len;i++)
{
ans[i]+=s1[i]+s2[i];
if(ans[i]>=10)
{
ans[i+1]+=ans[i]/10;
ans[i]%=10;
}
}
if(ans[len+1]!=0)len++;
ans[0]=len;
}
高精度乘法
高精乘单精
void multiply(int *s,int x)
{
for(int i=1;i<=n;i++)
{
c[i]+=s[i]*x;
c[i+1]+=(s[i]*b)/10;
c[i]%=10;
}
c[0]=s[0]+1;
while(c[c[0]]>=10)
{
c[c[0]+1]+=c[c[0]]/10;
c[c[0]]%=10;
c[0]++;
}
while(c[0]>1&&c[c[0]]==0)
{
c[0]--;
}
}
高精乘高精
void multiply(int *s1,int *s2,int *ans)
{
for(int i=1;i<=s1[0];i++)
for(itn j=1;j<=s2[0];j++)
{
ans[i+j-1]+=s1[i]*s2[j];
ans[i+j]+=ans[i+j-1]/10;
ans[i+j-1]%=10;
}
ans[0]=s1[0]+s2[0]+1;
while(ans[0]>1&&ans[ans[0]]==0)
{
ans[0]--;
}
}
高精度除法
放弃吧!
压位
const int opt=100000000;
void multiply(int *num,int x)
{
for(int i=1;i<=num[0];i++)num[i]*=x;
for(int i=1;i<=num[0];i++)
{
if(num[i]>=opt)
{
num[i+1]+=num[i]/opt;
num[i]%=opt;
};
while(num[num[0]]!=0)
{
num[0]++;
};
}
}
void print(int *a)
{
for(int i=a[0];i>=1;i--)
{
if(i==a[0])
{
cout<<a[i];
}else
{
if(a[i]<10000000)cout<<0;
if(a[i]<1000000)cout<<0;
if(a[i]<100000)cout<<0;
if(a[i]<10000)cout<<0;
if(a[i]<1000)cout<<0;
if(a[i]<100)cout<<0;
if(a[i]<10)cout<<0;
cout<<a[i];
};
};
}
⚠️:使用压位的时候的读入不要读错
比如不要把99存到数组的两个位置里面,而应该是一个;
图论
最短路
单源最短路SPFA
以下代码中包括邻接表(前向星)存图
#include<iostream>
#include<cstdio>
#define maxm 500005
#define maxn 10005
#define INF 0x7fffffff
using namespace std;
int n,m,s;
struct Edge
{
int next;
int to;
int w;
}e[maxm] ;
int q[maxm],h[maxn],ok[maxn],dis[maxn];
int p;
void add_edge(int from ,int to,int w)
{
e[++p].next=h[from];
e[p].to=to;
e[p].w=w;
h[from]=p;
}
int tail,head;
void SPFA()
{
for(int i=1;i<=n;i++)
dis[i]=INF;
dis[s]=0;
ok[s]=1;
q[tail++]=s;
while(head<tail)
{
int u=q[head++];
ok[u]=0;
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].to;
if(dis[u]+e[i].w<dis[v])
{
dis[v]=dis[u]+e[i].w;
if(!ok[v])
{
q[tail++]=v;
ok[v]=1;
}
}
}
}
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++)
{
int from,to,w;
scanf("%d%d%d",&from,&to,&w);
add_edge(from,to,w);
}
SPFA();
for(int i=1;i<=n;i++)
{
printf("%d ",dis[i]);
}
return 0;
}
双端队列优化版
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
#include<cstring>
using namespace std;
struct qq{
int t,w;
}c;
int s,n,m,u;
int dis[10005];
vector<qq>e[10005];
bool vis[10005]; deque<int>q;
void spfa(){
for(int i=1;i<=n;i++)dis[i]=2147483647;
dis[s]=0;q.clear(); q.push_back(s);
while(!q.empty()){
u=q.front();q.pop_front();vis[u]=0;
for(int i=0;i<e[u].size();i++){
c=e[u][i];
if(dis[c.t]>dis[u]+c.w){
dis[c.t]=dis[u]+c.w;
if(!vis[c.t]){
vis[c.t]=1;
if(q.empty()||dis[q.front()]<dis[c.t])q.push_back(c.t);
else q.push_front(c.t);
}
}
}
}
for(int i=1;i<=n;i++)printf("%d ",dis[i]);
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&c.t,&c.w);
e[u].push_back(c);
}
spfa();
return 0;
}
copy by lijialin
多源最短路FLOYD
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF 10000100
using namespace std;
int a[101],d[105][105];
int arrive[10005];
int main()
{
int n,m,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&d[i][j]);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",d[u][v]);
}
return 0;
}
次短路
代码比较麻烦,不写了,说一下思路吧。
先用SPFA跑一遍,找出来最短路。把最短路记下来。
接下来,每次删掉最短路上的一条边,再跑一边SPFA。
运行一遍以后,路径长度最小的即次短路。
最小生成树(MST)
最小生成树即一个无向连通不含圈图中,连接G中所有点,且边集是E的子集,且边权和最小的生成树。(我解释的有点拗口)
最小生成树算法一共有两个:Prim和Kruskal算法,由于经并查集优化的Kruskal算法比Prim算法优秀得多,且Prim算法较容易理解,这里只给出Kruscal算法的模板。
Kruskal
下面展现两种做法,一种是普通的暴力枚举做法,另一种是并查集优化过的。并查集优化过的算法比较快,但是要忽略生成树的形状。就是说如果你需要用到新生成树的形状,那么不能使用此种方法。
1.普通方法:类似Prim算法
struct node{int u,v,w;}e[maxe];//u是起点,v是终点,w是权
node MST[maxe];
bool com(node a,node b){return a.w<b.w;};
void Kruskal()
{
sort(e+1,e+m+1,com);//按边权从小到大排序
for(int i=1;i<=m;i++)
{
if(!b[e[i].u]&&!b[e[i].v])//b判断是否已经在集合里
MST[++tot]=e[i];
};
}
以上版本是自己写的,感觉不对,于是抄下来了粉书上的伪代码和讲解:
把所有边排序,记第i小的边为e[i](1<=i<m)
初始化MST为空
初始化连通分量,让每个点自成一个独立的连通分量
for(int i=1;i<=m;i++)
if(e[i].u和e[i].v不在同一个连通分量)
{
把边e[i]加入MST
合并e[i].u和e[i].v所在的连通分量
}
在上面的伪代码中,最关键的地方在于”连通分量的查询与合并”:需要知道任意两个点是否在同一个连通分量中,还需要合并两个连通分量。
最容易想到的方法是”暴力”——每次”合并”时只在MST中加入一条边(如果使用邻接矩阵,只需G[e[i].u][e[i].v]=1),而”查询”时直接在MST中进行图遍历(DFS和BFS都可以判断连通性)。
2.使用并查集优化
int find(int x){return p[x]==x?x:p[x]=find(p[x]);}//并查集的find和路径压缩
int Kruskal()//返回的最小生成树的边权和
{
int ans=0;
for(int i=1;i<=n;i++)p[i]=i;//初始化并查集
sort(edge+1,edge+m+1,com);//给边从小到大排序
for(int i=1;i<=m;i++)
{
int x=find(edge[i].u);
int y=find(edge[i].v);
if(x!=y)
{
ans+=edge[i].w;//求和
p[x]=y;
};//如果在不同的集合,合并
}
return ans;
}
其实此处还有一个优化,虽然不会节省很长时间,但是,优势都是一点点积累出来的!就是循环枚举边的时候不用for而用while,当当前得到的最小生成树一共有n-1条边时,最小生成树就已经生成完了,剩下的边就不用再枚举了。
TARJIAN
图的遍历
还有一点相关的东西就是传递闭包(Transitive Closure)
即在有向图中,有时不必关心路径长度,而只关心每两点间是否有通路,则可以用1和0分别表示”连通”和”不连通”。得到的结果称为有向图的传递闭包。
只需将FLOYD程序中的
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
改为
d[i][j]=d[i][j]||(d[i][k]&&d[k][j]));
建树
具体思路:对于一个节点来说,其他的任意一个节点,不是他的父节点,就是他的子节点。
传递闭包
详见上面图论部分Floyd算法。
LCA
#include<bits/stdc++.h>
#define MN 500005
using namespace std;
int m,n,s,tot,dep[MN],up[MN][20];
struct lll
{
int to;
lll *ne;
}a[2*MN];
lll *head[MN];
void DFS(int u)
{
for(int i=1;(1<<i)<=n;i++)
{
up[u][i]=up[up[u][i-1]][i-1];
}
for(lll *e=head[u];e;e=e->ne)
{
if(dep[e->to]==-1)
{
dep[e->to]=dep[u]+1;
up[e->to][0]=u;
DFS(e->to);
}
}
return;
}
int LCA(int u,int v)
{
if(dep[v]>dep[u]) swap(u,v);
for(int i=19;i>=0;i--)
{
if(dep[up[u][i]]>=dep[v])
u=up[u][i];
}
if(u==v) return v;
for(int i=19;i>=0;i--)
{
if(up[u][i]!=up[v][i])
{
u=up[u][i];
v=up[v][i];
}
}
return up[u][0];
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
a[++tot].ne=head[x];
head[x]=&a[tot];
a[tot].to=y;
a[++tot].ne=head[y];
head[y]=&a[tot];
a[tot].to=x;
}
memset(dep,-1,sizeof(dep));
dep[s]=0;
up[s][0]=0;
DFS(s);
for(int j=1;j<=m;j++)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",LCA(u,v));
}
}
copy by RWZ
线段树
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
struct cs
{
LL ll,rr,vv;
}T[824290];
LL a[200005],v[824290];
LL n,m,x,y,z,sum,N;
void clean(LL x)
{
if(!v[x])return;
T[x].vv+=(T[x].rr-T[x].ll+1)*v[x];
if(T[x].ll!=T[x].rr)
{
v[x*2]+=v[x];
v[x*2+1]+=v[x];
}
v[x]=0;
}
{
T[num].ll=x;
T[num].rr=y;
if(x==y) T[num].vv=a[x],return;
maketree(x,(x+y)/2,num*2);
maketree((x+y)/2+1,y,num*2+1);
T[num].vv=T[num*2].vv+T[num*2+1].vv;
}
void inc(LL x,LL y,LL z,LL num)
{
clean(num);
if(x<=T[num].ll&&T[num].rr<=y) v[num]+=z, return;
T[num].vv+=(min(y,T[num].rr)-max(x,T[num].ll)+1)*z;
if(T[num].ll==T[num].rr) return;
int mid=(T[num].ll+T[num].rr)/2;
if(x>mid)inc(x,y,z,num*2+1);
else
if(y<=mid)inc(x,y,z,num*2);else
{
inc(x,y,z,num*2);
inc(x,y,z,num*2+1);
}
}
void out(int LL,int LL,LL num)
{
clean(num);
if(x<=T[num].ll&&T[num].rr<=y) sum+=T[num].vv,return;
int mid=(T[num].ll+T[num].rr)/2;
if(x>mid)out(x,y,num*2+1);
else
if(y<=mid)out(x,y,num*2);
else
{
out(x,y,num*2);
out(x,y,num*2+1);
}
}
int main()
{
scanf("%d%d",&n,&N);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
maketree(1,n,1);
for(int i=1;i<=N;i++)
{
scanf("%d%d%d",&m,&x,&y);
if(m==1)
{
scanf("%d",&z);
inc(x,y,z,1);
}
else
{
sum=0;
out(x,y,1);
printf("%lld\n",sum);//用cout输出什么事都没有
}
}
}
树状数组
比线段树短啊
1. 单点修改,区间查询(codevs1080 线段树练习)
单点修改:
#define lowbit(x) x&(~x+1)
using namespace std;
int a[MAXN],tree[MAXN],n,q;
void add(int k,int x)
{
while(k <= n)
{
tree[k] += x;
k += lowbit(k);
}
}
区间查询:
int read(int k)
{
int sum = 0;
while(k)
{
sum += tree[k];
k -= lowbit(k);
}
return sum;
}
/*
printf("%d\n",read(b) - read(a-1));//输出
2.区间修改,单点查询(codevs1081 线段树练习 2)
修改和查询函数与上面是一样的,不过预处理的时候有所不同,这里只放主函数代码
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i ++)
{
scanf("%d",&a[i]);
add(i,a[i]);
add(i+1,-a[i]);
}
scanf("%d",&q);
int t;
for(int i = 1; i <= q; i ++)
{
scanf("%d",&t);
if(t == 1)
{
int a,b,x;
scanf("%d%d%d",&a,&b,&x);
add(a,x);
add(b+1,-x);
}
if(t == 2)
{
int a;
scanf("%d",&a);
printf("%d\n",read(a));
}
}
return 0;
}
3. 区间修改,区间查询(codevs1082 线段树练习 3)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#define MAXN 200000+5
#define lowbit(x) x&(~x+1)
using namespace std;
typedef long long ll;
ll a[MAXN],d1[MAXN],d2[MAXN],n,q;
void add(ll *d,ll k,ll x)
{
while(k <= n)
{
d[k] += x;
k += lowbit(k);
}
}
ll read(ll *d,ll k)
{
ll sum = 0;
while(k)
{
sum += d[k];
k -= lowbit(k);
}
return sum;
}
int main()
{
scanf("%lld",&n);
for(int i = 1; i <= n; i ++)
{
scanf("%lld",&a[i]);
add(d1,i,a[i]);
add(d1,i+1,-a[i]);
add(d2,i,i*a[i]);
add(d2,i+1,-a[i]*(i+1));
}
scanf("%lld",&q);
int t;
for(int i = 1; i <= q; i ++)
{
scanf("%d",&t);
if(t == 1)
{
int a,b,x;
scanf("%d%d%d",&a,&b,&x);
add(d1,a,x);
add(d1,b+1,-x);
add(d2,a,x*a);
add(d2,b+1,-x*(b+1));
}
if(t == 2)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%lld\n",((b+1)*read(d1,b)-read(d2,b))-(a*read(d1,a-1)-read(d2,a-1)));
}
}
return 0;
}
并查集
#include<iostream>
#include<cstdio>
using namespace std;
int x,y,n,m,q;
int fa[20020];
int find (int x)
{
if(fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void unionn(int x,int y)
{
int r1=find(x),r2=find(y);
if(r1!=r2) fa[r2]=r1;
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
unionn(x,y);
}
for(int i=1;i<=q;i++)
{
scanf("%d%d",&x,&y);
if(find(x)==find(y)) printf("Yes\n");
else printf("No\n");
}
return 0;
}
字符串
KMP
TRIE树
MANACHER
#include<iostream>
#include<cmath>
#include<cstring>
#define maxn 51000100
using namespace std;
int n,hw[maxn],ans;
char a[maxn],s[maxn<<2];//因为要插入字符所以*2,空间限制也很安全
void manacher()
{
int maxright=0,mid;
for(int i=1;i<n;i++)
{
if(i<maxright)
hw[i]=min(hw[2*mid-i],hw[mid]+mid-i);//这句话重点,好好理解
else
hw[i]=1;
for(;s[i+hw[i]]==s[i-hw[i]];++hw[i]);//继续扩展
if(hw[i]+i>maxright)
{
maxright=hw[i]+i;//更新
mid=i;
}
}
}
void change()
{
s[0]=s[1]='#';
for(int i=0;i<n;i++)
{
s[i*2+2]=a[i];
s[i*2+3]='#';
}
n=n*2+2;
s[n]=0;
}
int main()
{
scanf("%s",a);
n=strlen(a);
change();
manacher();
ans=1;
for(int i=0;i<n;i++)
ans=max(ans,hw[i]);
printf("%d",ans-1);//想想为什么要-1
return 0;
}
动态规划
钟长者说:有几个未知量,DP数组就有几维,若求个数能再省掉最后一维。
然而这只是一般情况,例如有个例外:HAOI2012 音量调节/Luogu P1877,这道题就不能省掉最后一维。
铭哥说:重推所有的DP方程是复习DP的最佳方法
01背包
for(int i=1;i<=m;i++)//m个物品
for(int j=1;j<=w;j++)//背包容量为w
if(a[i]<=j)
{
dp[i][j]=max(dp[i-1,j-a[i]]+val[i],dp[i-1,j]);
//a数组是占用的容量,val是价值
}else
{
dp[i][j]=dp[i-1][j];
};
//此时dp[m][w]即为最大权值
优化:
//条件和上面一样
for(int i=1;i<=n;i++)
for(int j=w;i>=a[i];j--)
dp[j]=(dp[j],dp[j-a[i]]+val[i]);
完全背包
//条件和上面一样,只是每个物品可以取无数次
for(int i=1;i<=n;i++)
for(int j=a[i];i<=w;j++)//注意这里的改动
dp[j]=(dp[j],dp[j-a[i]]+val[i]);
分组背包(多维背包)(多重限制背包(迷))
即有好几种背包的条件,分别写dp满足条件就可以了
其他模板
归并排序(逆序对)
#include<iostream>
#include<cstdio>
#define MAXN 100005
using namespace std;
int a[MAXN],tmp[MAXN];
int ans;
void mergesort(int l,int r)
{
if(l==r) return;
int mid=(l+r)/2;
mergesort(l,mid);
mergesort(mid+1,r);
int i=l,j=mid+1,pos=l;
while (i<=mid&&j<=r)
{
if(a[i]<=a[j])
{
tmp[pos++]=a[i++];
}
else
{
tmp[pos++]=a[j++];
ans+=(mid-i+1);
}
}
while (i<=mid)tmp[pos++]=a[i++];
while (j<=r)tmp[pos++]=a[j++];
for(int i=l;i<=r;i++)
{
a[i]=tmp[i];
}
return ;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
mergesort(1,n);
cout<<ans;
return 0;
}
二分
void binary(int l,int r) //找最小
{
while(r>l)
{
mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid+1;
}
ans=l;
}
void binary(int l,int r) //找最大
{
while(r>l)
{
mid=(r+l)/2+1; //特别注意
if(check(mid))l=mid+1;
else r=mid;
}
ans=l;
}
网络流(dinic)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 0x7fffffff
#define MAXN 10005
#define MAXM 100005
using namespace std;
struct EDGE
{
int next,to,w;
}e[MAXM<<1];
int p;
int q[MAXM+MAXN],h[MAXM];
int deep[MAXN];
inline void add_edge(int from,int to,int w)
{
e[++p].next=h[from];
e[p].to=to;
e[p].w=w;
h[from]=p;
}
int s,t;
inline bool bfs()
{
memset(deep,0,sizeof(deep));
int head=0,tail=0;
q[++tail]=s;
deep[s]=1;
while(head<tail)
{
int now=q[++head];
for(int i=h[now];i;i=e[i].next)
{
int v=e[i].to;
if(!deep[v]&&e[i].w)
{
deep[v]=deep[now]+1;
q[++tail]=v;
if(v==t) return true;
}
}
}
return false;
}
inline int dfs(int now,int cur_flow)
{
if(now==t) return cur_flow;
int rest=cur_flow;
for(int i=h[now];i;i=e[i].next)
{
int v=e[i].to;
if(deep[v]==deep[now]+1&&rest&&e[i].w)
{
int new_flow=dfs(v,min(rest,e[i].w));
rest-=new_flow;
e[i].w-=new_flow;
(i%2)?(e[i+1].w+=new_flow):(e[i-1].w+=new_flow);
}
}
return cur_flow-rest;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
ans+=dfs(s,inf);
}
return ans;
}
int main()
{
int n,m;
cin>>n>>m>>s>>t;
int from,to,w;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&from,&to,&w);
add_edge(from,to,w);
add_edge(to,from,0);
}
cout<<dinic();
return 0;
}
最长上升子序列
把楼下离散化去掉即可
最长公共子序列
离散化+最长上升子序列
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int a1[100010],a2[100010];
int belong[100010];
int f[100010],b[100010],len;
int main()
{
freopen("a.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a1[i]);
belong[a1[i]]=i;
}
for(int i=1;i<=n;i++)
scanf("%d",&a2[i]);
for(int i=1;i<=n;i++)
{
if(belong[a2[i]]>b[len])
{
b[++len]=belong[a2[i]];
f[i]=len;
continue;
}
int k=lower_bound(b+1,b+len+1,belong[a2[i]])-b;
b[k]=belong[a2[i]];
f[i]=k;
}
printf("%d\n",len);
return 0;
}
RMQ(区间最值)
#include<iostream>
#include<cstdio>
#define MAXN 100005
using namespace std;
int n,m;
long long a[MAXN],d[MAXN][18];
inline int read ()
{
int n=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9')
{
n=n*10+ch-'0';
ch=getchar();
}
return n;
}
inline void RMQ_pre()
{
for(int i=1;i<=n;i++)
{
d[i][0]=read();
}
for(int j=1;(1<<j)<=n;j++)//(1<<j)<=n
for(int i=1;i+(1<<j)-1<=n;i++)//i+(1<<j)-1<=n
d[i][j]=max(d[i][j-1],d[i+(1<<(j-1))][j-1]);
}
inline int RMQ(int l,int r)
{
int k=0;
while((1<<k)<=r-l+1) k++;
k--;
return max(d[l][k],d[r-(1<<k)+1][k]);
}
int main()
{
n=read();
m=read();
RMQ_pre();
for(int i=1;i<=m;i++)
{
int l=read(),r=read();
printf("%d\n",RMQ(l,r));
}
return 0;
}
TREAP
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
插入x数
删除x数(若有多个相同的数,因只删除一个)
查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
查询排名为x的数
求x的前驱(前驱定义为小于x,且最大的数)
求x的后继(后继定义为大于x,且最小的数)
#include <cstdio>
#include <cstdlib>
#include <ctime>
#define N 500005
using namespace std;
int inline read()
{
int 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;
}
int ch[N][2],val[N],pri[N],siz[N],sz;
void update(int x){siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]];}
int new_node(int v)
{
siz[++sz]=1;
val[sz]=v;
pri[sz]=rand();
return sz;
}
int merge(int x,int y)
{
if (!x || !y) return x+y;
if (pri[x]<pri[y])
{
ch[x][1]=merge(ch[x][1],y);
update(x);
return x;
}
else
{
ch[y][0]=merge(x,ch[y][0]);
update(y);
return y;
}
}
void split(int now,int k,int &x,int &y)
{
if (!now) x=y=0;
else
{
if (val[now]<=k)
x=now,split(ch[now][1],k,ch[now][1],y);
else
y=now,split(ch[now][0],k,x,ch[now][0]);
update(now);
}
}
int kth(int now,int k)
{
while(1)
{
if (k<=siz[ch[now][0]])
now=ch[now][0];
else
if (k==siz[ch[now][0]]+1)
return now;
else
k-=siz[ch[now][0]]+1,now=ch[now][1];
}
}
int main()
{
//srand((unsigned)time(NULL));
int T,com,x,y,z,a,b,root=0;
scanf("%d",&T);
while(T--)
{
com=read(),a=read();
if (com==1)
{
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
else
if (com==2)
{
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
else
if (com==3)
{
split(root,a-1,x,y);
printf("%d\n",siz[x]+1);
root=merge(x,y);
}
else
if (com==4)
printf("%d\n",val[kth(root,a)]);
else
if (com==5)
{
split(root,a-1,x,y);
printf("%d\n",val[kth(x,siz[x])]);
root=merge(x,y);
}
else
{
split(root,a,x,y);
printf("%d\n",val[kth(y,1)]);
root=merge(x,y);
}
}
return 0;
}
匈牙利算法
bool dfs(int now)
{
for (int a=1;a<=m;a++)
if (match[now][a] && !use[a])
{
use[a]=true;
if (!result[a] || dfs(result[a]))
{
result[a]=now;
return true;
}
}
return false;
}
void xiongyali()
{
int ans=0;
for (int a=1;a<=n;a++)
{
memset(use,false,sizeof(use));
if (dfs(a)) ans++;
}
}
对于变式题
ans
n-ans
m-ans
n+m-ans
总有一个是答案