Codeforces Round 909(Div 3)朴素题解

比赛链接

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的模板题状态转移方程为

dp[i]=max(dp[i-1]+a[i],a[i])

考虑这个限制之后,只需要加上一个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数组中b_{i}=2^{a_{i}},然后找有多少对(i,j)(i<j)使得b_{i}^{b_{j}}=b_{j}^{b_{i}},最后,我们可以将此方程化简成\frac{a_{i}}{^{a_{j}}}=2^{a_{i}-a_{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个询问,每次询问树上是否有两个距离正好为d_{i}的叶子节点,每次询问之后,可以执行一次操作:

选择顶点 u 、 v1 和 v2,使得 u和 v1 之间有一条边, u和 v2 之间没有边。然后删除 u和 v1 之间的边,并在 u和 v2 之间添加一条边。如果操作后图形不再是树形,则不能执行此操作。

为了每次每次都有两个距离正好为d_{i}的叶子节点,输出最开始的树的形状,然后每次输出该如何执行操作或者说明不需要操作

思路:看懂题意之后就是一个脑筋急转弯了,其实我们一开始可以让这颗树变成一条链,链的头尾分别是1,n号节点,我们每次只需要让1,n号节点的距离为d_{i}就好了

G. Unusual Entertainment

题意:给定一颗顶点为1,大小为n的有根树,再给出一个长度为n的排列p,有q次询问l,r,x的三元组,对于每个三元组,问p_{l},p_{l+1},...,p_{r}中是否至少有一个节点是编号为x节点的后代。

思路:离线处理+树上启发式合并+前缀和处理

离线处理

首先存下对每个x的询问的l,r以及该次询问的编号id,这样我们可以在树上搜索的时候计算该节点的询问答案,并根据该次询问的编号记录到对应编号的答案序列中

前缀和处理:

考虑一颗顶点编号为a的子树,我们可以暴力搜索这颗子树,并将搜索到的节点编号在排列P中的位置记录在一个树状数组tr[N]中,这样我们对于a的询问,只需要判断tr[r]-tr[l-1]是否大于0就能回答该次询问

树上启发式合并(dsu on tree):

对于每个节点,如果我们都暴力搜索它的子树的信息,很容易发现时间复杂度是O(n^{2})的。

考虑优化:对于一个节点所有儿子的搜索,我们可以发现,对于最后一个儿子为子树时搜索的信息可以保留下来,然后再加上其他儿子以及节点自身的信息。这样我们对于每个节点,就少搜索了一个儿子为子树的所有信息,这一步叫做合并,即把一个儿子的信息合并到父节点。

那么合并的儿子应该是哪个儿子呢?可以发现应该是子树最大的儿子,即重儿子,所以叫做启发式合并

想学习树上启发式合并的可以看看这里:

树上启发式合并

这是我随意找的一篇,也可以通过别的方法学习。

来看代码

#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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值