目录
1.2.RMQ-ST:(RMQ)POJ3264 Balanced Lineup
1.3.1.构建线段树,区间更新(需要lazy),区间查询。(线段树--区间更新lazy)CDOJ1057 秋实大哥与花
1.3.2.单点更新,区间求最大值 (线段树入门--建树、单点更新、区间求最大值)hdu1754 I Hate It
2.1.最小生成树: (最小生成树问题:Prim,Kruskal)村村通公路
1.数据结构
1.1.树状数组:(树状数组)NYOJ116 士兵杀敌
树状数组是一种解决动态前缀和的数据结构,其实质是二进制拆分。树状数组单次查询的复杂度为O(log(n)).
可以解决的问题:区间和,单点更新,区间加,单点查询等问题。
对于区间加和单点查询是放在一起的概念,单点查询其实就是传统意义上的区间求和问题。
int lowbit(int x){ //返回二进制第一个非0数,例10100-->00100,110->010,001-->001
return x&(-x);
}
int sum(int x){ //查询前x项和,过程相当与对二进制的每一位进行拆分
//1101-->1100-->1000,13-->12-->8
int res=0;
while(x){
res+=d[x];
x-=lowbit(x);
}
return res;
}
void add(int x,int v){ //根据树状数组的结点规律初始化数组,例如,n==5时,x=1,对d[1]进行赋值,接着修改d[2],d[4]
while(x<=n){
d[x]+=v;
//cout<<"x="<<x<<",d["<<x<<"]="<<d[x]<<",lowbit(x)="<<lowbit(x)<<endl;
x+=lowbit(x);
}
}
1.2.RMQ-ST:(RMQ)POJ3264 Balanced Lineup
//ST(Sparse-Table)算法
void RMQ(){
for(int i=1 ;i<=n ;i++){
dp1[i][0]=a[i];
dp2[i][0]=a[i];
}
for(int j=1 ;(1<<j)<=n ;j++){
for(int i=1 ;i+(1<<j)-1<=n ;i++){
dp1[i][j] = max(dp1[i][j-1],dp1[i+(1<<(j-1))][j-1]);
dp2[i][j] = min(dp2[i][j-1],dp2[i+(1<<(j-1))][j-1]);
}
}
}
int k = (int)(log(y - x + 1.0) / log(2.0));
int ans1 = max(dp1[x][k],dp1[y-(1<<k)+1][k]);
int ans2 = min(dp2[x][k],dp2[y-(1<<k)+1][k]);
1.3.线段树(单点更新,区间更新):
1.3.1.构建线段树,区间更新(需要lazy),区间查询。(线段树--区间更新lazy)CDOJ1057 秋实大哥与花
//区间更新
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
int a[maxn];
struct Tree{
int l,r;
long long sum,lazy;
void update(long long x){
sum+=1LL*(r-l+1)*x; //sum+=1ll*(r-l+1)*x;也可 这里的1LL将int转化成long long
lazy+=x;
}
}tree[maxn<<2];
void push_up(int x){
tree[x].sum=tree[x<<1].sum+tree[x<<1|1].sum;
}
void push_down(int x){
int lazyval=tree[x].lazy;
if(lazyval){
tree[x<<1].update(lazyval);
tree[x<<1|1].update(lazyval);
tree[x].lazy=0;
}
}
void build(int x,int l,int r){
tree[x].l=l,tree[x].r=r;
tree[x].sum=tree[x].lazy=0;
if(l==r){
tree[x].sum=a[l];
return ;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
push_up(x);
}
void update(int x,int l,int r,long long val){
int L=tree[x].l,R=tree[x].r;
if(l<=L&&R<=r)
tree[x].update(val);
else{
push_down(x);
int mid=(L+R)>>1;
if(mid>=r) update(x<<1,l,r,val);
else if(l>mid) update(x<<1|1,l,r,val);
else{
update(x<<1,l,mid,val);
update(x<<1|1,mid+1,r,val);
}
push_up(x);
}
}
long long query(int x,int l,int r){
int L=tree[x].l,R=tree[x].r;
if(l<=L&&R<=r)
return tree[x].sum;
else{
long long ans=0;
push_down(x);
int mid=(L+R)>>1;
if(mid>=r) ans=query(x<<1,l,r);
else if(l>mid) ans=query(x<<1|1,l,r);
else ans=query(x<<1,l,mid)+query(x<<1|1,mid+1,r);
push_up(x);
return ans;
}
}
//略有不同,下面这两个函数,个人感觉有点难理解。 现在都可以理解了
/*
void update(int x,int l,int r,long long val){
int L=tree[x].l,R=tree[x].r;
if(l<=L&&R<=r)
tree[x].update(val); //更新子区间
else{
push_down(x);
int mid=(L+R)>>1; //注意这里的L,R是查询区间的边界
if(mid>=l) update(x<<1,l,r,val); //不同处
if(mid<r) update(x<<1|1,l,r,val);
push_up(x);
}
}
long long query(int x,int l,int r){
int L=tree[x].l,R=tree[x].r;
if(l<=L&&R<=r)
return tree[x].sum;
else{
push_down(x);
long long ans=0;
int mid=(L+R)>>1;
if(mid>=l) ans+=query(x<<1,l,r); //不同处
if(mid<r) ans+=query(x<<1|1,l,r);
push_up(x);
return ans;
}
}
*/
int main(){
int n,m;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
build(1,1,n);
scanf("%d",&m);
int l,r,v;
while(m--){
scanf("%d%d%d",&l,&r,&v);
update(1,l,r,v);
printf("%d\n",query(1,l,r));
}
return 0;
}
1.3.2.单点更新,区间求最大值 (线段树入门--建树、单点更新、区间求最大值)hdu1754 I Hate It
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 200005
#define inf 0x3f3f3f3f
struct Tree{
int left,right; //区间的端点
int max,sum; //视题目要求而定
}tree[maxn<<2];
int a[maxn];
void pushup(int id){
tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
tree[id].max=max(tree[id<<1].max,tree[id<<1|1].max);
}
void build(int id,int l,int r){ //构造线段树
tree[id].left=l,tree[id].right=r;
if(l==r){ //叶节点,直接更新mx[i]
tree[id].sum=a[r];
tree[id].max=a[r];
return;
}
int mid=(tree[id].left+tree[id].right)>>1;
build(id<<1,l,mid); //递归更新左子树
build(id<<1|1,mid+1,r); //递归更新右子树
pushup(id); //递归的过程,就是自底向上更新mx[i]的过程,在纸上模拟下就能体会到
}
void update(int id,int pos,int val){ //更新某个点的信息,并维护相关点的信息
if(tree[id].left==tree[id].right){ //对应的叶节点直接更新
tree[id].sum=tree[id].max=val;
return ;
}
int mid=(tree[id].left+tree[id].right)>>1;
if(pos<=mid) update(id<<1,pos,val);
else update(id<<1|1,pos,val);
pushup(id); //递归的过程,就是自底向上更新mx[i]的过程,在纸上模拟下就能体会到
}
int query(int id,int l,int r){ //查询[l,r]中的最大值
if(tree[id].left==l&&tree[id].right==r){ //当前结点完全包含在查询区间内
return tree[id].max;
}
int mid=(tree[id].left+tree[id].right)>>1;
//cout<<"mid="<<mid<<endl;
if(r<=mid) return query(id<<1,l,r); //往左走
else if(mid<l) return query(id<<1|1,l,r); //往右走
else return max(query(id<<1,l,mid),query(id<<1|1,mid+1,r));
}
int main()
{
int n,m;
int u,v;
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
build(1,1,n);
char q;
while(m--){
getchar();
scanf("%c%d%d",&q,&u,&v);
if(q=='Q'){
printf("%d\n",query(1,u,v));
}
else {
update(1,u,v);
}
}
}
return 0;
}
2.图论
2.1.最小生成树: (最小生成树问题:Prim,Kruskal)村村通公路
2.1.1.Prim
//这里更新的未加入最小生成树的点到最小生成树的距离,而迪杰斯特拉更新的是未加入集合的点经或不经过集合中的点到源点的距离
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1010;
int n,m,vis[maxn],d[maxn],mp[maxn][maxn];
long long ans;
bool Prim(){
memset(vis,0,sizeof(vis));
ans=0;
vis[1]=1; //加入顶点1
d[1]=0;
for(int i=2;i<=n;i++) //更新各点与最小生成树的距离
d[i]=mp[1][i];
int tmp;
for(int i=1;i<n;i++){ //还有n-1个顶点待加入
int mn=INF;
for(int j=2;j<=n;j++){
if(!vis[j]&&d[j]<mn){
mn=d[j];
tmp=j;
}
}
if(mn==INF) return false;
ans+=mn;
vis[tmp]=1;
for(int j=2;j<=n;j++){ //更新各点与最小生成树的距离
if(!vis[j]&&mp[tmp][j]<d[j]) d[j]=mp[tmp][j];
}
}
return true;
}
int main(){
while(~scanf("%d%d",&n,&m)){
int u,v,w;
memset(mp,0x3f,sizeof(mp));
for(int i=0;i<m;i++){
scanf("%d%d%d",&u,&v,&w);
mp[u][v]=mp[v][u]=w;
}
if(!Prim()) ans=-1;
printf("%lld\n",ans);
}
return 0;
}
2.1.2.Kruskal
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
using namespace std;
const int maxn=1010;
const int maxm=3010;
int n,m;
int par[maxn];
struct edge{
int from,to;
int w;
bool operator <(const edge &e)const{
return w<e.w;
}
}E[maxm];
int fi(int x){
if(par[x]!=x) return par[x]=fi(par[x]);
return x;
}
//非递归形式
/*
int fi(int t)
{
int r=t;
while(par[r]!=r)
r=par[r];
int i=t,j;
while(i!=r)//压缩路径
{
j=par[i];
par[i]=r;
i=j;
}
return r;
}
*/
bool Same(int x,int y){
return fi(x)==fi(y);
}
void join(int x,int y){
int fx=fi(x),fy=fi(y);
if(fx!=fy) par[fy]=fx;
}
long long kruskal(){
for(int i=1;i<=n;i++)//各顶点自成一个连通分量
par[i]=i;
long long res=0;
sort(E,E+m);
for(int i=0;i<m;i++){
if(Same(E[i].from,E[i].to)) continue;
join(E[i].from,E[i].to);
res+=E[i].w;
}
return res;
}
int main(){
while(scanf("%d%d",&n,&m)==2){
for(int i=0;i<m;i++){
scanf("%d%d%d",&E[i].from,&E[i].to,&E[i].w);
}
long long res=kruskal();
for(int i=1;i<=n;i++){
if(!Same(1,i)){
res=-1;
break;
}
}
printf("%lld\n",res);
}
return 0;
}
2.2.最短路径
2.2.1.Dijkdtra算法(单源最短路算法)
/*权值必须为非负值
单源最短路径,邻接矩阵形式,复杂度为O(N^2)
和最小生成树的Pime算法差不多,迪杰斯特拉更新的是未加入集合的点经或不经过集合中的点到源点的最短距离,
而Prim更新的未加入最小生成树的点到最小生成树的最短距离*/
#include<iostream>
using namespace std;
#define INF 10010
#define maxn 210
int n,m;
int maps[maxn][maxn];
bool visited[maxn];
int dist[maxn];
int dijkstra(int s,int t)
{
for(int i=0;i<n;i++){
dist[i]=maps[s][i];
visited[i]=false;
}
dist[s]=0;
visited[s]=true;
int temp,k;
for(int i = 0;i < n;i++)
{
temp = INF;
for(int j = 0;j < n;j++) //找出最小值
{
if(!visited[j]&& temp > dist[j])
{
k = j;
temp = dist[j];
}
}
if(temp == INF) //找完了
break;
visited[k] = 1;
for(int j = 0;j < n;j++)
dist[j]=min(dist[j],dist[k] + maps[k][j]);
}
if(dist[t] == INF)
return -1;
else
return dist[t];
}
int main()
{
while(cin>>n>>m){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
maps[i][j]=(i==j?0:INF);
}
}
int a,b,x,ans;
while(m--)
{
cin>>a>>b>>x;
if(x<maps[a][b])
maps[a][b]=maps[b][a]=x;
}
int s,t;
cin>>s>>t;
ans=dijkstra(s,t);
cout<<ans<<endl;
}
return 0;
}
2.2.2.Floyd算法(多源最短路算法)
Floyd算法用来找到每对点之间的最短距离,可以是有向图,也可以是无向图,边权可正可负,唯一要求是不能有负环。
Floyd算法的核心是松弛操作,松弛操作的原理就是著名的定理:“三角形两边之和大于第三边”,在信息学中我们叫它三角不等式。所谓对i,j进行松弛,就是判断是否d[j]>d[i]+w[i,j],如果该式成立,则将d[j]减小到d[i]+w[i,j],否则不动。
#include<bits/stdc++.h>
using namespace std;
const int maxn =205;
#define INF 1e9
int n,m;
int x,y,z;
int mp[maxn][maxn];
//floyd算法(动态规划思想,可处理负权)
void floyd()
{
for(int k=0;k<n;k++) //枚举中间点
for(int i=0;i<n;i++) //枚举起点
for(int j=0;j<n;j++) //枚举终点
mp[i][j]=min(mp[i][j],mp[i][k]+mp[k][j]); //松弛操作,经过k+1个点的i到j的最短路径
}
//数据输入处理
void init()
{
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
mp[i][j]=(i==j?0:INF);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
mp[x][y]=mp[y][x]=min(z,mp[y][x]);
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
int s,t;
scanf("%d%d",&s,&t);
floyd();
if(mp[s][t]==INF) cout<<"-1"<<endl;
else cout<<mp[s][t]<<endl;
}
return 0;
}
2.2.3.SPAF
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int maxn=205;
vector<pair<int,int> >E[maxn];
int n,m;
int d[maxn],inq[maxn];
void init(){
for(int i=0;i<maxn;i++) E[i].clear();
for(int i=0;i<maxn;i++) inq[i]=0;
for(int i=0;i<maxn;i++) d[i]=1e9;
}
int main(){
while(~scanf("%d%d",&n,&m)){
init();
for(int i=0;i<m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
E[x].push_back(make_pair(y,z));
E[y].push_back(make_pair(x,z));
}
int s,t;
scanf("%d%d",&s,&t);
queue<int> Q;
Q.push(s);
d[s]=0;
inq[s]=1;
while(!Q.empty()){
int now=Q.front();
Q.pop();
inq[now]=0;
for(int i=0;i<E[now].size();i++){
int v=E[now][i].first;
if(d[v]>d[now]+E[now][i].second){
d[v]=d[now]+E[now][i].second;
if(inq[v]==1) continue;
inq[v]=1;
Q.push(v);
}
}
}
if(d[t]==1e9) printf("-1\n");
else printf("%d\n",d[t]);
}
return 0;
}
2.3.二分图匹配
传送门:P3386 【模板】二分图匹配
二分图的最大匹配最常用的算法是匈牙利算法,即由增广路求最大匹配。
详解请点击右侧链接:趣写算法系列之--匈牙利算法
//二分图的最大匹配——匈牙利算法,即由增广路求最大匹配
//#define LOCAL
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1010;
int n,m,e,vis[maxn],link[maxn],mp[maxn][maxn];
bool match(int x){
for(int i=1;i<=m;i++){
if(mp[x][i]&&!vis[i]){
vis[i]=1;
if(link[i]==-1||match(link[i])){
link[i]=x;
return true;
}
}
}
return false;
}
int hungry(){
int tot=0;
memset(link,-1,sizeof(link));
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(match(i)) tot++;
}
return tot;
}
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d%d%d",&n,&m,&e);
int u,v;
memset(mp,0,sizeof(mp));
for(int i=0;i<e;i++){
scanf("%d%d",&u,&v);
if(u<=n&&v<=m)
mp[u][v]=1;
}
int cnt=hungry();
printf("%d\n",cnt);
return 0;
}