文章目录
HDU2586 How far away ?
题目大意
T个测试样例,每个测试样例给定一个N(N间房屋)和M(M个查询),接下来N-1行会给出三个数i,j,k,表示房间 i 到房间 j 的距离为 k。再接下来M行分别会给出两个数,表示需要查询的两个房屋。(T<=10,2<=n<=40000,1<=m<=200,0<k<=40000)
道路的建造方式是每两栋房屋之间都有一条唯一的简单路径(“简单”意味着您不能两次访问某个地方)
问:对于M个询问,每个询问的两个房屋之间的距离为多少?
解题思路
求树上两点距离可用LCA求解。
distance(u,v) = dis[u] + dis[v] - 2 * dis[LCA(u,v)]
其中 distance 是树上两点间的距离,dis 代表某点到树根的距离。
AC代码【倍增 + LCA】
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 4e4 + 5;
const int MAXM = 4e4 + 5;
const int INF = 0x3f3f3f3f;
int n,m,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN],dis[MAXN];
struct Node {
int to,w,pre;
}edge[MAXM << 1];
void init() {
memset(head, -1, sizeof(head));
memset(dp, 0, sizeof(dp));
memset(dis, 0, sizeof(dis));
memset(deep, 0, sizeof(deep));
node = 0;
N = (int)log2(n);
}
void add_edge(int u, int v, int w) {
edge[node].to = v;
edge[node].w = w;
edge[node].pre = head[u];
head[u] = node++;
}
void dfs(int now, int fa) {
dp[now][0] = fa;
for (int i = 1; (1 << i) <= deep[now]; i++) {
dp[now][i] = dp[dp[now][i - 1]][i - 1];
}
for (int i = head[now]; ~i; i = edge[i].pre) {
int v = edge[i].to;
if (v != fa) {
deep[v] = deep[now] + 1;
dis[v] = dis[now] + edge[i].w;
dfs(v,now);
}
}
}
int lca(int u, int v) {
if (deep[u] < deep[v]) {
swap(u,v);
}
int h = deep[u] - deep[v];
for (int i = 0, j = (1 << i); j <= h; i++, j = (1 << i)) {
if (h & j) {
u = dp[u][i];
}
}
if (u == v) {
return u;
}
for (int i = N; i >= 0; i--) {
if (dp[u][i] != dp[v][i]) {
u = dp[u][i];
v = dp[v][i];
}
}
return dp[u][0];
}
void solve() {
int u,v,w;
scanf("%d%d",&n,&m);
init();
for (int i = 1; i < n; i++) {
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
dfs(1,0);
for (int i = 0; i < m; i++) {
scanf("%d%d",&u,&v);
printf("%d\n",dis[u] + dis[v] - 2 * dis[lca(u,v)]);
}
}
int main() {
int T;
cin >> T;
for (int i = 1; i <= T; i++) {
solve();
}
return 0;
}
HDU2874 Connections between cities
传送门:HDU2874 Connections between cities
题目大意
n个城市,m条路,c个查询(2<=n<=10000, 0<=m<10000, 1<=c<=1000000)。两个城市之间可能没有道路,也无环。问:两个城市之间是否存在道路,如果不存在输出"Not connected",否则输出最短路径。
解题思路
求森林中两点距离。还是可以用LCA,但是需要额外判断两点是否连通。
判断两点是否连通一般是使用并查集维护集合。但是这里我们在使用LCA预处理dp数组时已经记录了某个结点的父亲结点是谁,当某个结点父亲结点是0时,说明它不在已知集合之中,则可以以它为根继续dfs建树。
当查询LCA时,如果两个点的LCA(u,v) == 0,说明这两个点在不同集合,那么自然也就不连通了。
AC代码【倍增 + LCA】
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 1e4 + 5;
const int MAXM = 1e4 + 5;
const int INF = 0x3f3f3f3f;
int n,m,c,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN];
ll dis[MAXN];
struct Node {
int to,w,pre;
}edge[MAXM << 1];
void init() {
memset(head, -1, sizeof(head));
memset(dp, 0, sizeof(dp));
memset(dis, 0, sizeof(dis));
memset(deep, 0, sizeof(deep));
node = 0;
N = (int)log2(n);
}
void add_edge(int u, int v, int w) {
edge[node].to = v;
edge[node].w = w;
edge[node].pre = head[u];
head[u] = node++;
}
void dfs(int now, int fa) {
dp[now][0] = fa;
for (int i = 1; (1 << i) <= deep[now]; i++) {
dp[now][i] = dp[dp[now][i - 1]][i - 1];
}
for (int i = head[now]; ~i; i = edge[i].pre) {
int v = edge[i].to;
if (v != fa) {
deep[v] = deep[now] + 1;
dis[v] = dis[now] + edge[i].w;
dfs(v,now);
}
}
}
int lca(int u, int v) {
if (deep[u] < deep[v]) {
swap(u,v);
}
int h = deep[u] - deep[v];
for (int i = 0, j = (1 << i); j <= h; i++,j = (1 << i)) {
if (h & j) {
u = dp[u][i];
}
}
if (u == v) {
return u;
}
for (int i = N; i >= 0; i--) {
if (dp[u][i] != dp[v][i]) {
u = dp[u][i];
v = dp[v][i];
}
}
return dp[u][0];
}
void solve() {
int u,v,w;
while (~scanf("%d%d%d",&n,&m,&c)) {
init();
for (int i = 0; i < m; i++) {
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
for (int i = 1; i <= n; i++) {
if (!dp[i][0]) {
// 点 i 不在已知集合中,以 i 为根 dfs 建树
dfs(i,0);
}
}
for (int i = 0; i < c; i++) {
scanf("%d%d",&u,&v);
int l = lca(u,v);
if (l == 0) {
// lca(u, v) == 0 说明 u,v不在同一个集合,即不连通
puts("Not connected");
} else {
printf("%lld\n",dis[u] + dis[v] - 2 * dis[l]);
}
}
}
}
int main() {
solve();
return 0;
}
CodeForces1328E Tree Queries
传送门:CodeForces1328E Tree Queries
题目大意
给你n个顶点n-1条边的树,然后有m个查询,查询由一个数k和k个数组成,问是否存在从根到某个顶点u的路径,以使给定的k个顶点中的每个顶点都属于该路径,或者与该路径上的某个顶点之间的距离为1。
解题思路
对于题目要求的路径,可以看做以根结点为起点,深度最深的点为终点,然后判断其他点是否在这条路径上,或者距离这条路径上某个点距离为1。
怎么判断呢?找到某个点u和深度最深的点v的LCA(u,v),他们的LCA(u,v)必定在路径上,那么当deep[u] - deep[LCA(u,v)] <= 1时,就说明点u在路径上(u == LCA(u,v))或者离路径上的点距离为1。 当所有的点u都满足条件时为YES,有一个点不满足则为NO。
AC代码【倍增 + LCA + 思维】
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 2e5 + 5;
const int MAXM = 2e5 + 5;
const int INF = 0x3f3f3f3f;
int n,m,c,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN],a[MAXN];
struct Node {
int to,pre;
}edge[MAXM << 1];
void init() {
memset(head, -1, sizeof(head));
memset(dp, 0, sizeof(dp));
memset(deep, 0, sizeof(deep));
node = 0;
N = (int)log2(n);
}
void add_edge(int u, int v) {
edge[node].to = v;
edge[node].pre = head[u];
head[u] = node++;
}
void dfs(int now, int fa) {
dp[now][0] = fa;
for (int i = 1; (1 << i) <= deep[now]; i++) {
dp[now][i] = dp[dp[now][i - 1]][i - 1];
}
for (int i = head[now]; ~i; i = edge[i].pre) {
int v = edge[i].to;
if (v != fa) {
deep[v] = deep[now] + 1;
dfs(v,now);
}
}
}
int lca(int u, int v) {
if (deep[u] < deep[v]) {
swap(u,v);
}
int h = deep[u] - deep[v];
for (int i = 0, j = (1 << i); j <= h; i++,j = (1 << i)) {
if (h & j) {
u = dp[u][i];
}
}
if (u == v) {
return u;
}
for (int i = N; i >= 0; i--) {
if (dp[u][i] != dp[v][i]) {
u = dp[u][i];
v = dp[v][i];
}
}
return dp[u][0];
}
void solve() {
int u,v,k;
while (~scanf("%d%d",&n,&m)) {
init();
for (int i = 1; i < n; i++) {
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
for (int i = 0; i < m; i++) {
scanf("%d",&k);
// vertices 存储深度最深的结点
int vertices = 0;
for (int j = 0; j < k; j++) {
scanf("%d",&a[j]);
if (deep[a[j]] > deep[vertices]) {
vertices = a[j];
}
}
int flag = 1;
for (int j = 0; j < k; j++) {
int l = lca(a[j],vertices);
// 点 a[j] 到路径上的点距离大于 1,不符合题意
if (deep[a[j]] - deep[l] > 1) {
puts("NO");
flag = 0;
break;
}
}
if (flag) {
puts("YES");
}
}
}
}
int main() {
solve();
return 0;
}
CodeForces1304E 1-Trees and Queries
传送门;CodeForces1304E 1-Trees and Queries
题目大意
给你有n个顶点n-1条边的树,然后有q个查询。 每个查询包含5个整数:x,y,a,b和k。 意为在顶点x和y之间添加双向边后,问是否存在从顶点a到b的路径,该路径恰好包含k个边。 路径可以多次包含相同的顶点和相同的边。 所有查询彼此独立,也就是说,在下一个查询时删除了以往查询添加的边。
解题思路
对于给定的询问,可能存在三种路径:
1、a->b
2、a->x->y->b
3、a->y->x->b
对于三条路径任意一条满足距离小于等于k且和k同奇偶性为YES,三条都不满足为NO。
距离小于等于k且同奇偶性是为了可以反复走某一路径使得凑够k条边。
AC代码【倍增 + LCA + 思维】
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false),cin.tie(NULL),cout.tie(NULL);
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 1e5 + 5;
const int MAXM = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int n,m,c,node,N;
int head[MAXN],dp[MAXN][30],deep[MAXN],a[MAXN];
struct Node {
int to,pre;
}edge[MAXM << 1];
void init() {
memset(head, -1, sizeof(head));
memset(dp, 0, sizeof(dp));
memset(deep, 0, sizeof(deep));
node = 0;
N = (int)log2(n);
}
void add_edge(int u, int v) {
edge[node].to = v;
edge[node].pre = head[u];
head[u] = node++;
}
void dfs(int now, int fa) {
dp[now][0] = fa;
for (int i = 1; (1 << i) <= deep[now]; i++) {
dp[now][i] = dp[dp[now][i - 1]][i - 1];
}
for (int i = head[now]; ~i; i = edge[i].pre) {
int v = edge[i].to;
if (v != fa) {
deep[v] = deep[now] + 1;
dfs(v,now);
}
}
}
int lca(int u, int v) {
if (deep[u] < deep[v]) {
swap(u,v);
}
int h = deep[u] - deep[v];
for (int i = 0, j = (1 << i); j <= h; i++,j = (1 << i)) {
if (h & j) {
u = dp[u][i];
}
}
if (u == v) {
return u;
}
for (int i = N; i >= 0; i--) {
if (dp[u][i] != dp[v][i]) {
u = dp[u][i];
v = dp[v][i];
}
}
return dp[u][0];
}
int dis(int u, int v) {
return deep[u] + deep[v] - 2 * deep[lca(u,v)];
}
bool check(int len, int k) {
return (len <= k) && (len % 2 == k % 2);
}
void solve() {
int u,v,x,y,a,b,k;
while (~scanf("%d",&n)) {
init();
for (int i = 1; i < n; i++) {
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1,0);
scanf("%d",&m);
for (int i = 0; i < m; i++) {
scanf("%d%d%d%d%d",&x,&y,&a,&b,&k);
if (check(dis(a,b),k) || check(dis(x,a) + dis(y,b) + 1,k) || check(dis(y,a) + dis(x,b) + 1,k)) {
puts("YES");
} else {
puts("NO");
}
}
}
}
int main() {
solve();
return 0;
}