可能会有错误,路过大神请见谅我个蒟蒻!
如果侵犯他人合法权益,请与我联系删除。
4.17总结
今天,老师说好给我们补补之前的题目,复习一下之前的知识,但是莫名其妙搞出来个比赛,还是老师的老师叫学生创的,就被迫参加了,取得了个不错的成绩。(第一次完成比赛全部题目,开心!)
今天我们比赛的主要内容是LCA、倍增和并查集。
第一题:最近公共祖先二
这题之前我没有见过,但是跟LCA的基本做法还是一样的。这题是有根树,第一个出现的名字就是根,然后我们就要把字符串转换成编号,好办法就是hash,但是我们有STL的神器map,把字符串映射成一个编号,这题就变成LCA的模板题。LCA好像有两个写法,一种是树链剖分的LCA,一种是倍增的LCA,我的是后者,深度还是dfs出来的,大神是倍增出来的,太强了。
这题我还被自己坑了一把,把名字装进name数组时,没有用大括号括起来,结果就算n没有改变,name就修改成另一个了,坑。
Code:
#include <map>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
using namespace std;
const int maxN = 100010, maxM = 100010, maxcf = 30;
int N, M, las[maxN], n, fat[maxN][maxcf], dep[maxN];
string fa, son, name[maxN], a, b;
struct edge {
int v, ltb;
}E[maxM];
map <string, int> id;
void dfs(int, int);
string LCA(int, int);
int main() {
scanf("%d", &N);
memset(las, -1, sizeof(las));
for (int i = 0; i < N; i++) {
cin >> fa >> son;
if (!id[fa]) {
id[fa] = ++n;
name[n] = fa;
}
if (!id[son]) {
id[son] = ++n;
name[n] = son;
}
E[i].v = id[son];
E[i].ltb = las[id[fa]];
las[id[fa]] = i;
fat[id[son]][0] = id[fa];
}
for (int k = 1; k < 20; k++)
for (int i = 1; i <= n; i++) fat[i][k] = fat[fat[i][k-1]][k-1];
dfs(1, 1);
scanf("%d", &M);
for (int i = 0; i < M; i++) {
cin >> a >> b;
cout << LCA(id[a], id[b]) << endl;
}
}
void dfs(int r, int d) {
dep[r] = d;
for (int i = las[r]; i != -1; i = E[i].ltb) dfs(E[i].v, d+1);
}
string LCA(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int dec = dep[x]-dep[y];
for (int i = 19; i >= 0; i--) if (dec&(1<<i))
x = fat[x][i];
if (x == y) return name[y];
for (int i = 19; i >= 0; i--) if (fat[x][i] != fat[y][i]) {
x = fat[x][i];
y = fat[y][i];
}
return name[fat[x][0]];
}
第二题:Distance Queries
这题嘛,其实很不严谨,有多少个农场没说,有多少条边也没说,我看来一把网上题解就照抄范围了,我假设最多有100,000个农场,农场数-1是边数。方向其实没有什么用的,就忽略了,这题就变成一个带权LCA,其实可以修改模板就做出来了。
Code:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 100000, maxM = 200000, maxcf = 30;
char ch;
int n, m, u, v, c, K, a, b, M;
int las[maxn], dep[maxn];
bool vis[maxn];
struct ansnode {
int fa, c;
}ans[maxn][maxcf];
struct edge {
int v, c, ltb;
}E[maxM];
void adde(int, int, int);
void dfs(int, int);
int LCA(int, int);
int main() {
scanf("%d%d", &n, &m);
memset(las, -1, sizeof(las));
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &u, &v, &c);
cin >> ch;
adde(u, v, c);
adde(v, u, c);
}
vis[1] = 1;
dfs(1, 1);
for (int k = 1; k < 20; k++)
for (int i = 1; i <= n; i++) {
ans[i][k].fa = ans[ans[i][k-1].fa][k-1].fa;
ans[i][k].c = ans[i][k-1].c+ans[ans[i][k-1].fa][k-1].c;
}
scanf("%d", &K);
while (K--) {
scanf("%d%d", &a, &b);
printf("%d\n", LCA(a, b));
}
return 0;
}
void adde(int u, int v, int c) {
E[M].v = v;
E[M].c = c;
E[M].ltb = las[u];
las[u] = M++;
}
void dfs(int r, int d) {
dep[r] = d;
for (int i = las[r]; i != -1; i = E[i].ltb) if (!vis[E[i].v]){
vis[E[i].v] = 1;
ans[E[i].v][0].fa = r;
ans[E[i].v][0].c = E[i].c;
dfs(E[i].v, d+1);
}
}
int LCA(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int dec = dep[x]-dep[y], sum = 0;
for (int i = 19; i >= 0; i--) if (dec&(1<<i)) {
sum += ans[x][i].c;
x = ans[x][i].fa;
}
if (x == y) return sum;
for (int i = 19; i >= 0; i--) if (ans[x][i].fa != ans[y][i].fa) {
sum += ans[x][i].c;
sum += ans[y][i].c;
x = ans[x][i].fa;
y = ans[y][i].fa;
}
return sum+ans[x][0].c+ans[y][0].c;
}
第三题:Cow Relays
这题啊,可谓是道大难题,一开始我想bfs强行骗分,但无奈是ACM模式,没有部分分,就没有写。这题我们自然而然地发现这是最短路径算法,能用的只有spfa和floyd,通过分析,我们发现题目最多只有200个点,当然是选择floyd啦。其实这道题如果想要思路明确的话是选择记忆化搜索,状态描述为f【n】【i】【j】表示为经过n条边i到j的最小值是多少。我们可以写出状态转移方程,把f【n】【i】【j】=min(f【n>>1】【i】【k】,f【n>>1】【k】【j】)注意处理好细节,很多坑,对于说会爆空间的想法,我们可以预处理n的各种情况,或者用map映射,都可以。但是我的做法不一样,我是用floyd+矩阵乘法+快速幂。floyd可以理解吧,为什么关矩阵乘法什么事呢?众所周知,floyd是for(int k = 1; k <= n; k++)for(int i = 1; i < n; i++) for(int j = 1; j <= n; j++),那么我们可以理解为对于a矩阵i行k列加上b矩阵k行j列得到c矩阵i行j列。初始化时矩阵时读入的矩阵(i到j是经过1条边最短路径),不断乘初始状态(就是做floyd)(边数+1),乘n-1次(最终得经过n条边)就是答案了。但是会超时,我们就用快速幂优化,就ok了。(各位慢慢理解吧,哈哈哈,不会请百度!)(我一开始也是搜题解,实在想不到!)
Code:
#include <map>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 210;
int N, T, S, E, length, u, v, n;
map <int, int> id;
struct state {
int cost[maxn][maxn];
state operator * (state &r) {
state c;
memset(c.cost, 0x3f, sizeof c.cost);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
c.cost[i][j] = min(c.cost[i][j], cost[i][k] + r.cost[k][j]);
return c;
}
}tmp, ans;
int main() {
scanf("%d%d%d%d", &N, &T, &S, &E);
memset(tmp.cost, 0x3f, sizeof tmp.cost);
while (T--) {
scanf("%d%d%d", &length, &u, &v);
if (!id[u]) u = id[u] = ++n;
else u = id[u];
if (!id[v]) v = id[v] = ++n;
else v = id[v];
tmp.cost[u][v] = tmp.cost[v][u] = length;
}
ans = tmp;
N--;
while (N) {
if (N&1) ans = ans*tmp;
tmp = tmp*tmp;
N >>= 1;
}
printf("%d", ans.cost[id[S]][id[E]]);
return 0;
}
第四题:Cube Stacking
这题就是带权并查集,但是我一开始思考错方向,移动操作时,把X所在的栈移动到Y的栈顶,但是我想题时就把Y所在的栈移动到X所在的栈底,结果硬是想不出怎么做,惨。后来,去问问人,翻翻题解,才理解出来,就AC了。如果按照题目的做法,用ans【i】表示i到集合的跟结点的距离是多少,cnt【i】表示i为跟结点的总方块数。而i到跟集合根结点的距离就是答案啦。而按照我的想法,就是很难实现的(大家可以想想两者的区别)。
Code:
#include <cstdio>
#include <iostream>
using namespace std;
void _union();
int getfa(int);
const int maxn = 30010;
char ch;
int P, X, Y, fa[maxn], cnt[maxn], ans[maxn];
int main() {
scanf("%d", &P);
for (int i = 1; i < maxn; i++) {
fa[i] = i;
cnt[i] = 1;
ans[i] = 0;
}
while (P--) {
cin >> ch;
if (ch == 'M') {
scanf("%d%d", &X, &Y);
_union();
}
if (ch == 'C') {
scanf("%d", &X);
getfa(X);
printf("%d\n", ans[X]);
}
}
return 0;
}
void _union() {
int xf = getfa(X), yf = getfa(Y);
if (xf == yf) return;
fa[xf] = yf;
ans[xf] += cnt[yf];
cnt[yf] += cnt[xf];
}
int getfa(int c) {
if (c == fa[c]) return c;
int tmp = fa[c];
fa[c] = getfa(fa[c]);
ans[c] += ans[tmp];
return fa[c];
}
第五题:pog loves szh III
这题就是会构造出一棵树,然后多个询问编号l到r的区间的最近公共祖先是谁?其实这道题就是问LCA,但是要加强算法。暴力解肯定是不行的啦,所以我们要优化暴力,分块?好像还是很大,想到分块,我又想到倍增,接着想到RMQ的思想,突然来了灵感。用ans【i】【j】表示从i起2^j个的区间的最近公共祖先,发现时空复杂度都符合题意,就果断写下去,然后调试了一些小错误,就AC了。很多人都是用线段树做的,但是我不会,好菜啊!
Code:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 300010, maxm = 600010, maxcf = 30;
int n, m, u, v, las[maxn], Q, dep[maxn], a, b, fa[maxn][maxcf], ans[maxn][maxcf];
bool vis[maxn];
struct edge {
int v, ltb;
}E[maxm];
void dfs(int, int, int);
void load();
void add(int, int);
int LCA(int, int);
int answer(int, int);
int main() {
while (scanf("%d", &n) != EOF) {
memset(las, -1, sizeof(las));
memset(dep, 0, sizeof(dep));
memset(vis, 0, sizeof(vis));
memset(fa, 0, sizeof(fa));
memset(ans, 0, sizeof(ans));
m = 0;
for (int i = 0; i < n-1; i++) {
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}//邻接链表储存边
dfs(1, 1, 1);//建树并确定深度
load();//倍增
scanf("%d", &Q);
for (int i = 0; i < Q; i++) {
scanf("%d%d", &a, &b);
printf("%d\n", answer(a, b));
}
}
return 0;
}
void add(int u, int v) {
E[m].v = v;
E[m].ltb = las[u];
las[u] = m++;
}
void dfs(int r, int f, int d) {
vis[r] = 1;
dep[r] = d;
fa[r][0] = f;
for (int i = las[r]; i != -1; i = E[i].ltb) if (!vis[E[i].v])
dfs(E[i].v, r, d+1);
}
void load() {
for (int k = 1; k < 20; k++)
for (int i = 1; i <= n; i++) fa[i][k] = fa[fa[i][k-1]][k-1];//倍增最近公共祖先
for (int i = 1; i <= n; i++) ans[i][0] = i;
for (int k = 1, x = 2; k < 20; k++, x <<= 1)
for (int i = 1, j; i <= n; i++) {
j = i+(x>>1) > n ? n : i+(x>>1);
ans[i][k] = LCA(ans[i][k-1], ans[j][k-1]);//倍增第i位起2的j次方个的最近公共祖先
}
}
int LCA(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
int dec = dep[a]-dep[b];
for (int i = 19; i >= 0; i--) if (dec&(1<<i))
a = fa[a][i];
if (a == b) return a;
for (int i = 19; i >= 0; i--) if (fa[a][i] != fa[b][i]) {
a = fa[a][i];
b = fa[b][i];
}
return fa[a][0];
}
int answer(int a, int b) {
int dec = b-a+1, x = 1;
for (int i = 1; i < 20; i++) x <<= 1;
for (int i = 19; i >= 0; i--){
if (x+x >= dec && a+x-1<=b) return LCA(ans[a][i], ans[b-(1<<i)+1][i]);
x >>= 1;
}
}
后来,听老师和同学们讲评,发现自己好菜啊!第三题可以dfs先序加线段树维护,还是不会,证明也是半懂半不懂,唉,明天比赛准备暴力水分,啊啊啊!!!