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
题目大意
有一个有向图,有边权。你可以选中一个边的集合,将这些边反转,代价是这些边的权值最大值。求使至少有一个点能够到达其它所有点的最小代价。
提示
-
对于一条边,如果能对它进行反转,那么建双向边其实不影响。
-
二分答案。
-
判断强连通分量之间的边是否使图合法。
思路
如果从 \(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;
}