4.17总结

可能会有错误,路过大神请见谅我个蒟蒻!

如果侵犯他人合法权益,请与我联系删除。

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模式,没有部分分,就没有写。这题我们自然而然地发现这是最短路径算法,能用的只有spfafloyd,通过分析,我们发现题目最多只有200个点,当然是选择floyd啦。其实这道题如果想要思路明确的话是选择记忆化搜索,状态描述为fn】【i】【j】表示为经过n条边ij的最小值是多少。我们可以写出状态转移方程,把fn】【i】【j=minfn>>1】【i】【k,fn>>1】【k】【j】)注意处理好细节,很多坑,对于说会爆空间的想法,我们可以预处理n的各种情况,或者用map映射,都可以。但是我的做法不一样,我是用floyd+矩阵乘法+快速幂。floyd可以理解吧,为什么关矩阵乘法什么事呢?众所周知,floydfor(int k = 1; k <= n; k++)for(int i = 1; i < n; i++) for(int j = 1; j <= n; j++),那么我们可以理解为对于a矩阵ik列加上b矩阵kj列得到c矩阵ij列。初始化时矩阵时读入的矩阵(ij是经过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了。如果按照题目的做法,用ansi】表示i到集合的跟结点的距离是多少,cnti】表示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

这题就是会构造出一棵树,然后多个询问编号lr的区间的最近公共祖先是谁?其实这道题就是问LCA,但是要加强算法。暴力解肯定是不行的啦,所以我们要优化暴力,分块?好像还是很大,想到分块,我又想到倍增,接着想到RMQ的思想,突然来了灵感。用ansi】【j】表示从i2^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位起2j次方个的最近公共祖先

}

}

 

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先序加线段树维护,还是不会,证明也是半懂半不懂,唉,明天比赛准备暴力水分,啊啊啊!!!


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值