海亮01/15数据结构专题
海亮01/15数据结构专题
T1
题意
在 X 星球上有 \(n\) 个国家,每个国家占据着 X 星球的一座城市,城市从 \(1\) 至 \(n\) 编号。由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的。
X 星球上战乱频发,如果 A 国打败了 B 国,那么 B 国将永远从这个星球消失,而 B 国的国土也将归 A 国管辖。A 国国王为了加强统治,会在 A 国和 B 国之间修建一条公路,即选择原 A 国的某个城市和 B 国某个城市,修建一条连接这两座城市的公路。
同样为了便于统治自己的国家,国家的首都会选在某个使得其他城市到它距离之和最小的城市,这里的距离是指需要经过公路的条数,如果有多个这样的城市,编号最小的将成为首都。
现在告诉你发生在 X 星球的战事,需要你处理一些关于国家首都的信息,具体地,有如下3种信息需要处理:
A x y:表示某两个国家发生战乱,战胜国选择了 \(x\) 城市和 \(y\) 城市,在它们之间修建公路(保证其中城市一个在战胜国另一个在战败国)。Q x:询问当前编号为 \(x\) 的城市所在国家的首都。Xor:询问当前所有国家首都编号的异或和。
题解
不会LCT的出门左转。
现在假设你已经会了LCT并且写过了模板。
然后考虑怎么维护一颗树的重心。
树的重心有很多优秀的性质,比如说我们即将用的这俩(摘自OI Wiki):
- 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
- 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
先增加两个LCT节点的维护信息:
- \(siz\):辅助树中,以自己为根的子树节点总数,加上自己的虚儿子的总数。
- \(si\):自己虚儿子的 \(siz\) 总和。
然后就发现,对于连接操作,在连接两个节点之后,你可以 \(split\) 下原来的两棵树的重心,然后在 \(Splay\) 上二分答案。
维护重心直接用并查集维护即可。
总时间复杂度 \(O(n\log n\times 大常数)\)。
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x = 0, f = 1;char ch =getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 1e5 + 10;
struct LCT{
struct node{
int fa, ch[2], tag;
int siz, si;
node(int fa = 0,int c0 = 0,int c1 = 0,int tag = 0,int siz = 0,int si = 0
):fa(fa),tag(tag),siz(siz),si(si){ch[0] = c0;ch[1] = c1;}
}d[maxn];
#define C(p,x) d[p].ch[x]
#define FA(p) d[p].fa
void maintain(int p){d[p].siz = d[C(p,0)].siz + d[C(p,1)].siz + d[p].si + 1;}
int get(int p){return p == C(FA(p),0) ? 0 : (p == C(FA(p),1) ? 1 : -1);}
void connect(int p,int f,int chk){FA(p) = f;if(chk == 1 || chk == 0)C(f,chk) = p;}
void pushdown(int p){
if(d[p].tag){
swap(C(p,0),C(p,1));
if(C(p,0))d[C(p,0)].tag ^= 1;
if(C(p,1))d[C(p,1)].tag ^= 1;
d[p].tag = 0;
}
}
void pushdownall(int p){if(get(p) != -1)pushdownall(FA(p));pushdown(p);}
void rotate(int x){
int y = FA(x), z = FA(y), chkx = get(x), chky = get(y);
int u = chkx != -1 ? C(x,chkx ^ 1) : 0;
connect(u,y,chkx);connect(y,x,chkx ^ 1);connect(x,z,chky);
maintain(y);maintain(x);
}
void splay(int p){
pushdownall(p);
for(int f = FA(p);f = FA(p),get(p) != -1;rotate(p))
if(get(f) != -1)rotate(get(p) == get(f) ? f : p);
}
void access(int p){
int rs = 0;
while(p){
splay(p);
d[p].si += d[C(p,1) ].siz;
d[p].si -= d[C(p,1) = rs].siz;
rs = p;maintain(p);p = FA(p);
}
}
void makeroot(int p){
access(p);splay(p);
d[p].tag ^= 1;
}
void split(int x,int y){
makeroot(x);
access(y);splay(y);
}
int findroot(int p){
access(p);splay(p);
while(C(p,0)){pushdown(p);p = C(p,0);}
splay(p);return p;
}
void link(int x,int y){
makeroot(x);if(findroot(y) == x)return;
FA(x) = y; d[y].si += d[x].siz;maintain(y);
}
int getmid(int root,int siz){
int lsum = 0, rsum = 0;
int u = root;siz /= 2;
int rt = 0x3f3f3f3f;
while(u){
pushdown(u);
int L = lsum + d[C(u,0)].siz;
int R = rsum + d[C(u,1)].siz;
if(L <= siz && R <= siz){rt = min(rt,u);}
if(L < R){lsum = L + d[u].si + 1;u = C(u,1);}
else {rsum = R + d[u].si + 1;u = C(u,0);}
}
return rt;
}
}tree;
int fa[maxn];
int getf(int x){return fa[x] == x ? x : fa[x] = getf(fa[x]);}
int n, m;
int ans;
signed main(){
n = read(); m = read();char opt[10];int u, v;
for(int i = 1;i <= n;i++){ans ^= i;fa[i] = i;}
for(int i = 1;i <= m;i++){
scanf("%s",opt);
if(opt[0] == 'A'){
u = read(); v = read();
int fu = getf(u), fv = getf(v);
ans ^= fu ^ fv;tree.link(u, v);
tree.split(fu, fv);
int siz = tree.d[fv].siz;
int rt = tree.getmid(fv,siz);
ans ^= rt;tree.splay(rt);
fa[rt] = fa[fu] = fa[fv] = rt;
}
if(opt[0] == 'Q'){printf("%d\n",getf(read()));}
if(opt[0] == 'X'){printf("%d\n",ans);}
}
return 0;
}
T6
题意
给一个\(n×m\)的网格图(\(n,m<=2000,nm<=2*10^5\)),每个格子上有个权值\(f_{ij}\),保证\(f_{ij}\)构成一个1~\(nm\)的排列。问有多少区间满足这个权值在这个区间内的格子构成的连通块是一棵树。
题解
首先需要知道一个图是树,等价于满足以下两个条件:
- 没有环。
- 点数-边数=1
发现,如果设 \(r_i\) 表示如果以 \(i\) 为左端点,最大的不会形成环的右端点是 \(r_i\)。
然后显然发现这玩应有单调性,可以 \(two pointer+LCT\) 维护。
然后变成求 \([i,r_i]\) 中有多少个 \(j\in[i,r_i]\),满足只保留 \([i,j]\) 的导出子图是树。
然后每个点存储点数-边数,显然发现这个东西不会小于1(否则与不成环相违背),并且一定有一个等于1(\(j=i\) 时显然),维护最小值和出现次数即可。
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f =-1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 4e5 + 10;
struct LCT{
struct node{
int fa, ch[2];int tag;
node(int fa = 0,int c0 = 0,int c1 = 0,int tag = 0):fa(fa),tag(tag){ch[0] = c0;ch[1] = c1;}
}d[maxn];
#define FA(p) d[p].fa
#define C(p,x) d[p].ch[x]
void maintain(int p){}
int get(int p){return p == C(FA(p),0) ? 0 : (p == C(FA(p),1) ? 1 : -1);}
void pushdown(int p){
if(d[p].tag){
swap(C(p,0),C(p,1)); d[p].tag = 0;
d[C(p,0)].tag ^= 1;d[C(p,1)].tag ^= 1;
}
}
void pushdownall(int p){
if(get(p) != -1)pushdownall(FA(p));
pushdown(p);
}
void connect(int p,int f,int chk){FA(p) = f;if(chk == 1 || chk == 0)C(f,chk) = p;}
void rotate(int x){
int y = FA(x), z = FA(y), chkx = get(x), chky = get(y);
int u = chkx != -1 ? C(x,chkx ^ 1) : 0;
connect(u, y, chkx);connect(y, x, chkx ^ 1);connect(x,z,chky);
maintain(y);maintain(x);
}
void splay(int p){
pushdownall(p);
for(int f = FA(p);f = FA(p),get(p) != -1;rotate(p))
if(get(f) != -1)rotate(get(f) == get(p) ? f : p);
}
void access(int p){
int rs = 0;
while(p){
splay(p); C(p,1) = rs; rs = p;
maintain(p);p = FA(p);
}
}
void makeroot(int p){access(p);splay(p);d[p].tag ^= 1;}
void split(int x,int y){
makeroot(x);
access(y);splay(y);
}
int findroot(int p){
access(p);splay(p);
while(C(p,0)){pushdown(p);p = C(p,0);}
splay(p);return p;
}
bool check(int x,int y){makeroot(x);return findroot(y) == x;}
bool link(int x,int y){
makeroot(x);if(findroot(y) == x)return false;
// puts("11242324314312423141234123");
FA(x) = y;return true;
}
void cut(int x,int y){
if(findroot(x) != findroot(y))return;
split(x, y);if(FA(x) != y || C(x, 1))return;
FA(x) = C(y,0) = 0;
maintain(x);maintain(y);
}
}lct;
int n, m;
struct Segment_Tree{
struct node{
int minn, cnt;int tag;
node(int minn = 0x3f3f3f3f,int cnt = 1,int tag = 0):minn(minn),cnt(cnt),tag(tag){}
}d[maxn << 2];
node mergenode(node l,node r){
node ans = l;
if(l.minn > r.minn){ans = r;}
if(l.minn == r.minn){ans.cnt += r.cnt;}
ans.tag = 0;return ans;
}
void change(int p,int upd){if(p >= (maxn << 2))return;d[p].minn += upd;d[p].tag += upd;}
void pushdown(int p){
if(d[p].tag){
change(p << 1,d[p].tag); change(p << 1 | 1,d[p].tag); d[p].tag = 0;
}
}
void build(int l,int r,int p){
if(l == r){d[p] = node(0);return;}
int mid = l + r >> 1;
build(l,mid,p << 1); build(mid + 1,r,p << 1 | 1);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
void update(int l,int r,int s,int t,int p,int upd){
// printf("l = %d r = %d s = %d t = %d, p = %d,upd = %d\n",l,r,s,t,p,upd);
if(s <= l && r <= t){change(p,upd);return;}
int mid = l + r >> 1;pushdown(p);
if(s <= mid)update(l,mid,s,t,p << 1,upd);
if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,upd);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
node query(int l,int r,int s,int t,int p){
if(s <= l && r <= t)return d[p];
int mid = l + r >> 1;pushdown(p);
if(t <= mid)return query(l,mid,s,t,p << 1);
if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
}
void update(int l,int r,int upd){if(l > r)return;update(1,n,l,r,1,upd);}
node query(int l,int r){return query(1,n,l,r,1);}
void DEBUG(int l = 1,int r = n,int p = 1){
printf("[%d,%d],p = %d,minn = %d,cnt = %d,tag = %d\n",l,r,p,d[p].minn,d[p].cnt,d[p].tag);
if(l == r)return;int mid = l + r >> 1;
DEBUG(l,mid,p << 1);DEBUG(mid + 1,r,p << 1 | 1);
}
}tree;
int a[1001][1001];
const int dx[4] = {-1, 0, 0, 1};
const int dy[4] = { 0, 1,-1, 0};
vector<int> edg[maxn];
int R[maxn];
signed main(){
#ifndef ONLINE_JUDGE
freopen("tmp.in","r",stdin);
freopen("tmp.out","w",stdout);
#endif
n = read(); m = read();
for(int i = 1;i <= n;i++)
for(int j = 1;j <= m;j++)
a[i][j] = read();
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
for(int k = 0;k < 4;k++){
int nx = i + dx[k], ny = j + dy[k];
if(nx < 1 || ny < 1 || nx > n || ny > m)continue;
edg[a[i][j]].push_back(a[nx][ny]);
}
}
}
int l = 0, r = 0;n = n * m;
while(l < n){
for(int v : edg[l]){lct.cut(l,v);}l++;
while(r < n){
r++;bool book = true;
for(int v : edg[r]){
if(v >= l && v < r && !lct.link(v, r)){book = false;}
}
// printf("book = %d\n",book);
if(!book){
for(int v : edg[r]){lct.cut(v, r);}
r--;break;
}
// printf("r = %d\n",r);
}
R[l] = r;
// printf("l = %d\n",l);
}
// puts("111113423412334123");
long long ans = 0;tree.build(1,n,1);Segment_Tree::node lst;
for(l = n;l;l--){
tree.update(l,R[l],1);
for(int v : edg[l]){
if(v > l && v <= R[l]){tree.update(v, R[l],-1);}
}
auto tmp = tree.query(l,R[l]);
if(tmp.minn < 1)printf("l = %d : %d %d %d %d\n",l,lst.minn,lst.cnt,tmp.minn,tmp.cnt);
assert(tmp.minn >= 1);
ans += 1ll * tmp.cnt;lst = tmp;
}
printf("%lld\n",ans);
return 0;
}
T9
题意
求一个数列的所有子区间的 mex 值的 mex
某个数组的 mex 是这个数组中没有包含的最小正整数。
\(n\le 10^5,1\le a_i\le n\)
题解
发现 \(mex\) 的 \(mex\) 中,外层的 \(mex\) 是可以枚举的,也就是说,对每一个 \(1\le x \le n + 2\),都要判定是否 \(\exists l,r\in[1,n],mex_{i\in[l,r]}\{a_i\} = x\)。
这个是很好做的,假设当前只考虑 \(x\) 是否出现在外层的 \(mex\) 中,那么将所有 \(a_i=x\) 的 \(i\) 提出来,设为 \(p_1,p_2,\dots,p_k\),那么现在需要询问的是在 \([1,p_1 - 1],[p_1+1,p_2-1],\dots,[p_{k-1}+1,p_k-1],[p_k+1,n]\) 中,是否存在一个区间满足出现了所有的 \([1,x-1]\) 数字。
这个只需要从左向右维护右端点,然后维护最后出现位置即可。
然后所有数字可以一起考虑。
不过需要注意对 \(1\) 的特判。
代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f =-1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 1e5 + 10;
int n, a[maxn];
int lst[maxn];
struct Segment_Tree{
struct node{
int minn;
node(int minn = 0):minn(minn){}
}d[maxn << 2];
node mergenode(node l,node r){return node(min(l.minn,r.minn));}
void update(int l,int r,int pos,int p,int upd){
if(l == r){d[p] = node(upd);return;}
int mid = l + r >> 1;
if(pos <= mid)update(l,mid,pos,p << 1,upd);
else update(mid + 1,r,pos,p << 1 | 1,upd);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
node query(int l,int r,int s,int t,int p){
if(t < s)return node(114514);
if(s <= l && r <= t)return d[p];
int mid = l + r >> 1;
if(t <= mid)return query(l,mid,s,t,p << 1);
if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
}
}tree;
bool book[maxn];
signed main(){
n = read();
for(int i = 1;i <= n;i++){
a[i] = read();if(a[i] != 1)book[1] = 1;
if(a[i] > 1 && tree.query(1,n,1,a[i] - 1,1).minn > lst[a[i]])book[a[i]] = 1;
tree.update(1,n,a[i],1,i);lst[a[i]] = i;
}
for(int i = 2;i <= n + 1;i++){
if(tree.query(1,n,1,i - 1,1).minn > lst[i])book[i] = 1;
}
int x = 1;while(book[x])x++;
printf("%d\n",x);
return 0;
}
T10
题意
Chanek先生有一个果园,果园的结构是一棵三叉的有根树。树上有N个顶点,编号从 \(1\) 到 $ N$ ,并且树的根是顶点 \(1\) 。对于 \(2 \le i \le N\) ,定义 \(P_i\) 表示顶点\(i\)的父节点。树的高度不大于 \(10\),这里的树的高度被定义为树根到树上顶点的最大距离。
果园的每个顶点上都存在一个灌木丛。最初所有的灌木丛上都有果实,顶点 \(i\) 的灌木丛在最后一次被采摘后的 \(A_i\) 天后将会长出果实。目前已经有果实的灌木丛上不会再长出果实。
Chanek先生会到他的果园参观 $ Q $ 天。在第 \(i\) 天,他将采集节点 \(X_i\) 的子树内所有灌木上的果实。你需要求出每一天所有被采集的灌木到 $ X_i $ 的距离之和,以及当天被采集的果实的数量。
题解
讲个冷笑话,这道题暴力由于小常数+难卡,可以直接过。谁写正解就是小丑了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f = 1;char ch =getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 5e4 + 10;
int n, q;
vector<int> edg[maxn];
int dep[maxn], st[maxn], ed[maxn], idx;
int a[maxn], b[maxn], t[maxn];
void dfs(int u){st[u] = ++idx;for(int v : edg[u]){dep[v] = dep[u] + 1;dfs(v);}ed[u] = idx;}
void calc(int *a,int *p){
for(int i = 1;i <= n;i++)b[i] = a[i];
for(int i = 1;i <= n;i++)a[p[i]] = b[i];
}
signed main(){
n = read(); q = read();
for(int i = 1;i <= n;i++){a[i] = read();t[i] = 0;}
for(int i = 2;i <= n;i++)edg[read()].push_back(i);
dfs(1);calc(a,st);calc(dep,st);
for(int tim = 1;tim <= q;tim++){
int u = read();int sumdep = 0, cnt = 0;
for(int i = st[u];i <= ed[u];i++){
if(t[i] <= tim){
cnt++;sumdep += dep[i];
t[i] = tim + a[i];
}
}
sumdep -= cnt * dep[st[u]];
printf("%d %d\n",sumdep,cnt);
}
return 0;
}

浙公网安备 33010602011771号