Gym 100962F Frank Sinatra
2016-05-12 13:18
295 查看
树上莫队。
关于树上莫队,需要做的是把树分块。可以直接按dfs序分块,但是这样速度比较慢。还有一种就是按后序遍历的方法去分块。这样就可以按左端点的块号排序,再按右端点的dfs序排序,然后上莫队了。
还需要注意的是转移。需要的是每次把链给变掉,这个可以用两端点的变化来搞定。链的异或操作,可以实现链的转换。
这道题题意是,给一棵树,然后每条边有边权。给你q个查询,每个查询问你一条链上最小的没出现的数字是多少。于是可以莫队+分块来做。由于是单组数据,所以随便跑。但是这道题好像卡常数了,然后好像dfs还会爆栈(后来发现并没有
)。。于是我手写栈的时候忘了记录dfn,tle到死,各种小优化到最后才发现这个问题。
第二种姿势。
首先可以知道的是,对于一棵树的dfs序(访问u时记录一次,离开u时记录一次),我们可以用一个区间中出现奇数次的点来表示一个路径,具体类似这个。由于本题是用边下面的点表示该边,所以对于两个点的最近公共祖先是没必要记录的。所以就直接可以用区间表示两点之间的路径。其中如果两点[u,
v]路径,其中u是v的祖先的话,我们需要把u点给去掉。具体可以通过路径的具体表现形式来实现。
鉴于大家可能不怎么会表示该路径,这里说一下路径的表示形式。对于两点之间的路径[u, v](这里我们默认先访问了u节点),那么u->v的路径就可以表示为离开u到到达v这个序列。比如对于样例:
7 6
2 1 1
3 1 2
1 4 0
4 5 1
5 6 3
5 7 4
则dfs序列为:
(1 4 5 7 7 6 6 5 4 3 3 2 2 1)
(注意由于用的是邻接表存的边,所以序列是这样)
那么对于[1, 7]就可以用序列(1 4 5 7)表示,对于本题需要把祖先节点去掉,于是表示为(4 5 7)
对于[2, 6],可以用序列(6 5 4 3 3 2)表示,其种3点出现了偶数次,需要排除掉,于是剩下的便是去掉公共祖先的路径。想象一下还是挺容易理解。
于是树上的询问就变成了一个序列区间了,且在该序列区间中出现奇数次的点是有效点,于是就可以直接莫队了。
关于树上莫队,需要做的是把树分块。可以直接按dfs序分块,但是这样速度比较慢。还有一种就是按后序遍历的方法去分块。这样就可以按左端点的块号排序,再按右端点的dfs序排序,然后上莫队了。
还需要注意的是转移。需要的是每次把链给变掉,这个可以用两端点的变化来搞定。链的异或操作,可以实现链的转换。
这道题题意是,给一棵树,然后每条边有边权。给你q个查询,每个查询问你一条链上最小的没出现的数字是多少。于是可以莫队+分块来做。由于是单组数据,所以随便跑。但是这道题好像卡常数了,然后好像dfs还会爆栈(后来发现并没有
)。。于是我手写栈的时候忘了记录dfn,tle到死,各种小优化到最后才发现这个问题。
//#pragma comment(linker, "/STACK:1024000000,1024000000") #include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<cmath> #include<queue> #include<stack> #include<set> #include<map> #define xx first #define yy second #define LL long long #define MP make_pair #define inf 0x3f3f3f3f #define CLR(a, b) memset(a, b, sizeof(a)) #define lson l, m, rt << 1 #define rson m + 1, r, rt << 1 | 1 using namespace std; const int maxn = 100100; int fa[maxn][22], dep[maxn], dfn[maxn], belong[maxn]; int val[maxn], cnt[maxn * 3], block[maxn]; int K, idx; vector<pair<int, int> > G[maxn]; struct Q { int a, b, id; bool operator < (const Q& rhs) const { return (belong[a] == belong[rhs.a] && dfn[b] < dfn[rhs.b]) || belong[a] < belong[rhs.a]; } } qrt[maxn]; //struct Q { // int a, b, id; // bool operator < (const Q& rhs) const { // return (dfn[a] / K == dfn[rhs.a] / K && dfn[b] < dfn[rhs.b]) // || dfn[a] / K < dfn[rhs.a] / K; // } //} qrt[maxn]; struct Node { int u, p, i; Node() {} Node(int u, int p, int i) : u(u), p(p), i(i) {} }; int stk[maxn]; int fir[maxn], nxt[maxn * 2], to[maxn * 2], dis[maxn * 2], ecnt; /** void dfs(int u, int p) { dfn[u] = idx ++; fa[u][0] = p; dep[u] = dep[p] + 1; for(int i = 0; i < (int)G[u].size(); i ++) { int v = G[u][i].xx, c = G[u][i].yy; if(v == p) continue; val[v] = c; dfs(v, u); if(sz >= K) { while(sz) { belong[stk[sz - 1]] = tot; sz --; } tot ++; } } stk[sz ++] = u; } */ inline void dfs(int u, int p) { stack<Node> S; int sz = 0, tot = 0, idx = 0; S.push(Node(u, p, fir[u])); dfn[u] = idx ++; fa[u][0] = 0; dep[u] = 1; while(!S.empty()) { Node u = S.top(); S.pop(); if(u.i == -1) { stk[sz ++] = u.u; if(sz >= K) { while(sz) { belong[stk[sz - 1]] = tot; sz --; } tot ++; } continue; } S.push(Node(u.u, u.p, nxt[u.i])); int v = to[u.i]; if(v == u.p) continue; fa[v][0] = u.u; dep[v] = dep[u.u] + 1; val[v] = dis[u.i]; dfn[v] = idx ++; S.push(Node(v, u.u, fir[v])); } while(sz) { belong[stk[sz - 1]] = tot; sz --; } tot ++; } int ans[maxn], vec[maxn * 3], Log[maxn]; inline void LCA_init(int n) { Log[0] = -1; for(int i = 1; i <= n; i ++) Log[i] = Log[i >> 1] + 1; for(int j = 1; j < 20; j ++) { for(int i = 1; i <= n; i ++) fa[i][j] = fa[fa[i][j - 1]][j - 1]; } } int n; inline int LCA(int u, int v) { if(dep[u] > dep[v]) swap(u, v); int c = dep[v] - dep[u]; for(int i = Log[c]; i >= 0; i --) if((1 << i) & c) { v = fa[v][i]; } if(u == v) return u; for(int i = Log ; i >= 0; i --) { if(fa[u][i] == fa[v][i]) continue; u = fa[u][i]; v = fa[v][i]; } return fa[u][0]; } bool vis[maxn]; inline void gao(int u, int v, int lca) { while(u != lca) { if(vis[u]) { cnt[val[u]] --; if(cnt[val[u]] == 0) block[val[u] / K] --; vis[u] = false; } else { cnt[val[u]] ++; if(cnt[val[u]] == 1) block[val[u] / K] ++; vis[u] = true; } u = fa[u][0]; } while(v != lca) { if(vis[v]) { cnt[val[v]] --; if(cnt[val[v]] == 0) block[val[v] / K] --; vis[v] = false; } else { cnt[val[v]] ++; if(cnt[val[v]] == 1) block[val[v] / K] ++; vis[v] = true; } v = fa[v][0]; } } inline int mex() { if(cnt[0] == 0) return 0; for(int i = 0; ; i ++) { if(block[i] != K) { for(int j = i * K; ; j ++) if(cnt[j] == 0) return vec[j]; } } return 0; } inline int getint() { char c = getchar(); int con = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') con = con * 10 + c - '0', c = getchar(); return con; } inline void add(int u, int v, int c) { to[ecnt] = v; dis[ecnt] = c; nxt[ecnt] = fir[u]; fir[u] = ecnt ++; } int main() { int q; ecnt = 0; scanf("%d%d", &n, &q); { K = sqrt(n + 0.0); CLR(fir, -1); int vsz = 0; vec[vsz ++] = 0; for(int i = 2; i <= n; i ++) { int u = getint(), v = getint(), c = getint(); add(u, v, c); add(v, u, c); vec[vsz ++] = c; vec[vsz ++] = c + 1; } sort(vec, vec + vsz); vsz = unique(vec, vec + vsz) - vec; dep[0] = 0; CLR(fa, 0); idx = 0; dfs(1, 0); LCA_init(n); for(int i = 1; i <= n; i ++) { val[i] = lower_bound(vec, vec + vsz, val[i]) - vec; } for(int i = 0; i < q; i ++) { qrt[i].a = getint(); qrt[i].b = getint(); if(belong[qrt[i].a] > belong[qrt[i].b]) swap(qrt[i].a, qrt[i].b); // if(dfn[qrt[i].a] > dfn[qrt[i].b]) // swap(qrt[i].a, qrt[i].b); qrt[i].id = i; } sort(qrt, qrt + q); CLR(vis, false); CLR(cnt, 0); CLR(block, 0); gao(qrt[0].a, qrt[0].b, LCA(qrt[0].a, qrt[0].b)); ans[qrt[0].id] = mex(); for(int i = 1; i < q; i ++) { gao(qrt[i - 1].a, qrt[i].a, LCA(qrt[i - 1].a, qrt[i].a)); gao(qrt[i - 1].b, qrt[i].b, LCA(qrt[i - 1].b, qrt[i].b)); ans[qrt[i].id] = mex(); } for(int i = 0; i < q; i ++) { printf("%d\n", ans[i]); } } return 0; }
第二种姿势。
首先可以知道的是,对于一棵树的dfs序(访问u时记录一次,离开u时记录一次),我们可以用一个区间中出现奇数次的点来表示一个路径,具体类似这个。由于本题是用边下面的点表示该边,所以对于两个点的最近公共祖先是没必要记录的。所以就直接可以用区间表示两点之间的路径。其中如果两点[u,
v]路径,其中u是v的祖先的话,我们需要把u点给去掉。具体可以通过路径的具体表现形式来实现。
鉴于大家可能不怎么会表示该路径,这里说一下路径的表示形式。对于两点之间的路径[u, v](这里我们默认先访问了u节点),那么u->v的路径就可以表示为离开u到到达v这个序列。比如对于样例:
7 6
2 1 1
3 1 2
1 4 0
4 5 1
5 6 3
5 7 4
则dfs序列为:
(1 4 5 7 7 6 6 5 4 3 3 2 2 1)
(注意由于用的是邻接表存的边,所以序列是这样)
那么对于[1, 7]就可以用序列(1 4 5 7)表示,对于本题需要把祖先节点去掉,于是表示为(4 5 7)
对于[2, 6],可以用序列(6 5 4 3 3 2)表示,其种3点出现了偶数次,需要排除掉,于是剩下的便是去掉公共祖先的路径。想象一下还是挺容易理解。
于是树上的询问就变成了一个序列区间了,且在该序列区间中出现奇数次的点是有效点,于是就可以直接莫队了。
//#pragma comment(linker, "/STACK:1024000000,1024000000") #include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<cmath> #include<queue> #include<stack> #include<set> #include<map> #define xx first #define yy second #define LL long long #define MP make_pair #define inf 0x3f3f3f3f #define CLR(a, b) memset(a, b, sizeof(a)) #define lson l, m, rt << 1 #define rson m + 1, r, rt << 1 | 1 using namespace std; const int maxn = 100100; int fa[maxn][22], dep[maxn], dfn[maxn * 2], fst[maxn], sec[maxn]; int val[maxn], cnt[maxn * 3], block[maxn]; int K, idx; vector<pair<int, int> > G[maxn]; struct Q { int a, b, id; bool operator < (const Q& rhs) const { return (a / K == rhs.a / K && b < rhs.b) || a / K < rhs.a / K; } } qrt[maxn]; int fir[maxn], nxt[maxn * 2], to[maxn * 2], dis[maxn * 2], ecnt; int ans[maxn], vec[maxn * 3], Log[maxn]; int n; void dfs(int u, int p) { fst[u] = ++ idx; dfn[idx] = u; dep[u] = dep[p] + 1; fa[u][0] = p; for(int i = fir[u]; ~i; i = nxt[i]) { int v = to[i], c = dis[i]; if(v == p) continue; val[v] = c; dfs(v, u); } sec[u] = ++ idx; dfn[idx] = u; } inline void LCA_init(int n) { Log[0] = -1; for(int i = 1; i <= n; i ++) Log[i] = Log[i >> 1] + 1; for(int j = 1; j < 20; j ++) { for(int i = 1; i <= n; i ++) fa[i][j] = fa[fa[i][j - 1]][j - 1]; } } inline int LCA(int u, int v) { if(dep[u] > dep[v]) swap(u, v); int c = dep[v] - dep[u]; for(int i = Log[c]; i >= 0; i --) if((1 << i) & c) { v = fa[v][i]; } if(u == v) return u; for(int i = Log ; i >= 0; i --) { if(fa[u][i] == fa[v][i]) continue; u = fa[u][i]; v = fa[v][i]; } return fa[u][0]; } int vis[maxn]; inline int mex() { if(cnt[0] == 0) return 0; for(int i = 0; ; i ++) { if(block[i] != K) { for(int j = i * K; ; j ++) if(cnt[j] == 0) return vec[j]; } } return 0; } inline int getint() { char c = getchar(); int con = 0; while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') con = con * 10 + c - '0', c = getchar(); return con; } inline void add(int u, int v, int c) { to[ecnt] = v; dis[ecnt] = c; nxt[ecnt] = fir[u]; fir[u] = ecnt ++; } void add(int x) { cnt[x] ++; if(cnt[x] == 1) block[x / K] ++; } void sub(int x) { cnt[x] --; if(cnt[x] == 0) block[x / K] --; } int main() { int q; ecnt = 0; scanf("%d%d", &n, &q); { K = sqrt(n + 0.0); CLR(fir, -1); int vsz = 0; vec[vsz ++] = 0; for(int i = 2; i <= n; i ++) { int u = getint(), v = getint(), c = getint(); add(u, v, c); add(v, u, c); vec[vsz ++] = c; vec[vsz ++] = c + 1; } sort(vec, vec + vsz); vsz = unique(vec, vec + vsz) - vec; dep[0] = 0; CLR(fa, 0); idx = 0; dfs(1, 0); LCA_init(n); for(int i = 1; i <= n; i ++) { val[i] = lower_bound(vec, vec + vsz, val[i]) - vec; } for(int i = 0; i < q; i ++) { qrt[i].a = getint(); qrt[i].b = getint(); int lca = LCA(qrt[i].a, qrt[i].b); if(lca == qrt[i].b) swap(qrt[i].a, qrt[i].b); if(lca == qrt[i].a) { qrt[i].a = fst[qrt[i].a] + 1; qrt[i].b = fst[qrt[i].b]; } else { if(fst[qrt[i].a] > fst[qrt[i].b]) swap(qrt[i].a, qrt[i].b); qrt[i].a = sec[qrt[i].a]; qrt[i].b = fst[qrt[i].b]; } qrt[i].id = i; } sort(qrt, qrt + q); CLR(vis, 0); CLR(cnt, 0); CLR(block, 0); int L = 1, R = 0; for(int i = 0; i < q; i ++) { while(L > qrt[i].a) { L --; vis[dfn[L]] ++; if(vis[dfn[L]] == 1) add(val[dfn[L]]); else sub(val[dfn[L]]); } while(R < qrt[i].b) { R ++; vis[dfn[R]] ++; if(vis[dfn[R]] == 1) add(val[dfn[R]]); else sub(val[dfn[R]]); } while(L < qrt[i].a) { vis[dfn[L]] --; if(vis[dfn[L]] == 1) add(val[dfn[L]]); else sub(val[dfn[L]]); L ++; } while(R > qrt[i].b) { vis[dfn[R]] --; if(vis[dfn[R]] == 1) add(val[dfn[R]]); else sub(val[dfn[R]]); R --; } ans[qrt[i].id] = mex(); } for(int i = 0; i < q; i ++) { printf("%d\n", ans[i]); } } return 0; }
相关文章推荐
- [BZOJ 3757]苹果树
- UOJ 58 BZOJ 3052 [wc2013] 糖果公园
- BZOJ 3757 苹果树
- SPOJ COT2 Count on a tree II
- 树上莫队算法
- GYM 100962F Problem F. Frank Sinatra(树上莫队+分块)
- SPOJ Count on a tree II(树上莫队)
- [bzoj 4129]Haruna’s Breakfast
- [bzoj 3052--wc2013]糖果公园
- [bzoj 3460]Jc的宿舍
- 「BZOJ3052」「WC2013」糖果公园
- 软件测试方法大汇总
- nyoj420 P次方求和(快速幂)
- 如何在前端项目中实现热更新
- c#中decimal ,double,float的区别
- 博客的起航
- Keepalived实现Mysql双主高可用
- 用户“Michael-PC\Michael”不具有所需的权限。请验证授予了足够的权限并且解决了 Windows 用户帐户控制(UAC)限制问题。
- 聊聊jQuery的反模式
- 15校赛