比赛链接
A. Game with Integers
题意:A,B两人轮流对一个数 n 进行 +1 或 -1 操作
如果B操作之后 n 可以被3整除,B win,否则A win,问最终是谁赢
思路:很容易发现,当B操作时,如果 n%3!=0 则可以让他变成3的倍数,所以A要尽量让n变成能够被3整除的局面,所以只有当n开始就能被3整除时,A lose,Bwin.否则,A win ,B lose.
B. 250 Thousand Tons of TNT
题意:给定 n 个整数,找到一个 n 的因子 k ,之后按原来的序列,每 k 个数+在一起生成一个新的序列q,要使得MAX(q)-MIN(q)最大
思路:按照题意模拟即可,没找到一个K就计算出序列q,然后找到q中的最大值与最小值更新一次答案.
C. Yarik and Array
题意:给定一个由n个元素组成的数组a,找到一个没有两个偶数或者两个奇数相邻的子数组的最大值.
思路:如果我们不考虑需要交替的奇偶检验这一限制,那么此题就是线性dp的模板题状态转移方程为
考虑这个限制之后,只需要加上一个if判断即可
if((a[i] xor a[i-1]) & 1) //判断两数是否一个是奇数一个是偶数
dp[i]=max(dp[i-1]+a[i],a[i]);
else
dp[i]=a[i];
最后找到dp数组中的最大值输出即可
D. Yarik and Musical Notes
题意:
给定一个数组a,而b数组中,然后找有多少对(i,j)(i<j)使得
,最后,我们可以将此方程化简成
的形式,最后我们可以找到3组特殊解:
a[i]=a[j]
a[i]=1.a[j]=2
a[i]=2.a[j]=1
遍历a数组,用一个map存下每个数出现的次数,然后遍历的时候计算答案即可.
map<int,int> mp;
for(int i=1;i<=n;i++)
{
cin>>p;
ans+=mp[p];
if(p==1)
ans+=mp[2];
if(p==2)
ans+=mp[1];
mp[p]++;
}
注意!这里n的大小为2e5,如果用unordered_map的话,由于哈希冲突,unordered_map单次的时间复杂度可能由O(1)退化到O(n),所以这里用unordered_map会超时,所以用的map,对unordered_map底层感兴趣的话可以自行搜索资料学习
E. Queue Sort
题意:给定一个长度为n的整数数组 a ,为了让这个数组按不递减的顺序排序,可以多次执行一个操作:
- 提取数组的第一个元素,并将其插入末尾;
- 将个元素与前一个元素对调,直到它成为第一个元素或严格大于前一个元素。
例如,如果对数组 [ 4,3,1,2,6,4 ]执行操作,它将发生如下变化:
- [ 4,3,1,2,6,4]
- [ 3,1,2,6,4,4]
- [ 3,1,2,6,4,4]
- [ 3,1,2,4,6,4]
问对该数组排序所需的最少操作数,或者不能变成一个有序的数组
思路:
首先我们考虑在什么情况下是不能变成一个有序数组的,手玩之后会发现:当数组的第一个元素是数组中最小的元素时,执行这个操作又会回到当前的数组。
也就是说,在最开始的数组中,最小的数后面的数是不会参与到操作中来的,而前面的数是可以通过操作进行排序的。
所以,在最原始的数组中,如果最小的数后面的子数组不是有序的,那么就不可能变成有序数组
那么最小的操作次数就是最小的数前面的数的个数了
ll minv=1e9+5;
int p;
for(int i=1;i<=n;i++)
{
cin>>b[i];
if(b[i]<minv)
{
minv=b[i];
p=i;
}
}
for(int i=p+1;i<n;i++)
{
if(b[i]>b[i+1])
{
cout<<"-1"<<endl;
return;
}
}
cout<<p-1<<endl;
F. Alex's whims
题意:有一颗有n个节点的树,我们一开始可以自定义这颗树的形状,给出q个询问,每次询问树上是否有两个距离正好为的叶子节点,每次询问之后,可以执行一次操作:
选择顶点 u 、 v1 和 v2,使得 u和 v1 之间有一条边, u和 v2 之间没有边。然后删除 u和 v1 之间的边,并在 u和 v2 之间添加一条边。如果操作后图形不再是树形,则不能执行此操作。
为了每次每次都有两个距离正好为的叶子节点,输出最开始的树的形状,然后每次输出该如何执行操作或者说明不需要操作
思路:看懂题意之后就是一个脑筋急转弯了,其实我们一开始可以让这颗树变成一条链,链的头尾分别是1,n号节点,我们每次只需要让1,n号节点的距离为就好了
G. Unusual Entertainment
题意:给定一颗顶点为1,大小为n的有根树,再给出一个长度为n的排列p,有q次询问l,r,x的三元组,对于每个三元组,问中是否至少有一个节点是编号为x节点的后代。
思路:离线处理+树上启发式合并+前缀和处理
离线处理:
首先存下对每个x的询问的l,r以及该次询问的编号id,这样我们可以在树上搜索的时候计算该节点的询问答案,并根据该次询问的编号记录到对应编号的答案序列中
前缀和处理:
考虑一颗顶点编号为a的子树,我们可以暴力搜索这颗子树,并将搜索到的节点编号在排列P中的位置记录在一个树状数组tr[N]中,这样我们对于a的询问,只需要判断tr[r]-tr[l-1]是否大于0就能回答该次询问
树上启发式合并(dsu on tree):
对于每个节点,如果我们都暴力搜索它的子树的信息,很容易发现时间复杂度是的。
考虑优化:对于一个节点所有儿子的搜索,我们可以发现,对于最后一个儿子为子树时搜索的信息可以保留下来,然后再加上其他儿子以及节点自身的信息。这样我们对于每个节点,就少搜索了一个儿子为子树的所有信息,这一步叫做合并,即把一个儿子的信息合并到父节点。
那么合并的儿子应该是哪个儿子呢?可以发现应该是子树最大的儿子,即重儿子,所以叫做启发式合并。
想学习树上启发式合并的可以看看这里:
这是我随意找的一篇,也可以通过别的方法学习。
来看代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
#include<set>
using namespace std;
const int N=2e5+5;
typedef long long ll;
typedef pair<int,int> PII;
int e[N],ne[N],h[N],idx,tr[N],hson[N],sz[N],pos[N],ans[N];
int n,q;
typedef struct{
int l,r,id;
}ques;
vector<vector<ques> > que(N);
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int lowbit(int x)
{
return x&-x;
}
void ins(int x,int c)
{
for(int i=x;i<=n;i+=lowbit(i))
{
tr[i]+=c;
}
}
int ask(int x)
{
int ans=0;
for(int i=x;i;i-=lowbit(i))
{
ans+=tr[i];
}
return ans;
}
void dfs(int x,int fa)
{
sz[x]=1;
int maxv=-1;
for(int i=h[x];~i;i=ne[i])
{
int j=e[i];
if(j==fa) continue;
dfs(j,x);
//更新子树大小最大的节点为重儿子
if(sz[j]>maxv)
{
maxv=sz[j];
hson[x]=j;
}
sz[x]+=sz[j];
}
}
void update(int x,int fa,int c)
{
ins(pos[x],c);
for(int i=h[x];~i;i=ne[i])
{
int j=e[i];
if(j==fa) continue;
update(j,x,c);
}
}
void dsu(int x,int fa)
{
//先搜轻儿子
for(int i=h[x];~i;i=ne[i])
{
int j=e[i];
if(j==fa || j==hson[x]) continue;
dsu(j,x);
//去除搜过的这个轻儿子信息的影响
update(j,x,-1);
}
//再搜重儿子
if(hson[x])
dsu(hson[x],x);
//这里保留了重儿子的信息 ,然后我们要加回轻儿子的影响
for(int i=h[x];~i;i=ne[i])
{
int j=e[i];
if(j==fa || j==hson[x]) continue;
update(j,x,1);
}
//还有节点自身的信息 ins表示在有该位置(pos[i])的编号
ins(pos[x],1);
//对该节点的所有询问查找答案
for(int i=0;i<que[x].size();i++)
{
//ask为查找该位置前面有多少个之前插入的值
ans[que[x][i].id]=ask(que[x][i].r)-ask(que[x][i].l-1);
}
}
void solve()
{
cin>>n>>q;
//初始化
idx=0;
for(int i=1;i<=n;i++)
{
h[i]=-1;
//i节点的重儿子
hson[i]=0;
//i子树的大小
sz[i]=0;
//树状数组清空
tr[i]=0;
//i号节点的所有询问
que[i].clear();
}
int a,b;
//建树
for(int i=1;i<n;i++)
{
cin>>a>>b;
add(a,b),add(b,a);
}
for(int i=1;i<=n;i++)
{
int p;
cin>>p;
//p在排列中的位置
pos[p]=i;
}
int l,r,x;
for(int i=1;i<=q;i++)
{
//答案数组
ans[i]=0;
cin>>l>>r>>x;
//记录x节点的询问
que[x].push_back({l,r,i});
}
//第一个dfs是用来查找每个节点的重儿子的
dfs(1,0);
//树上启发式合并
dsu(1,0);
for(int i=1;i<=q;i++)
{
if(ans[i])
cout<<"YES"<<"\n";
else
cout<<"NO"<<"\n";
}
cout<<"\n";
}
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin>>t;
while(t--)
{
solve();
}
}
结束语
博主打算努努力长期更新Codeforce的div2,div3的题解(可能有时也会发一下atcoder,牛子,甚至是一些其他比赛的题解),当然博主也是个蒟蒻,并不是发出来的题解一定是自己独立思考出来的(肯定也有参考了别人的题解的),但是别人的题解可能比较抽象,博主的理念是将题解以刚入门的小白也能听懂的方式呈现出来,或者是将需要学习的知识点出来。
如果有帮助到你学习(大佬轻喷),请一定不要吝啬点赞支持,有什么建议或者是疑问可以发到评论区,我一定是会回复的(立下一个flag)