2019/07/31 做题记录
Elzat
2019-07-31 16:25:26
**不想再看到倍增和LCA了**
**这些题我也不想多说什么,看代码吧**
# [洛谷P3865](https://www.luogu.org/problem/P3865) 【模板】ST表
思想:**倍增**
### 预处理
$st[i][j]$表示从$i$开始的$2^{j}$个数的的最值
如$st[i][1]$表示的是$i$到$i+1$位置中两个数的最值
所以在转移时可以把当前的区间(平均)分成两个区间分别求最值
![ST表1](https://cdn.luogu.com.cn/upload/pic/67109.png)
### 查询
首先求区间长度的$log_{2}$(设为$k$)
然后再基于左右端点分别查询
![ST表2](https://cdn.luogu.com.cn/upload/pic/67110.png)
```cpp
#include<cstdio>
#include<algorithm>
#define maxn 500010
using namespace std;
int n;
int st[maxn][21];
int lg2[maxn];
void init()
{
for(int j = 1;(1 << j) <= n;j ++)
{
for(int i = 1;i + (1<<j) - 1 <= n;i ++)
{
st[i][j] = max(st[i][j-1],st[i +(1<<(j-1))][j-1]);
}
}
for(int i = 2;i <= n;i ++)
{
lg2[i] = lg2[i-1];
if((1 << (lg2[i] + 1)) == i) lg2[i] ++;
}
}
int query(int l,int r)
{
int k = lg2[r - l + 1];
return max(st[l][k],st[r - (1<<k) + 1][k]);
}
int main()
{
int m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++) scanf("%d",&st[i][0]);
init();
while(m --)
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}
```
# [洛谷P3379](https://www.luogu.org/problem/P3379)【模板】最近公共祖先(LCA)
我用tarjan做的这道题(具体请看[这篇博客](http://www.cnblogs.com/JVxie/p/4854719.html))
这里我只说一下针对这道题的改进
因为tarjan求最近公共祖先求出来的**顺序随机**且是在**所有询问输入完之后**去求的
那么就会想到用数组存储一下答案
但是看数据$N \leq 500000$
肯定不能用二维数组存了
那么我就新建了一个结构体$ques$
其中$u,v$表示有询问关系的两点
$num$表示该询问的序号(这是按顺序输出的关键)
$bro$表示与该询问内容相同的询问(因为在询问时要建双向边,而且询问$x,y$和$y,x$是相同的,所以在求到了其中一个之后另一个就不用求了)
$done$表示这个询问是否被求完了(默认$false$)
最后再用$ans$数组存起来就行了
```cpp
#include<cstdio>
using namespace std;
struct edge{
int u,v,w,next;
}e[1000010];
struct ques{
int u,v,next,num,bro;
bool done;
ques(){done = false;}
}E[1000010];
int ans[500010];
int head[500010],cnt,HEAD[500010],tot;
int f[500010],vis[500010];
void add(int u,int v)
{
e[++cnt].u = u;
e[cnt].v = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void add_q(int u,int v,int num) //双向边,所以两个点有两次建边过程
{
E[++tot].u = u;
E[tot].v = v;
E[tot].next = HEAD[u];
HEAD[u] = tot;
E[tot].num = num;
E[tot].bro = tot + 1;
E[++tot].u = v;
E[tot].v = u;
E[tot].next = HEAD[v];
HEAD[v] = tot;
E[tot].num = num;
E[tot].bro = tot - 1;
}
int find(int x)
{
if(x != f[x]) return f[x] = find(f[x]);
return x;
}
void merge(int x,int y)
{
x = find(x);
y = find(y);
f[y] = x;
}
void dfs(int x,int fa) //tarjan
{
for(int i = head[x];i;i = e[i].next)
{
int v = e[i].v;
if(v != fa) //防止回到父亲
{
dfs(v,x);
merge(x,v);
vis[v] = 1;
}
}
for(int i = HEAD[x];i;i = E[i].next)
{
int v = E[i].v;
if(vis[v] && !E[i].done)//!E[i].done的理由:若该询问(或其兄弟询问)做完了就不用再存了
{
ans[E[i].num] = find(v);
E[i].done = true; //若该询问做完了
E[E[i].bro].done = true; //那么其兄弟询问也就做完了
}
}
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i = 1;i < n;i ++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
f[i] = i;
}
f[n] = n;
for(int i = 1;i <= m;i ++)
{
int x,y;
scanf("%d%d",&x,&y);
add_q(x,y,i);
}
dfs(s,s);
for(int i = 1;i <= m;i ++)
printf("%d\n",ans[i]);
return 0;
}
```
# [洛谷P1613](https://www.luogu.org/problem/P1613) 跑路
整形$dis[i][j]$表示从$i$到$j$需要按跑路器的次数(也就是秒数,初始化无穷大)
布尔值$f[i][j][k]$表示$i$到$j$是否有长度为$2^{k}$的一条路
那么在输入$u,v$时 就可以设$dis[u][v] = 1$,$f[u][v][0]=true$(因为相邻两点距离为$1$,所以$u,v$间有一条长为$2^{0}$的路,所以只用按一次,也就是一秒就能从$u$到$v$)
那么再模仿$floyed$做一下假的“松弛”:
对于点$i$和$j$,如果有点$t$满足:
**$i$到$t$有一条长为$2^{k-1}$的路,$t$到$j$也有一条长为$2^{k-1}$的路,那么$i$到$j$就肯定有一条长为$2^{k}$的路(两条路相加),且因为该长度是$2$的次幂,所以只需按一次(用一秒)就能从$i$到$j$。**
最后再用真的$floyed$求一下最短路
```cpp
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dis[55][55]; //floyed没跑了
bool f[55][55][100];
int main()
{
memset(dis,0x3f,sizeof(dis));
int n,m;
scanf("%d%d",&n,&m);
while(m --)
{
int u,v;
scanf("%d%d",&u,&v);
dis[u][v] = 1;
f[u][v][0] = true;
}
for(int k = 1;k <= 64;k ++)
for(int i = 1;i <= n;i ++)
for(int t = 1;t <= n;t ++)
for(int j = 1;j <= n;j ++)
{
if(f[i][t][k-1] && f[t][j][k-1])
{
f[i][j][k] = true;
dis[i][j] = 1;
}
}
for(int k = 1;k <= n;k ++)
for(int i = 1;i <= n;i ++)
for(int j = 1;j <= n;j ++)
{
dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);
}
printf("%d",dis[1][n]);
return 0;
}
```
# [洛谷P1351](https://www.luogu.org/problem/P1351) 联合权值
因为两点间距离为$1$,所以**与同一个点连接的两点**肯定有联合权值
思路:
**用邻接表存储边 枚举所有点 若有两个或以上儿子就可以组合去求联合权值更新答案**
优化:**求每一段的和以及最大值,然后更新答案**
具体方法看洛谷题解
```cpp
#include<cstdio>
#include<algorithm>
using namespace std;
const int mod = 10007;
struct edge{
int v,next;
}e[400010];
int head[200010],cnt;
int w[200010];
void add(int u,int v)
{
e[++cnt].v = v;
e[cnt].next = head[u];
head[u] = cnt;
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i < n;i ++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i = 1;i <= n;i ++) scanf("%d",&w[i]);
int sum0,max0;
int maxn = 0,sum = 0;
for(int i = 1;i <= n;i ++)
{
int j = head[i]; //第一个儿子
sum0 = w[e[j].v] % mod; //预设一下这一段的和以及最大值
max0 = w[e[j].v];
for(j = e[j].next;j;j = e[j].next) //若有其他儿子就会进这个循环
{
int v = e[j].v;
sum = (sum + sum0 * w[v]) % mod;//这里利用乘法结合律,可省时间
maxn = max(maxn,max0 * w[v]);
sum0 = (sum0 + w[v]) % mod;
max0 = max(max0,w[v]);
}
}
printf("%d %d",maxn,(sum * 2) % mod);//最后的和要乘2
return 0;
}
```
# [洛谷P1967](https://www.luogu.org/problem/P1967) 货车运输
**步骤:**
1.求最大生成树
2.LCA(倍增求法)
3.在LCA过程中更新答案
```cpp
#include<cstdio>
#include<algorithm>
using namespace std;
const int inf = 0x3f3f3f;
struct edge{
int u,v,w,next;
}e[500010],E[500010];
int head[100010],cnt;
int fa[100010][21],w[100010][21];
int dep[100010],vis[100010];
void add(int u,int v,int w)
{
E[++cnt].u = u;
E[cnt].v = v;
E[cnt].w = w;
E[cnt].next = head[u];
head[u] = cnt;
}
//lca 初始化
void init(int n)
{
for(int j = 1;j <= 20;j ++)
{
for(int i = 1;i <= n;i ++)
{
fa[i][j] = fa[fa[i][j-1]][j-1];
w[i][j] = min(w[i][j-1],w[fa[i][j-1]][j-1]);
}
}
}
void dfs(int x) //预设最初的w和fa
{
vis[x] = 1;
for(int i = head[x];i;i = E[i].next)
{
int v = E[i].v;
if(vis[v]) continue;
dep[v] = dep[x] + 1;
fa[v][0] = x;
w[v][0] = E[i].w;
dfs(v);
}
}
void work(int n)
{
for(int i = 1;i <= n;i ++)
{
if(!vis[i])
{
dep[i] = 1;
dfs(i);
fa[i][0] = i;
w[i][0] = inf;
}
}
}
//并查集
int f[100010];
int find(int x)
{
if(f[x] != x) return f[x] = find(f[x]);
return x;
}
//最大生成树
bool cmp(edge a,edge b)
{
return a.w > b.w;
}
void kruskal(int n,int m)
{
sort(e+1,e+m+1,cmp);
for(int i = 1;i <= n;i ++) f[i] = i;
int k = 0;
for(int i = 1;i <= m;i ++)
{
int u = e[i].u,v = e[i].v,w = e[i].w;
if(find(u) != find(v))
{
add(u,v,w);
add(v,u,w);
v = find(v);
u = find(u);
f[v] = u;
k ++;
}
if(k == n - 1) break;
}
}
//lca
int lca(int x,int y)
{
if(find(x) != find(y)) return -1;
if(dep[x] < dep[y]) swap(x,y);
int ans = inf;
for(int j = 20;j >= 0;j --)
{
if(dep[fa[x][j]] >= dep[y])
{
ans = min(ans,w[x][j]);
x = fa[x][j];
}
}
if(x == y) return ans;
for(int j = 20;j >= 0;j --)
{
if(fa[x][j] != fa[y][j])
{
ans = min(ans,min(w[x][j],w[y][j]));
x = fa[x][j];
y = fa[y][j];
}
}
ans = min(ans,min(w[x][0],w[y][0]));
return ans;
}
int main()
{
int n,m,q;
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i ++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[i].u = u;
e[i].v = v;
e[i].w = w;
}
kruskal(n,m);
work(n);
init(n);
scanf("%d",&q);
for(int i = 1;i <= q;i ++)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",lca(u,v));
}
return 0;
}
```
今天做题做的心累,不想写更详细的了,如不清楚就请看其他dalao的题解吧。
BY Elzat