数据结构上机笔记
开一个贴记录一下上机作业里面比较有意思的题目。
双向链表
Description
给一个\(1\)$n$的排列$p_1,p_2,...,p_n$,对于$1$\(n\)中的每个数\(i\),问排列中有多少个子区间的最小值恰好等于\(i\)。
Input
第一行一个正整数 \(n (1≤n≤10^5)\),表示排列的大小。
第二行\(n\)个正整数\(p_1,p_2,...,p_n\),表示给定的\(1\)~\(n\)的排列。
Output
一行\(n\)个整数,第\(i\)个表示最小值恰好等于\(i\)的子区间个数。
Sample
Input
4
1 3 2 4
Output
4 4 1 1
Solution
问题的本质在于对于每个元素\(p_i\),需要知道它分别与前后第一个比它小的元素的距离。因此可以维护一个有序序列,初始情况下为空,按照元素大小顺序依次向序列中插入元素的位置,同时查看它的前驱后继。针对前驱后继为空的情况,可以先向序列中插入0和n+1两个哨兵节点。维护序列的数据结构可以使用set。
Pollard-Rho
Description
给定\(x_0,k\),求\(x_k\),其中\(x_i=({x_{i-1}}^2+1)\ mod\ 1000000007(i=1,2,...)\)
提示:可以认为序列\(x\)是一个随机序列。按照生日攻击的理论,序列\(x\)期望下会在\(\sqrt{1000000007}\)级别的位置出现循环节,但并不保证循环节开始的地方恰好就是\(x_0\)。
Input
仅一行两个整数\(x_0,k(0\leq x_0\leq 1000000007,1\leq k\leq 10^9)\)。
Output
仅一行一个整数,表示\(x_k\)。
Sample
Sample Input 1
1 4
Sample Output 1
677
Sample Input 2
998244353 593119681
Sample Output 2
803656963
Solution
Pollard-Rho算法的核心部分。将每个\(x_i\)看成一个点,点与点之间的转移形式就是变换\((x^2+1)\ mod\ 1000000007\)。于是整个图变成了一个\(\rho\)形的单向链表,问题的核心就是找到环的长度。
设两个指针x,y,每次指针x向前前进一步,指针y向前前进两步,直到两指针又相遇。这时两个指针已经在一个环里。此时指针x继续向前走,同时记录所走步数,直到两指针再次相遇。此时得到环的长度。
最长回文前缀
Description
给一个字符串\(s\),求\(s\)的最长回文前缀长度,即\(max\{t\ |\ t\in [1,|s|],s_1s_2...s_t=s_ts_{t-1}...s_1\}\)。
Input
仅一行一个由小写字母构成的数字串\(s(1\leq |s|\leq 10^6)\)。
Output
仅一行一个正整数,表示答案。
Sample
Sample Input
minimum
Sample Output
5
Solution
方法一
直接套用manacher。在最后得出答案时判断一下起点是否在最左端即可。
方法二
枚举中心点,字符串哈希判等。
二马路
Description
在谭州城里,有\(n\)条横向的马路和\(m\)条纵向的马路,每条马路有一个宽度\(w_i\)。
可知总共会形成\(m\)个交叉点,每个交叉点的不和谐度定义为该交叉点对应的两条马路的宽度差的绝对值。
求所有\(n\times m\)个交叉点的不和谐度之和。
Input
输入第一行两个正整数\(n,m(1\leq n,m\leq 10^5)\),表示横向的马路条数和纵向的马路条数。
第二行\(n\)个正整数\(h_1,h_2,...,h_n(1\leq h_i\leq 10^8)\),表示横向的每条马路的宽度。
第三行\(m\)个正整数\(v_1,v_2,...,v_m(1\leq v_i\leq 10^8)\),表示纵向的每条马路的宽度。
Output
输出仅一个非负整数,表示所有\(n\times m\)个交叉点的不和谐度之和。
Sample
Sample Input
5 4
1 7 9 3 5
2 8 4 6
Sample Output
60
Solution
将横向马路按照宽度升序排序,计算宽度前缀和,随后在序列内依次二分查找值为纵向马路宽度的元素在序列中所处的位置,O(1)计算答案即可。
三王街
Description
在一条街上有\(n\)个王,每个王有\(a_i\)点势力值。
现在要选出三个王组成三权分立的政府,但是要保证这三个王中的任意两个王的势力值之和都严格大于剩下的那个王的势力值,不然很可能会导致那个最大势力的王独裁统治。
问有多少种可行的选法。
Input
输入第一行一个正整数\(n(3\leq n\leq 9999)\),表示王的数量。
第二行\(n\)个正整数\(a_1,a_2,...,a_n(1\leq a_i\leq 10^9)\),表示每个王的势力值。
Output
一行一个非负整数,表示合法的选法种数。
Sample
Sample Input
6
1 4 2 8 5 7
Sample Output
6
Solution
问题本质在于求序列中能构成三角形的三元组\((i,j,k)\)的个数。因此先将序列排序,随后按如下代码运行:
for(i=0;i<n-2;i++){
k=i+2;
for(j=i+1;j<n-1;j++){
while(k<n&&a[i]+a[j]>a[k])k++;
ans+=k-j-1;
}
}
总复杂度\(O(n^2)\),勉强卡过。
fsxnb
Description
fsxnb=\(F\)ather'\(S\) inde\(X\) i\(N\) \(B\)ST
给一个长度为\(n\)的排列,按排列的顺序依次插入元素来构建二叉搜索树。问每个元素对应节点的父亲节点的元素,不存在父亲节点则视其为0。
Input
第一行一个正整数\(n(1\leq n\leq 10^5)\),表示排列大小。
接下来一行\(n\)个正整数\(a_1,a_2,...,a_n(1\leq a_i\leq n)\),表示排列。
Output
仅一行\(n\)个整数,第\(i\)个整数表示元素\(i\)对应节点的父亲节点的元素。
Sample
Sample Input
6
5 6 1 3 4 2
Sample Output
5 3 1 3 0 5
Hint
最后的二叉搜索树大概长这样:
5
/ \
/ \
/ \
/ \
/ \
1 6
\
\
3
/ \
2 4
Solution
大胆猜想,不需证明
发现如果插入元素i,那么元素i的父亲要么是插入后i的前驱,要么是插入后i的后继。
于是开两个数组表示元素i的左右儿子是否为空,每次插入i之后判断一下i的前驱的右儿子和i的后继的左儿子是否为空(不可能两个都为空),将为空的那个设为父亲。前驱后继的查询可以用set完成。
树
Description
给定一棵\(n\)个点的树,有\(q\)次染色操作,第\(i\)次操作会把树中的某条链\(u\rightarrow v\)上的所有边的颜色变成\(i\)。最后对于\(1\)~\(q\)中的每种颜色\(c\),问有多少条边的颜色是\(c\)。
Input
输入第一行两个正整数\(n,q(1\leq n,q\leq 2\times 10^5)\),表示点数和染色次数。
接下来一行\(n-1\)个整数,其中第\(i\)个整数\(f_i(1\leq f_i\leq i)\)表示树中第\(i+1\)个点的父亲节点为\(f_i\)。
接下来\(q\)行,每行两个正整数\(u,v(1\leq u<v\leq n)\),表示一次染色操作。
Output
输出一行\(q\)个整数,其中第\(i\)个整数表示颜色是\(i\)的边的条数。
Sample
Sample Input
7 3
1 1 2 2 3 3
4 7
4 5
6 7
Sample Output
2 2 2
Hint
样例里的树大概长这样:
1
/ \
/ \
2 3
/ \ / \
4 5 6 7
染色情况:
- 有\((1,2),(1,3)\)两条边的颜色是1
- 有\((2,4),(2,5)\)两条边的颜色是2
- 有\((3,6),(3,7)\)两条边的颜色是3
Solution
离线读入所有染色操作,随后反向处理,使用并查集将同色块合并。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int heade[maxn],ev[maxn],nexte[maxn];
int dep[maxn],fa[maxn],dc[maxn],cf[maxn];
int query[maxn][2],ans[maxn];
int n,q,tot=0;
void add_edge(int u,int v){ev[++tot]=v;nexte[tot]=heade[u];heade[u]=tot;}
void dfs(int ui){
int i,vi;
cf[ui]=ui;
for(i=heade[ui];~i;i=nexte[i]){
vi=ev[i];
dep[vi]=dep[ui]+1;
dfs(vi);
}
}
int find(int x){
if(cf[x]==x){return x;}
else{cf[x]=find(cf[x]);return cf[x];}
}
int work(int u,int v,int i){
int ans=0;
//printf("u=%d v=%d i=%d\n",u,v,i);
u=find(u);v=find(v);
if(u==v){return ans;}
if(dep[u]<dep[v]){swap(u,v);}
while(u!=v){
if(dep[u]==dep[v]){
u=cf[u]=fa[u];
v=cf[v]=fa[v];
u=find(u);v=find(v);
ans+=2;
}
else{
if(dep[u]<dep[v]){swap(u,v);}
u=cf[u]=fa[u];
u=find(u);
ans++;
}
}
//printf("ans=%d\n",ans);
return ans;
}
int main()
{
int i,j;
cin>>n>>q;
memset(heade,-1,sizeof(heade));
for(i=2;i<=n;i++){
scanf("%d",&fa[i]);
add_edge(fa[i],i);
}
dfs(1);
for(i=1;i<=q;i++){scanf("%d%d",&query[i][0],&query[i][1]);}
for(i=q;i>=1;i--){
ans[i]=work(query[i][0],query[i][1],i);
}
for(i=1;i<=q;i++){printf("%d ",ans[i]);}
return 0;
}
要走哪
Description
给定一张\(n\)个点,\(m\)条边的无向连通图,边有边权,问有多少条边有可能在这张图的最小生成树上。
Input
输入第一行两个整数\(n,m(1\leq n\leq 10^5,n-1\leq m\leq 2\times 10^5)\),表示点数和边数。
接下来\(m\)行,每行三个整数\(u,v,w(1\leq u<v\leq n,1\leq w\leq 10^6)\),表示一条链接\(u,v\),且边权为\(w\)的边。
保证输入的图是简单(无自环、无重边)连通图。
Output
输出仅一行一个整数,表示可能在这张图的最小生成树上的边的条数。
Sample
Sample Input
4 5
1 2 1
1 3 1
2 3 2
2 4 3
3 4 3
Sample Output
4
Hint
样例图大概长这样:
2
/|\
1 | 4
\|/
3
Solution
将所有边按照边权从小到大排序,分成若干组,每一组中的边的边权相等。在处理第i组边时,将前i-1组边全部尝试加入生成树中。枚举第i组中的边,如果边上两个端点在不同连通分量中,那么这条边可能在MST中;否则这条边不可能在MST中。