这次小白赛好难啊,我还是太菜了,最后三题完全不会。。ort...
D.子树的大小(思维)
链接:https://ac.nowcoder.com/acm/contest/47266/D
来源:牛客网
题目描述
牛牛有一颗包含 n 个结点的 k 叉树,这些结点编号为 0…n−1 。
定义一颗 k 叉树:
1、以结点 0 为根。
2、编号为 x 结点的 k 个儿子编号分别为: k×x+1…k×x+k。
牛妹有 m 个询问表示为:q1,q2…qm。
对于第 i 个询问,你需要告诉牛妹编号为qi 的结点,其的子树中结点的个数(含结点 qi)。
输入描述:
本题采用多组案例输入,第一行一个整数 T 代表案例组数。 每组案例中,第一行包含三个空格分隔的整数:n k m。 接下来一行包含 m 个空格分隔的整数代表:q1,q2…qm。 保证: 0<n≤1e16 0<k≤100 0<m≤1e5 0≤qi<n单个测试点中所有案例 m 的和不超过 3×1e5
输出描述:
对于每组案例,输出共 m 行,每行一个整数代表答案。示例1
输入
2 9 3 5 0 8 2 1 3 1 1 1 0输出
9 1 3 4 1 1
该题简单的思维题 (我比赛时竟然没想到ort...),根据观察可以发现,以q为根节点的子树,其每层最左边的节点的编号l等于上一层最左边节点的编号*k+1,其最右边的节点的编号r等于上一层最右边的节点编号*k+k, 而该层节点个数则等于r-l+1,所以可以dfs遍历每一层,然后用个变量记录该层节点个数。
到遍历到最后时,有两种情况,一种是该层的r要大于n-1(因为n是个数,所以n个节点最后的节点编号是n-1),这时该层节点个数为n-1-l+1; 另一种情况是该层的l要大于n-1,这时表示n-1号节点不在以q为根节点的子树内,所以舍弃这一层节点,直接返回。
要注意的是,k等于1时,是单叉树,遍历的次数可能会到1e16的级别,会超时,所以需要特判。
代码如下:
#include<iostream>
using namespace std;
typedef long long LL;
LL n, k, m, ans = 0;
void dfs(LL l, LL r)
{
if(l>n-1) return ; //l大于最后一个节点编号,直接返回
if(r>n-1)
{
ans += (n-1-l+1);
return ; //这里也可以不返回,当继续向下遍历时,上面if(l>n-1)的判断也会让它返回
}
ans += (r-l+1);
dfs(l*k+1, r*k+k);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while(t --)
{
cin >> n >> k >> m;
while(m --)
{
LL q;
cin >> q;
if(k==1) cout << (n-1-q+1) << endl; //特判1的情况
else
{
ans = 0;
dfs(q, q); //开始的最左节点和最右节点,就是它本身
cout << ans << endl;
}
}
}
return 0;
}
E.剖分(树上差分)
链接:https://ac.nowcoder.com/acm/contest/47266/E
来源:牛客网
题目描述
牛牛有一颗包含 n 个结点的二叉树,这些结点编号为 1…n 。这颗树被定义为:
1、以结点 1 为根。
2、编号为 x 结点的两个儿子编号分别为: 2×x 和 2×x+1。
3、每个结点的权重初始都为 0。
牛牛接下来会对这颗树进行 m 次操作,操作的格式是以下四种之一:
1、op x (这里 op=1 )代表牛牛将以编号 x 为根结点的子树中所有结点的权重 +1。
2、op x (这里 op=2 )代表牛牛将以编号 x 为根结点的子树外的所有结点权重 +1。
3、op x (这里 op=3 )代表牛牛将根结点到编号 x 结点的路径上的所有结点权重 +1。
4、op x (这里 op=4 )代表牛牛将根节点到编号 x 结点的路径上的结点之外的所有结点权重 +1。
牛妹想知道当牛牛的所有操作结束之后,树中权重为 0,1,2…m 的结点的数量分别是多少。输入描述:
第一行输入两个空格分隔的整数:n m。
接下来 m 行,每行描述了一个牛牛进行的操作。
保证:
0<n≤1e7
0<m≤5×1e5
0<op≤4
0<x≤n输出描述:
输出一行共 m+1 个整数,代表答案。示例1
输入
7 4 1 2 3 5 4 3 2 7输出
0 2 2 1 2
该题用树上差分写。 这里用了两种差分方式,第一种差分方式是向下更新,让每个编号为i的节点的权值等于其本身加上根节点的权值,即val[i] += val[i/2]。第二种差分方式是向上更新,让每个编号为i的节点的权值等于其本身加上子节点的权值,即val[i] += val[i*2]+val[i*2+1];
由于操作1,2与以编号为x的根节点的子树有关,所以更适合第一种差分方式,向下更新。
而操作3,4与整棵树的根节点到x节点的路径有关,所以更适合第二种差分方式,向上更新。(因为每次更新时不用考虑分支的问题,只需对x更新即可。用第一种差分方式,还得对该路径上分支的节点更新)
对于操作2和4的除指定部分的加一,只要对根节点加一(相当于对整棵树的所有节点加一),然后以相应的差分方式对指点部分的节点减一即可。
所以设向下更新的差分数组dt,向上更新的ut,根节点root,则:
进行操作1时,dt[x] ++
进行操作2时,dt[x] --, root ++;
进行操作3时,ut[x] ++;
进行操作4时,ut[x] --, root ++;
最后向上或向下更新所有节点,然后求答案即可。
代码如下:
#include<iostream>
using namespace std;
const int N = 1e7+5;
int dt[N], ut[N]; //向下更新的差分数组,和向上更新的差分数组
int fg[N]; //用来记录每个权值的节点个数
void dfs(int now, int n)
{
if(now>n) return ;
dt[now] += dt[now/2]; //向下更新
dfs(now*2, n);
dfs(now*2+1, n);
ut[now] += ut[now*2] + ut[now*2+1]; //向上更新
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n, m;
cin >> n >> m;
int root = 0; //根节点
for(int i=0; i<m; i++)
{
int op, x;
cin >> op >> x;
if(op==1) dt[x] ++;
else if(op==2) dt[x] --, root ++;
else if(op==3) ut[x] ++;
else ut[x] --, root ++;
}
dfs(1, n);
for(int i=1; i<=n; i++) fg[root+ut[i]+dt[i]] ++; //该节点的权值,即是两种更新方式最终的权值加上根节点记录的值
for(int i=0; i<=m; i++) cout << fg[i] << " ";
cout << endl;
return 0;
}
F.子串的子序列(dp)
链接:https://ac.nowcoder.com/acm/contest/47266/F
来源:牛客网
题目描述
牛牛获得了一个仅由小写字母构成的字符串 s 。
他想知道这个字符串中有多少个子区间满足:区间中含有子序列 "ac" ,且区间中包含的 "ac" 子序列为偶数个。
请你输出满足上述条件的子区间的个数。输入描述:
一行一个字符串代表 s。 保证: 字符串 s 的长度不超过 5×1e5 s 仅由小写字母组成输出描述:
一行一个整数代表答案。示例1
输入
acaacb输出
6说明
共有六个区间中包含偶数个 "ac" 子序列: 1、区间 [1,5] 包含 4 个 "ac" 子序列 2、区间 [1,6] 包含 4 个 "ac" 子序列 3、区间 [2,5] 包含 2 个 "ac" 子序列 4、区间 [2,6] 包含 2 个 "ac" 子序列 5、区间 [3,5] 包含 2 个 "ac" 子序列 6、区间 [3,6] 包含 2 个 "ac" 子序列
该题求子序列的个数,所以显然用dp,对于求区间内子序列ac是偶数个数,可以开个二维dp[i][j]数组,表示以i结尾的,ac个数的奇偶性为j的区间个数。
但由于,每次更新dp数组时,如果新的一个元素是'c'时,那么更新后新增的ac的个数还与 1~i-1 中a的个数有关,也就是更新后的ac的个数和奇偶性与其前面的a的个数和奇偶性有关,所以dp数组还需要开一个维度用来记录a的奇偶性。
所以可以开个数组dp[i][j][k], 表示以i位置结尾,a的个数奇偶性为j的,ac的个数奇偶性为k的 区间个数。(用0表示偶数,1表示奇数)
然后状态转移方程,首先
当上一个状态为dp[i-1][0][0]时,如果当前新增的一个元素为'a',那么a的数量加一,偶数变奇数,ac的数量不变,所以当前状态为dp[i][1][0] ;如果是'c',那么a的数量不变,a个数的奇偶性不变,ac的数量等于当前的偶数数量加上目前a的偶数数量,所以还是偶数,所以当前状态为dp[i][0][0]
当上一状态为dp[i-1][1][0]时,如果新增了一个'a',那么a的奇数个数变偶数,ac个数的奇偶性不变,所以dp[i][0][0],如果新增'c',a个数的奇偶性不变,ac的个数为之前的偶数加上a的个数奇数,变奇数,所以当前状态为dp[i][1][1];
当上一状态为dp[i-1][1][1],如果新增一个'a',那么a个数从奇变偶,ac个数奇偶性不变,所以当前状态为dp[i][0][1],如果新增'c',a个数奇偶性不变,ac个数奇偶性从奇变偶,当前状态为dp[i][1][0];
当上一状态为dp[i-1][0][1],如果新增一个'a',那么a的个数变为奇数,ac个数奇偶性不变,所以当前状态为dp[i][1][1],如果新增'c',a个数奇偶性不变,ac个数为奇数加a的偶数,还是奇数,奇偶性不变,所以当前状态为dp[i][0][1];
如果新增的元素不为'a'和'c',那么直接继承上一状态即可。
同时由于每次新增一个元素时,存在一个区间[i, i],需要把该区间加上去,所以当新增元素为'a'时,dp[i][1][0] ++, 反之dp[i][0][0] ++;
因此状态转移方程为:
if(s[i]=='c')
{
dp[i][0][0] ++;
dp[i][0][0] += dp[i-1][0][0];
dp[i][1][0] += dp[i-1][1][1];
dp[i][1][1] += dp[i-1][1][0];
dp[i][0][1] += dp[i-1][0][1];
}
else if(s[i]=='a')
{
dp[i][1][0] ++;
dp[i][0][0] += dp[i-1][1][0];
dp[i][1][0] += dp[i-1][0][0];
dp[i][1][1] += dp[i-1][0][1];
dp[i][0][1] += dp[i-1][1][1];
}
else
{
dp[i][0][0] ++;
for(int j=0; j<2; j++) for(int k=0; k<2; k++) dp[i][j][k] += dp[i-1][j][k];
}
最后,由于0也属于偶数,所以最终结果还需要减去区间中ac为0的情况的个数,具体实现代码如下:
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 5e5+5;
LL dp[N][2][2], nea[N], nec[N], n; //nea[i], nec[i]分别表示当前位置为i时,下一个'a'所在位置和下一个'c'所在位置。
char s[N];
LL NoAC() //求不存在ac的区间个数
{
nea[n+1] = nec[n+1] = n+1;
for(int i=n; i>=1; i--)
{
nea[i] = nea[i+1];
nec[i] = nec[i+1];
if(s[i]=='c') nec[i] = i; //记录下一个'c'所在位置
if(s[i]=='a') nea[i] = i; //记录下一个'a'所在位置
}
LL num = 0;
//nea[i]表示当前位置i的下一个'a'的位置, 则nec[nea[i]]表示下一个'a'位置的下一个'c'位置
//如果区间要不包含ac的话,那么就绝对不能同时包含这两个位置。所以区间[i, nec[nea[i]]]的所有子区间必然不包含ac,所以求出该区间的子区间数即可
//每次求完后,得跳到nea[i]+1的位置。
for(LL i=1; i<=n; )
{
int ne = nea[i];
for(LL len=1; len<=ne-i+1; len++)
num += (nec[ne]-i)-len+1; //求子区间数
i = ne + 1; //跳到下一个'a'的前面一个位置。
}
//这是大佬的代码,虽然好像看懂了,但是说不清ort....
// for(LL i=1; i<=n; i++)
// num += nec[nea[i]] - i;
return num;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> s+1;
n = strlen(s+1);
LL res = 0;
for(int i=1; i<=n; i++)
{
if(s[i]=='c')
{
dp[i][0][0] ++;
dp[i][0][0] += dp[i-1][0][0];
dp[i][1][0] += dp[i-1][1][1];
dp[i][1][1] += dp[i-1][1][0];
dp[i][0][1] += dp[i-1][0][1];
}
else if(s[i]=='a')
{
dp[i][1][0] ++;
dp[i][0][0] += dp[i-1][1][0];
dp[i][1][0] += dp[i-1][0][0];
dp[i][1][1] += dp[i-1][0][1];
dp[i][0][1] += dp[i-1][1][1];
}
else
{
dp[i][0][0] ++;
for(int j=0; j<2; j++) for(int k=0; k<2; k++) dp[i][j][k] += dp[i-1][j][k];
}
res += dp[i][1][0] + dp[i][0][0];
}
cout << res - NoAC() << endl;
return 0;
}