A 小橘编译器
知识点:字符串,模拟
按照题意模拟即可。先判断给定字符串是否含有 // ,如果没有,直接输出原字符串,如果有,则再判断字符串是不是以 // 开头,是则输出 null,否则输出删去被忽略的后缀得到的字符串。
参考代码:
void solve()
{
string s;
cin>>s;
auto pos=s.find("//");
if(pos==string::npos)
cout<<s<<el;
else if(pos==0)
cout<<"null\n";
else
cout<<s.substr(0,pos)<<el;
}
B 小橙的好序列
知识点:组合数学,差分
思路一:
的值固定为
,从
开始,
的可选值只与
有关,为
区间中的整数,共有
种,由分布乘法原理,可知总方案数为
,注意答案需要对
取模。
思路二:
从差分的角度考虑,令 ,
,则有好序列的差分序列满足对任意
都有
。反过来说,任意满足上诉条件的差分序列对应唯一一个好序列,可以直接考虑有多少个这样的差分序列,每个
独立,取值为
区间中的整数,不难得到总方案数为
。
参考代码:
using LL=long long;
const int p=998244353;
void solve()
{
LL n,k,ans=1;
cin>>n>>k;
for(int i=1;i<=n;i++)
ans=ans*(2*k+1)%p;
cout<<ans<<'\n';
}
C 小橙的完美序列
知识点:位运算,哈希表
先考虑如果一个序列是「完美序列」具有什么性质。
不妨设 ,是一个「完美序列」,则存在一个
,满足
,换一个形式,其实就是
(原等式两边同时异或
得到),也就是序列上每一个元素异或这个元素的下标得到同一个值;反过来说,如果序列上每一个元素异或下标都得到同一个值,那么这个序列是「完美序列」。
于是,计算 改成「完美序列」所需的最少操作次数,只需要对每个
异或它的下标,统计新序列哪一个元素出现次数最多,记这个最多次数为
,那么答案就是
。
void solve()
{
int n,a,k=1;
cin>>n;
map<int,int>cnt;
for(int i=1;i<=n;i++)
{
cin>>a;
cnt[a^i]++;
}
for(auto [_,b]:cnt)
k=max(k,b);
cout<<n-k<<el;
}
DE 小橙的幸运数
知识点:模运算,记忆化搜索,基环树,贪心,倍增
思路一:
根据题意可以得到,每一次操作会使 不变或增加,增量最多有
种,每次增量仅由
决定,这启示我们可以从同余类的角度考虑,同余类用通俗的话来说就是:所有整数,按对
取模后得到的结果进行分类,最终会得到
个互相不交的集合。
现在回到问题,
- 每一次操作会使
不变或增加,如果初始值就大于目标值
,显然是
No; - 我们称
为目标,称
为弱化目标。若
能通过若干次操作达到目标,则在达到目标的过程中,一定会至少一次达到弱化目标。
- 在操作前,令
,模拟操作过程,第
次操作产生的新值为
,其中
,以此类推,得到
,其中
。如果在达到弱化目标前,先有某两个值满足
,不难可知,之后
会在同余类上会陷入循环,永远无法达到弱化目标,是
No。 - 若达到弱化目标,此时
是
No,是
Yes,则进入下一步;
- 我们已经有
了,继续操作,如果能再次使
,记录这个过程的总增量
,只需要判断对于此时的
是否满足
是
的倍数,即可得到结果;如果不能再次达到弱化目标,陷入其他循环,显然是
NO。
对于 easy 版,步骤3和5都可以直接迭代模拟,迭代不超过 次会陷入循环或达到弱化目标,复杂度
。
对于 hard 版,直接迭代的复杂度是不可接受的。考虑步骤3,令 表示满足
的初始值
,达到
时,需要的最少总增量,可以使用记忆化搜索计算;也可以从图的角度考虑,对
个同余类建立节点,根据序列
连接
条边,会得到若干个基环树,之后计算节点间的距离。考虑步骤5, 对于
满足
,如果继续操作能再次使
,则这个过程的总增量
值不会变化,只需计算一次即可。总复杂度
。
参考代码(记忆化搜索做法):
#define int long long
void solve()
{
int m,c,q;
cin>>m>>c>>q;
int cc=c%m;
vector<int>a(m),f(m,-1),vis(m);
for(int i=0;i<m;i++)
cin>>a[i];
f[cc]=0,vis[cc]=1;
auto cal=[&](auto &self,int x)->int
{
if(vis[x])
return f[x];
vis[x]=1;
int res=self(self,(x+a[x])%m);
if(res!=-1)
f[x]=res+a[x];
return f[x];
};
int d=-1;
if(cal(cal,(cc+a[cc])%m)!=-1)
d=cal(cal,(cc+a[cc])%m)+a[cc];
while(q--)
{
int x;
cin>>x;
int dx=cal(cal,x%m);
if(dx==-1)
{
cout<<"No\n";
continue;
}
x+=dx;
if(x==c)
cout<<"Yes\n";
else if(x>c || d==-1 || (c-x)%d!=0)
cout<<"No\n";
else
cout<<"Yes\n";
}
}
思路二:
序列 的值是非负整数,每次操作后
的值单调不减,可以考虑使用倍增。令
表示对于满足
的初始值
,进行
次操作的总增量,预处理
数组,每次询问,不断贪心地增加
,判断能否到达
,总复杂度
。关于此思路更详细的说明,可以观看
的视频题解。
(题外话:如果将序列 的值扩展到负整数,思路二的倍增将无法处理,但思路一的记忆化搜索仍然可以计算)
F 小橙的异或和
知识点:均摊分析,并查集
思路一:
每一个 在进行不超过
次除以大于
的正整数,会变为
,而
除以任何数都为
,换一句话说,每一个
在变成
之前最多变化不超过
次,如果变成
则之后将不再变化。
于是,可以考虑维护一个下标集合 ,若
,代表当前
非零,每次操作一,直接修改区间内的所有非零元素,同时维护异或和,集合
在C++中可以简单地使用
set 维护,总复杂度为 。
思路二:
用并查集维护,创建 个节点的并查集,初始时,每个节点指向自身,如果
变成
,则将第
个节点指向
节点,于是,每次查找节点
的根节点就可以找到下标从
开始的第一个非零元素的下标。总复杂度
。
(此外,也有线段树上二分,势能线段树等做法,在此不多赘述)
参考代码(并查集做法):
void solve()
{
int n,q,ans=0;
cin>>n>>q;
vector<int>a(n+1),fa(n+2);
for(int i=1;i<=n;i++)
{
cin>>a[i];
ans^=a[i];
fa[i]=i;
}
fa[n+1]=n+1;
auto find=[&](auto &self,int u)->int
{
return fa[u]==u?u:fa[u]=self(self,fa[u]);
};
while(q--)
{
int op,l,r;
cin>>op;
if(op==2)
{
cout<<ans<<"\n";
continue;
}
cin>>l>>r;
for(int i=find(find,l);i<=r;i=find(find,i+1))
{
ans^=a[i];
a[i]/=i-l+1;
if(!a[i])
fa[i]=i+1;
ans^=a[i];
}
}
}
G 小橙交换水果
知识点:最近公共祖先,树上前缀和,多源BFS
令 为节点
和节点
的距离,
为度数大于
的节点到节点
的距离,对于询问
和
,我们有以下结论:
-
若树上不存在度数大于
的节点,则无解。
-
若在
和
的简单路径上,不包括端点
和
,存在度数大于
的节点,则最少操作次数为
;
-
若在
和
的简单路径上,不包括端点
和
,不存在度数大于
的节点,则最少操作次数为
。
判断简单路径上,不包括端点,是否存在度数大于 的节点,可以通过 树上前缀和 得到:对于度数大于
的节点,赋一个权值
,计算树上前缀和数组
,其中
表示从节点
到根节点的权值和,则
到
简单路径的权值和为
,再减去两个端点
和
的权值,就可以得到简单路径不包括端点的权值和,通过这个权值和是否大于
,判断是否有度数大于
的节点。
数组可以通过一次 多源BFS 得到:BFS的起点为所有度数大于
的节点,计算最短路。
总复杂度 或
,取决于LCA的实现方式。
参考代码:
void solve()
{
int n,q;
cin>>n>>q;
vector<vector<int>>g(n+1);
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int>dep(n+1);
vector<array<int,21>>fa(n+1);
auto dfs=[&](auto &self,int u,int fau)->void
{
dep[u]=dep[fau]+1;
fa[u][0]=fau;
for(int i=1;i<21;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto v:g[u])
if(v!=fau)
self(self,v,u);
};
dfs(dfs,1,0);
auto lca=[&](int u,int v)->int
{
if(dep[u]<dep[v])
swap(u,v);
for(int i=20;i>-1;i--)
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(u==v)
return u;
for(int i=20;i>-1;i--)
if(fa[u][i]!=fa[v][i])
{
u=fa[u][i];
v=fa[v][i];
}
return fa[u][0];
};
vector<int>pre(n+1),near(n+1),vis(n+1);
queue<int>que;
int ok=0;
for(int u=1;u<=n;u++)
if(g[u].size()>=3)
{
ok=1;
pre[u]=1;
que.push(u);
vis[u]=1;
}
auto dfs2=[&](auto &self,int u,int fau)->void
{
pre[u]+=pre[fau];
for(auto v:g[u])
if(v!=fau)
self(self,v,u);
};
dfs2(dfs2,1,0);
while(que.size())
{
int u=que.front();
que.pop();
for(auto v:g[u])
if(!vis[v])
{
near[v]=near[u]+1;
vis[v]=1;
que.push(v);
}
}
while(q--)
{
int u,v;
cin>>u>>v;
if(!ok)
{
cout<<-1<<el;
continue;
}
int ll=lca(u,v),dist=dep[u]+dep[v]-dep[ll]*2;;
if(pre[u]+pre[v]-pre[fa[ll][0]]-pre[ll]-(g[u].size()>=3)-(g[v].size()>=3)>0)
cout<<dist*2+2<<el;
else
cout<<dist*2+min(near[u],near[v])*4+4<<el;
}
}

京公网安备 11010502036488号