河南萌新联赛2024第(一)场:河南农业大学 题目讲解c++[2]
链接:https://ac.nowcoder.com/acm/contest/86639/E
来源:牛客网
题目E描述
染火枫林,琼壶歌月,长歌倚楼。
岁岁年年,花前月下,一尊芳酒。
水落红莲,唯闻玉磬,但此情依旧。
给定一棵有 nnn 个结点的有根树,根结点编号为 111,每个结点都对应一个小写拉丁字母 aia_{i}ai 作为它们的权值。
现在有 qqq 次操作,操作分为两种:
1.1.1. 给定结点编号 xxx , 将以 xxx 为根的子树中的所有结点(包括自己)的权值 aia_{i}ai 全部 +1+ 1+1。(a→b,b→c,……,y→z,z→a)(a \rightarrow b , b \rightarrow c ,…… ,y \rightarrow z , z \rightarrow a)(a→b,b→c,……,y→z,z→a)
2.2.2. 给定两个结点编号 x,yx , yx,y , 询问以 x,yx,yx,y 为端点的简单路径上有多少个结点可以作为树上任意一条长度不小于2的路径所构成的回文串的回文中心。
对于每次操作 222 ,输出一个整数表示答案。
这道题对于没做过类似题基础的真的好难!!!
在做这道题之前得先学习树状数组的概念(如果有想更多了解知识的,最好把线段树也学习了),使得在n次查询时,可以快速拿到路径上任意路上可作为回文节点的累加和。
做题思路为,
首先求出所有节点的父节点,节点大小,节点深度,节点重儿子(子树大小最大),子节点相较于节点,父节点相较于节点的偏移情况。以及该节点的权重情况(可以为回文节点的情况)。
#include<bits/stdc++.h>
using namespace std;
#define N 200001
basic_string<int> edges[N];//;将其他类型变成可以类似成string一样的操作,通过取String单个字符类似的方式取单个类型
int n;
int fa[N],sz[N],dep[N],son[N],off[N][27],w[N],a[N];
void dfsA(int x,int F){
//对父节点,树大小,深度,进行更新
fa[x] = F, sz[x]++,dep[x]=dep[F]+1;
//能够继续探索的显然不是它的父节点,不然就会死循环了
for(int i:edges[x])if(i!=F){
//保证了最先访问到的是叶子节点。
dfsA(i,x);
//更新重儿子
if(sz[i]>sz[son[x]]) son[x] = i;
//更新子节点相较于父节点的偏移位置情况
off[x][a[i]]++;
sz[x]+=sz[i];
}
//把a[x]更新为它相较于父节点的偏移位置
a[x] = (a[x]-a[F]+26)%26;
// 如果不是根节点,那么还需要把父节点相较于该节点的偏移位置加一
if(x>1){
off[x][(26-a[x])%26]++;
}
// 判断它是不是一个回文节点
for(int i=0;i<26;i++) w[x]+=off[x][i]>=2;
}
//首先输入数据
int main(){
cin>>n;
int x,y;
//完成对边情况的录入
for(int i=1;i<n;i++){
cin>>x>>y;
edges[x]+=y;
edges[y]+=x;
}
//完成对字符的录入
for(int i=1;i<=n;i++) {
string st;
cin>>st;
a[i]=st[0]-'a';
}
dfsA(1,0) ;
}
接下来进行重链划分,将树划分为多个重链,并对每个节点分配dfn序,
...
int tp[N] ,dfn[N],it;
void dfsB(int x,int F,int t){
tp[x] = t; dfn[x]=++it;
if(son[x])dfsB(son[x],x,t);
for(int i:edges[x])if(i!=F&&i!=son[x]){
dfsB(i,x,i);
}
}
int main(){
...
dfsB(1,0,1);
}
接下来根据dfn序构造树状数组
void Add(int x,int y) {
int x_=dfn[x];
for(;x_<=n;x_+=-x_&x_)
f[x_]+=y;
}
int main(){
for(int i=1;i<=n;i++)if(w[i]){
Add(i,1);
}
}
由于它只能对它以及它的子树产生影响,所以只可能对它和它父亲节点的回文节点情况产生影响。所以在我们进行+操作时,只用判断它本身和它的父节点与回文节点的变化情况
...
int get(int x,int y=0){for(;x;x-=(-x&x))
{y+=f[x];}return y;}
int Get(int x,int y) {
int res=0;
//直到它们两到同一重链
while(tp[x]!=tp[y]){
//保证x所在重链的重链头高度要大于等与y的所在重链的重链头高度
if(dep[tp[x]]<dep[tp[y]]){
swap(x,y);
}
res+=get(dfn[x])-get(dfn[tp[x]]-1);
//求出当前点到重链头的所有回文节点数
x=fa[tp[x]];
//将x转移到重链头的父节点
}
//保证x的dfn序号要小于等与y的dfn序
if(dfn[x]>dfn[y])swap(x,y);
res+=get(dfn[y])-get(dfn[x]-1);
return res;
}
int main(){
int t;
cin>>t;
while(t--){
int op;
cin>>op>>x;
if(op==1){
//如果x==1,所有节点都+1,相当于回文节点没有变化
if(x==1) continue;
//如果父节点显示为回文节点,或者它本身显示为回文节点,都应该对树桩数组进行修改
if(w[fa[x]])Add(fa[x],-1);
if(w[x])Add(x,-1);
//对父节点和该节点的回文节点情况进行维护
//修改该节点对父节点的偏移情况
w[fa[x]]-=(off[fa[x]][a[x]]==2);
off[fa[x]][a[x]]--;
//修改父节点对该节点的偏移情况
w[x]-=(off[x][(26-a[x])%26]==2);
off[x][(26-a[x])%26]--;
a[x]=(a[x]+1)%26;
//修改该节点对父节点的偏移情况
off[fa[x]][a[x]]++;
w[fa[x]]+=(off[fa[x]][a[x]]==2);
//修改父节点对该节点的偏移情况
off[x][(26-a[x])%26]++;
w[x]+=(off[x][(26-a[x])%26]==2);
//如果产生新的回文节点,维护树状数组
if(w[fa[x]])Add(fa[x],1);
if(w[x])Add(x,1);
}
else
{
cin>>y;
cout<<Get(x,y)<<'\n';
}
}
}
所有代码
#include<bits/stdc++.h>
using namespace std;
#define N 200050
basic_string<int> edges[N];//;将其他类型变成可以类似成string一样的操作,通过取String单个字符类似的方式取单个类型
int n;
int fa[N],sz[N],dep[N],son[N],off[N][27],w[N],a[N];
void dfsA(int x,int F){
//对父节点,树大小,深度,进行更新
fa[x] = F, sz[x]++,dep[x]=dep[F]+1;
//能够继续探索的显然不是它的父节点,不然就会死循环了
for(int i:edges[x])if(i!=F){
//保证了最先访问到的是叶子节点。
dfsA(i,x);
//更新重儿子
if(sz[i]>sz[son[x]]) son[x] = i;
//更新子节点相较于父节点的偏移位置情况
off[x][a[i]]++;
sz[x]+=sz[i];
}
//把a[x]更新为它相较于父节点的偏移位置
a[x] = (a[x]-a[F]+26)%26;
// 如果不是根节点,那么还需要把父节点相较于该节点的偏移位置加一
if(x>1){
off[x][(26-a[x])%26]++;
}
// 判断它是不是一个回文节点
for(int i=0;i<26;i++) w[x]+=off[x][i]>=2;
}
int tp[N] ,dfn[N],it;
void dfsB(int x,int F,int t){
tp[x] = t; dfn[x]=++it;
if(son[x])dfsB(son[x],x,t);
for(int i:edges[x])if(i!=F&&i!=son[x]){
dfsB(i,x,i);
}
}
int f[N];
void Add(int x,int y) {
int x_=dfn[x];
for(;x_<=n;x_+=-x_&x_)
f[x_]+=y;
}
int get(int x,int y=0){for(;x;x-=(-x&x)){y+=f[x];}return y;}
int Get(int x,int y) {
int res=0;
//直到它们两到同一重链
while(tp[x]!=tp[y]){
//保证x所在重链的重链头高度要大于等与y的所在重链的重链头高度
if(dep[tp[x]]<dep[tp[y]]){
swap(x,y);
}
res+=get(dfn[x])-get(dfn[tp[x]]-1);
//求出当前点到重链头的所有回文节点数
x=fa[tp[x]];
//将x转移到重链头的父节点
}
//保证x的dfn序号要小于等与y的dfn序
if(dfn[x]>dfn[y])swap(x,y);
res+=get(dfn[y])-get(dfn[x]-1);
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
int x,y;
//完成对边情况的录入
for(int i=1;i<n;i++){
cin>>x>>y;
edges[x]+=y;
edges[y]+=x;
}
//完成对字符的录入
for(int i=1;i<=n;i++) {
string st;
cin>>st;
a[i]=st[0]-'a';
}
dfsA(1,0) ;
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i;
// for(int j=0;j<26;j++){
// cout<<j<<":"<<off[i][j]<<" ";
// }
// cout<<endl;
// }
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i<<" "<<w[i]<<" ";
// }
// cout<<endl;
// cout<<"sz:";
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i<<" "<<sz[i]<<" ";
// }
// cout<<endl;
// cout<<"dep:";
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i<<" "<<dep[i]<<" " ;
// }
// cout<<endl;
// cout<<"son:";
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i<<" "<<son[i]<<" " ;
// }
dfsB(1,0,1);
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i<<"tp,dfn"<<tp[i]<<","<<dfn[i]<<endl;
// }
// cout<<endl;
for(int i=1;i<=n;i++)if(w[i]){
Add(i,1);
}
// for(int i=1;i<=n;i++){
// cout<<"i:"<<i<<" "<<f[dfn[i]]<<" ";
// }
int t;
cin>>t;
while(t--){
int op;
cin>>op>>x;
if(op==1){
//如果x==1,所有节点都+1,相当于回文节点没有变化
if(x==1) continue;
//如果父节点显示为回文节点,或者它本身显示为回文节点,都应该对树桩数组进行修改
if(w[fa[x]])Add(fa[x],-1);
if(w[x])Add(x,-1);
//对父节点和该节点的回文节点情况进行维护
//修改该节点对父节点的偏移情况
w[fa[x]]-=(off[fa[x]][a[x]]==2);
off[fa[x]][a[x]]--;
//修改父节点对该节点的偏移情况
w[x]-=(off[x][(26-a[x])%26]==2);
off[x][(26-a[x])%26]--;
a[x]=(a[x]+1)%26;
//修改该节点对父节点的偏移情况
off[fa[x]][a[x]]++;
w[fa[x]]+=(off[fa[x]][a[x]]==2);
//修改父节点对该节点的偏移情况
off[x][(26-a[x])%26]++;
w[x]+=(off[x][(26-a[x])%26]==2);
//如果产生新的回文节点,维护树状数组
if(w[fa[x]])Add(fa[x],1);
if(w[x])Add(x,1);
}
else
{
cin>>y;
cout<<Get(x,y)<<'\n';
}
}
}
链接:https://ac.nowcoder.com/acm/contest/86639/F
来源:牛客网
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目F描述
现在有长度为 nnn 的数组 aaa,你可以在两种操作中选择一种进行最多一次操作。
- 操作1:
选择一个数 iii(1≤i≤n)(1\leq i\leq n)(1≤i≤n) 使得 ai:=ai+xa_i:=a_i+xai:=ai+x,xxx 可以是 [1,⌊n/i⌋][1,\lfloor n/i \rfloor][1,⌊n/i⌋] 范围内任意正整数( ⌊⌋\lfloor\rfloor⌊⌋ 表示向下取整 )。
- 操作2:
选择一个数 iii(1≤i≤n)(1\leq i\leq n)(1≤i≤n) 使得 ai:=ai×xa_i:=a_i \times xai:=ai×x,xxx 可以是 [1,⌊n/i⌋][1,\lfloor n/i \rfloor][1,⌊n/i⌋] 范围内任意正整数。
请问进行操作后,最大的数组异或和是多少?
数组异或和:数组 aaa 中 a1⊕a2⊕a3…⊕ana_1\oplus a_2 \oplus a_3 … \oplus a_na1⊕a2⊕a3…⊕an的值,⊕\oplus⊕ 表示异或。
输入描述:
输入包含两行.
第一行一个正整数 nnn (1≤n≤2×105)(1\leq n\leq 2\times10^5)(1≤n≤2×105) 表示数组 aaa 的长度。
第二行 nnn 个正整数 aia_iai (1≤ai≤109)(1\leq a_i \leq10^9)(1≤ai≤109) 表示数组 aaa 的元素。
输出描述:
输出包含一行一个整数,表示最大的数组异或和。
这题采用暴力枚举做即可。首先把这个数组的异或和求出来,然后依次更换每个元素的值,找到最大的数组的异或和即可。
#include<bits/stdc++.h>
#define ll unsigned long long
using namespace std;
int main(){
int n;
cin>>n;
vector<ll>a(n+1);
ll sum=0;
ll t;
//求出原始数组的异或和
for(int i=1;i<=n;i++){
cin>>a[i];
sum^=a[i];
}
//由于异或具有,A异或 A=0的性质,我们可以先把元素从这个sum中取出来,再用新的值放进去
ll res = sum;
for(int i=1;i<=n;i++){
for(int j=1;j<=n/i;j++){
res = max(res,sum^a[i]^(a[i]*j));
res = max(res,sum^a[i]^(a[i]+j));
}
}
//找到最大的即可
cout<<res;
}
链接:https://ac.nowcoder.com/acm/contest/86639/G
来源:牛客网
题目G描述
在某大陆上面有 nnn 个国家,作为旅行者兼冒险家的你想以一种既定的路线(即从1到 nnn )去畅游这 nnn 个国家,但由于这 nnn 个国家并不太平,因此每到一个国家你都需要消耗 aia_iai 点的生命力来帮助这个国家重回往日的安宁然后再进行畅游。不过天生拥有神力的你却有 kkk 次释放神力的机会来帮助这个国家恢复安宁,且释放神力时不消耗任何生命力。你在旅行前拥有 mmm 点的生命力,若你在旅途中不幸用完全部的生命力,则便会回到你诞生的地方陷入沉睡。现在请问你最多可以畅游多少个国家。注意:若在当前国家消耗完生命力则意味着你并没有畅游该国家。
输入描述:
输入包含 2 行。
第一行三个正整数 n,m,k(1≤n≤2×105,1≤m≤1018,0≤k≤2×105)n,m,k(1≤n≤2\times10^5,1≤m≤10^{18},0≤k≤2\times10^5)n,m,k(1≤n≤2×105,1≤m≤1018,0≤k≤2×105) ,分别代表国家的个数,你拥有的初始生命力,你可以释放神力的次数。
第二行包含 nnn 个正整数,第 iii 个正整数 ai(1≤ai≤1018)a_i (1≤a_i≤10^{18})ai(1≤ai≤1018) 代表你不释放神力帮助第 iii 个国家需要消耗的生命力的大小。
输出描述:
输出包含一行,共一个数,表示你能畅游的国家的个数。
该题目的思路在于,从1走到n时,我们可以先假设前k个都使用了神力即走了k个国家,当走到第k+1个国家时,应该从前k+1中找到需要最小消耗神力,如果m-完最小消耗神力后>0,说明,走了一个国家。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//定义一个最小堆
priority_queue<ll,vector<ll>,greater<ll>>q;
ll n,m,k;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
ll ans=0;
for(ll i=1;i<=n;++i)
{
ll x;
cin>>x;
//如果存在可以释放神力的点 ans++
if(k){q.push(x);++ans;--k;}
else
{
//如果当前值比堆栈最小的值大,则进行更新,把最小的取出来。
if(q.size()&&q.top()<x)
{
q.push(x);
x=q.top();
q.pop();
}
if(m>x){m-=x;++ans;}
else break;
}
}
cout<<ans;
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/86639/H
来源:牛客网
题目H描述
现在有长度为 nnn 的数组 aaa,你可以在两种操作中选择一种进行最多一次操作。
- 操作1:
选择一个数 iii(1≤i≤n)(1\leq i\leq n)(1≤i≤n) 使得 ai:=ai+xa_i:=a_i+xai:=ai+x,xxx 可以是 [1,n][1,n][1,n] 范围内任意正整数。
- 操作2:
选择一个数 iii (1≤i≤n)(1\leq i\leq n)(1≤i≤n) 使得 ai:=ai×xa_i:=a_i \times xai:=ai×x,xxx 可以是 [1,n][1,n][1,n] 范围内任意正整数。
请问进行操作后,最大的数组总和是多少?
输入描述:
输入包含两行.
第一行一个正整数 nnn (1≤n≤2×105)(1\leq n\leq 2 \times 10^5)(1≤n≤2×105) 表示数组 aaa 的长度。
第二行 nnn 个正整数 aia_iai (1≤ai≤109)(1\leq a_i \leq10^9)(1≤ai≤109) 表示数组 aaa 的元素。
输出描述:
输出包含一行一个整数,表示最大的数组总和。
这个题非常简单,我们只需要找到数组中的最大值,就可以完成求解。可以在累加的过程中找到最大值
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int n;
ll max=0;
cin>>n;
ll sum=0;
int a;
//找到最大值
for(int i=0;i<n;i++) {
cin>>a;
sum+=a;
if(a>max) max=a;
}
//选择最大的情况
ll a1 = sum+n;
ll a2 = sum-max+max*n;
cout<<(a1>a2?a1:a2);
}