Codeforces Round #845 (Div. 2) and ByteRace 2023 A-E 题解

本文网址:https://www.cnblogs.com/zsc985246/p/17142411.html ,转载请注明出处。

传送门

Codeforces Round #845 (Div. 2) and ByteRace 2023

A. Everybody Likes Good Arrays!

题目大意

给定一个数组,每次可以将相邻两个数乘起来,以新数代替旧的两个数。求使数组奇偶交替的最小操作次数。

思路

相乘不会改变奇偶性,所以找奇偶相同且相邻的数对个数即可。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;

ll n;
ll a[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	For(i,1,n){
		scanf("%lld",&a[i]);
	}
	
	For(i,1,n-1){
		if(a[i]%2==a[i+1]%2)ans++;
	}
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

B. Emordnilap

题目大意

一个排列 \(p\) 的得分为这个排列反转后接到原排列上形成的数组 \(a\) 的逆序对数。

求长度为 \(n\) 的所有排列的得分总和。

思路

单独考虑一个排列。

因为 \(a_i = a_{2n - i}\),一个数 \(a_i\) 对得分的贡献为 \(j > i , a_j < a_i\)\(j > 2n - i , a_j < a_i\) 的和。

发现 \(j > 2n - i , a_j < a_i\) 等价于 \(j < i , a_j < a_i\)

所以最后 \(a_i\) 对得分的贡献为 \(a_j < a_i\) 的总数,也即是 \(2a_i-2\)

那么总数就是 \(n \times (n-1)\)

一共有 \(n!\) 个排列,所以答案最后要乘一个 \(n!\)

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
const ll p=1e9+7;
using namespace std;

ll n;
ll jc[N];

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	
	printf("%lld\n",jc[n]*n%p*(n-1)%p);
	
}

int main(){
	jc[0]=1;
	For(i,1,100000)jc[i]=jc[i-1]*i%p;
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

C. Quiz Master

题目大意

有一个序列和一个数 \(m\),选出一些数,使得对于每个 \(i \le m\) 都能在选出的数中找到数 \(x\) 满足 \(i|x\)

求选出的数极差的最小可能值。

思路

容易想到将序列排序。

排序后发现其实就是选出一段连续的区间。

双指针枚举区间,用 \(t_i\) 维护有多少数满足 \(i|x\),用一个 \(cnt\) 记录 \(t\) 中不为 \(0\) 的个数。最后取答案最小值即可。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m;
ll a[N];
vector<ll>b[N];
ll t[N];

void mian(){
	
	ll ans=1e6;
	scanf("%lld",&n);
	scanf("%lld",&m);
	For(i,1,n){
		scanf("%lld",&a[i]);
	}
	sort(a+1,a+n+1);
	
	For(i,1,n)b[i].clear();
	For(i,1,n){
		//预处理因数,但实际上并没有必要
		for(ll j=1;j*j<=a[i];++j){
			if(a[i]%j==0){
				//只有在范围内的因数才需要统计
				if(j>=1&&j<=m)b[i].pb(j);
				if(j!=a[i]/j&&a[i]/j>=1&&a[i]/j<=m)b[i].pb(a[i]/j);
			}
		}
	}
	
	For(i,1,m)t[i]=0;
	ll cnt=0;//不为0的个数
	ll r=0;//右端点
	For(l,1,n){//枚举左端点
		while(cnt!=m){//拓展右端点
			if(r==n)break;
			r++;
			for(ll j:b[r]){
				if(t[j]==0)cnt++;
				t[j]++;
			}
		}
		if(cnt==m){//满足条件,统计答案
			ans=min(ans,a[r]-a[l]);
		}
		for(ll j:b[l]){
			t[j]--;
			if(t[j]==0)cnt--;
		}
	}
	
	if(ans==1e6)printf("-1\n");
	else printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

D. Score of a Tree

题目大意

你有一颗树,上面每个节点有一个权值 \(0/1\),每次操作会将每个节点变为其所有儿子节点的异或和,所有叶子节点权值变为 \(0\),并将当前所有节点的权值和计入答案。你会一直重复这个操作直到权值和为 \(0\)

求对于每种权值分配方案,答案的总和。

思路

设一个节点 \(i\) 代表的子树最大深度为 \(f_i\),可以发现,在 \(f_i\) 次操作后,节点 \(i\) 权值一定为 \(0\)

一个点的初始值为 \(0/1\) 的概率都为 \(\frac{1}{2}\)

那么第一次操作之后一个点如果有可能为 \(1\),那么这个节点为 \(0/1\) 的概率仍为 \(\frac{1}{2}\),因为它的儿子节点权值为 \(0/1\) 的概率为 \(\frac{1}{2}\)

根据数学归纳法可知:如果一个点有可能变为 \(1\),这个节点为 \(0/1\) 的概率为 \(\frac{1}{2}\)

一个点 \(i\)\(d_i-1\) 次操作及以前都可能为 \(1\),那么答案就会对应增加 \(f_i \times 2^n \times \frac{1}{2}=f_i \times 2^{n-1}\)

注意初始值也会有贡献,所以点 \(i\) 的贡献次数为 \(f_i-1+1\)

将所有点的 \(f_i\) 相加,再乘上 \(2^{n-1}\) 就是答案了。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
const ll p=1e9+7;
using namespace std;
ll ksm(ll a,ll b){ll bns=1;while(b){if(b&1)bns=bns*a%p;a=a*a%p;b>>=1;}return bns;}

ll n,m,k;
ll f[N];
vector<ll>e[N];

void dfs(ll x,ll fa){
	f[x]=1;
	for(ll y:e[x]){
		if(y==fa)continue;
		dfs(y,x);
		f[x]=max(f[x],f[y]+1);
	}
}

void mian(){
	
	ll ans=0;
	scanf("%lld",&n);
	For(i,1,n)e[i].clear(),f[i]=0;
	For(i,1,n-1){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		e[x].pb(y),e[y].pb(x);
	}
	
	ans=ksm(2,n-1);
	dfs(1,0);
	ll s=0;
	For(i,1,n)s=(s+f[i])%p;
	
	printf("%lld\n",ans*s%p);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

E. Edge Reverse

题目大意

有一个有向图,有边权。你可以选中一个边的集合,将这些边反转,代价是这些边的权值最大值。求使至少有一个点能够到达其它所有点的最小代价。

提示

  1. 对于一条边,如果能对它进行反转,那么建双向边其实不影响。

  2. 二分答案。

  3. 判断强连通分量之间的边是否使图合法。

思路

如果从 \(i\)\(j\) 经过 \(i \to x \to y \to j\),那么从 \(i\)\(k\) 有一条 \(i \to y \to x \to k\) 的路径不会影响答案,因为实际上可以走 \(i \to x \to k\)。所以,对于一条可以进行反转的边,建双向边其实不影响

那么就可以考虑二分答案。每次将权值小于等于当前值的边建成双向,然后判断合法性。

显然,一个强连通分量之间可以互相到达。我们可以用 tarjan 缩点,这样就只需要在 DAG 上判断是否合法了。

发现如果一个 DAG 是合法的,那么一定有且仅有一个点入度为 \(0\)。因为入度为 \(0\) 的点必定会作为起点,显然只能有一个起点。当然,如果所有点入度都不为 \(0\),那一定会被缩点。

那么我们就已经拿到了这道题的正解。tarjan 和统计度数都是 \(O(n)\),外层的二分答案为 \(O(\log\ 10^9)\),完全可过。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m,k;
ll X[N],Y[N],Z[N];//输入的边 
ll num,dfn[N],low[N];//tarjan 
ll top,s[N];//栈 
ll scc,vis[N],b[N];//scc 
vector<ll>e[N];//缩点后的图 
ll in[N];//度数 

void tarjan(ll x){//tarjan求scc 
	dfn[x]=low[x]=++num;
	s[++top]=x;
	vis[x]=1;
	for(ll y:e[x]){
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}else if(vis[y]){
			low[x]=min(low[x],dfn[y]);
		}
	}
	if(low[x]==dfn[x]){
		ll now=0;
		scc++;
		while(now!=x){
			now=s[top--];
			vis[now]=0;
			b[now]=scc;
		}
	}
}

bool check(){//判断是否满足条件 
	//初始化 
	num=top=scc=0;
	For(i,1,n)dfn[i]=low[i]=vis[i]=b[i]=in[i]=0;
	//可能不连通 
	For(i,1,n){
		if(!dfn[i])tarjan(i);
	}
	//统计度数 
	For(x,1,n){
		for(ll y:e[x]){
			if(b[x]!=b[y])in[b[y]]++;
		}
	}
	//度为0的节点个数 
	ll tot=0;
	For(i,1,scc){
		if(in[i]==0)tot++;
	}
	if(tot==1)return true;
	else return false;
}

void mian(){
	
	ll ans=-1;
	scanf("%lld",&n);
	scanf("%lld",&m);
	For(i,1,m){
		scanf("%lld%lld%lld",&X[i],&Y[i],&Z[i]);
	}
	 
	ll l=0,r=1e9;
	while(l<=r){
		ll mid=l+r>>1;
		For(i,1,n)e[i].clear();//清空 
		For(i,1,m){//重新建图 
			e[X[i]].pb(Y[i]);//正向边 
			if(Z[i]<=mid)e[Y[i]].pb(X[i]);//反向边 
		}
		if(check()){
			ans=mid;
			r=mid-1;
		}else{
			l=mid+1;
		}
	}
	
	printf("%lld\n",ans);
	
}

int main(){
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}
posted @ 2023-02-21 20:56  zsc985246  阅读(226)  评论(0编辑  收藏  举报