Educational Codeforces Round 143 (Rated for Div. 2) 题解

news2025/7/19 16:21:13

D. Triangle Coloring

大意:
给定一个有 n 个点 n 条边的无向带权图,保证 n 为 6 的倍数,组成 n/3个三元环: (1,2,3),(4,5,6),⋯。

现在给每个点染上红或蓝两种颜色,要求红色有 n/2 个点、蓝色也有 n/2 个点 。

定义一种染色方案的价值为,两端颜色不同的边的权值总和。

设所有染色方案种价值最大为 W ,问有多少种染色方案的价值为 W 。

思路:

感觉这是见过的D题里面最直白的了...

显然不同环之间互不干扰,我们分开看

每一个三元环里面想要有贡献的话,一定是取两个最大的边相加,要做到这一步,我们需要给环染成黑白比为2:1,或者反一反。那么最大值显然就是所有环的最大值之和。接下来考虑方案数,那就是看每一个环达到最大值有几种方法,乘一下就好了。

考虑到在这种策略下每一个环会让黑白的颜色数差1,又因为环数是偶数(n%6=0),所以取一半是黑色即可。

所以总的方案数=\prod(每一个环的方案数)*C(环数,环数/2);

做题的时候顺便考虑了一下环数为奇数的情况:

不难发现此时为了满足有一半是黑色,必须要有一个环全部染成黑色/白色,也就是有一个环必须抛弃。那么我们抛弃价值最小的就好了。然后该怎么做怎么做。

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=3e5+10;
const ll mod=998244353;
ll n;
struct ty
{
	ll a,b,c;
	ll val;
	ll gt()
	{
		if(a==b&&b==c) return 3;
		if(a<b) swap(a,b);if(a<c) swap(a,c);if(b<c) swap(b,c);
		if(a==c||b==c) return 2;
		return 1;
	}
}mas[N];
ll min(ll a,ll b,ll c)
{
	return min(a,min(b,c));
}
ll p[N];
ll pp[N];
ll ksm(ll x,ll y)
{
	ll ans=1;
	while(y)
	{
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return ans;
}
ll inv(ll x)
{
	return ksm(x,mod-2);
}
void init()
{
	p[0]=1;
	for(int i=1;i<=100000;++i) p[i]=p[i-1]*i%mod;
	pp[100000]=inv(p[100000]);
	for(int i=100000-1;i>=0;--i) pp[i]=pp[i+1]*(i+1)%mod;
}
void solve()
{
	init();
	cin>>n;
	n/=3;
	for(int i=1;i<=n;++i)
	{
		cin>>mas[i].a>>mas[i].b>>mas[i].c;
		mas[i].val=mas[i].a+mas[i].b+mas[i].c-min(mas[i].a,mas[i].b,mas[i].c);
	}
	ll mi=1e9,sum=0,pos=0;
	ll cnt=0;
	ll kl=0;
	for(int i=1;i<=n;++i)
	{
		if(mi>mas[i].val)
		{
			mi=min(mi,mas[i].val);
			pos=i;
		}
		else if(mi==mas[i].val&&mas[pos].gt()>mas[i].gt())
		{
			mi=mas[i].val;
			pos=i;
		}
	}
//	for(int i=1;i<=n;++i) cout<<mas[i].gt()<<" ";
//	cout<<endl;
	if(n%2) kl=pos;
	ll ans=1;
	for(int i=1;i<=n;++i)
	{
		if(i==kl) continue;
		ans=ans*mas[i].gt()%mod;
	}
	if(n%2) n--;
	ans=ans*p[n]%mod*pp[n/2]%mod*pp[n/2]%mod; 
	cout<<ans<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	//ll t;cin>>t;while(t--)
	solve();
	return 0;
}

E. Explosions?

大意:
 

有 n个怪物,第 i 个怪物的血量有 hi ,血量降为 0 时立刻死亡,你有两种攻击方式:

  1. 指定一个怪物,对其普通攻击,造成 1 点伤害,消耗 1 点魔法。普通攻击可以使用无限次。

  2. 指定一个怪物,对其释放爆炸魔法,造成的伤害取决于注入的魔法,如果想要消耗 x 点魔法注入其中,会造成 x 点伤害。

    爆炸魔法具有连锁性,如果被魔法击中的怪物死了,假设是第 i 个怪物,那么i−1,i+1 两个怪物会受到 hi−1 的伤害,以此类推,直至没有怪物死掉。这个魔法只能使用一次

问,最少需要多少魔法能消灭所有怪物。

思路:
显然的,我们会希望第二种技能能造成的伤害越高越好。剩余部分就用第一种伤害补齐就行了

不妨naive地考虑一下暴力:

对于每一个位置i,观察在该处使用第二种技能的最大伤害(这里指我们在经过一系列第一个技能之后使用该技能)。我们先考虑位置i的左边:在最理想的情况下,在位置j释放的伤害应该能够达到hi-(i-j),可以自己手推一下。那么,如果hj>=hi-(i-j),那么我们在一开始用第一技能把它削到可以被第二技能打死的地步,它就能持续对更左边造成伤害了。hj<hi-(i-j)时,j怪能死,但是向左传递的最高伤害就变了,我们记L[]表示位置向左的最高伤害,那么此时L[i]=L[j]+val(i,j),这个val代表区间总伤害,这时一个等差数列求和,我就不算了。

这是n^2的,考虑优化。一个很常见的思路就是相同变量放到一边去,我们就得到了伤害传递的判断式:

hj-j<hi-i (j<i)

此时就很显然了,对于一个i,我们找到最左边的j满足上述判断式,那么L[i]=L[i]=L[j]+val(i,j)

至于怎么找,显然维护一个单调不减的栈就可以了,具体见代码

这样我们就得到了每一个位置左边的最大伤害,右边同理。

最后每一个位置在最优操作下需要的总魔法就是sum-(L[i]-R[i]-hi)+hi,sum是总体血量,中间是一个小容斥,最后是第二个操作需要的魔法

这样我们从O(n^2)优化到了O(n)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=3e5+10;
const ll mod=998244353;
ll n;
ll mas[N];
ll h[N];
ll l[N],r[N];
ll sum=0;
void gt(ll *f)
{
	stack<ll> st;
	st.push(0);
	for(int i=1;i<=n;++i)
	{
		while(st.size()>1&&h[st.top()]>=h[i]) st.pop();
		ll len=i-st.top();
		len=min(len,mas[i]);//太长了也不行
		f[i]=f[st.top()]+len*(mas[i]+mas[i]-len+1)/2;
		st.push(i);
	}
}
void solve()
{
	sum=0;
	cin>>n;
	for(int i=1;i<=n;++i) cin>>mas[i],h[i]=mas[i]-i,sum+=mas[i];
	gt(l);
	
	reverse(mas+1,mas+1+n);
	for(int i=1;i<=n;++i) h[i]=mas[i]-i;
	gt(r);
	
	reverse(mas+1,mas+1+n);
	reverse(r+1,r+1+n);
	
	ll mi=1e18;
	for(int i=1;i<=n;++i)
	{
		mi=min(mi,sum-l[i]-r[i]+mas[i]+mas[i]);
	}
	cout<<mi<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

F. Blocking Chips

大意:
一棵树,n个点,有k个初始点是黑的,其余都是白的。按照顺序不断移动a_0...a_{k-1}点,然后再是a_0...a_{k-1}...每次移动到相邻的一个白色节点,并将其染黑。问最多能走几步。

思路:

因为每一个点都是按顺序来移动的,所如果我们知道总步数的话,其实也可以很快知道每一个点应该走的步数。

val[i]=mid/k+(i<(mid%k)),这里我们从0开始数

所以考虑二分,然后判断是否每一个点的移动都能满足条件即可。
然后呢,这里有一个看似很好想,但其实不好想的结论(好一句废话

对于每一个点,能往下走就往下走。

对于两个不同子树上的点,如果有人往上走,就有可能跟别人的路径相撞。如果大家都能往下走,那自然就是皆大欢喜,因为只会在自己的子树里活动。如果跟别人在一颗子树里了,那就只能听天由命了。

注意到一旦往下走了就不能再往上了,所以我们需要一遍dfs来统计每一个点最多往下走几步。如果步数大于需要走的步数,那么这一个点就是可行的。否则,我们就只能向上走。

这显然就是写一个dfs。dfs的时候,先递归子树统计该点能向下走的最大步数。看看最大步数是否大于需要步数,可以的话就return 1,否则就要向上走。如果能向上走的话(父亲没有需要走的步数),就是将父亲的所需步数更新为该点所需步数-1.注意,一个父亲只能让一个儿子踩一遍,第二个儿子再过来就不能走了。这里可能会有一个疑问,会不会存在更优策略/遍历顺序使得两个儿子都能过去?不会。两个儿子都要向上走,意味着它们都无法向下走,都只有一个决策。而不管是谁向上走,都有一个不能走了,此时二分的判断条件也就已经判否了(所有点都要满足条件),就没有讨论的必要了。

 时间复杂度O(nlogn)

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=2e5+20;
const ll mod=998244353;
ll n,k;
vector<ll> vt[N];
ll gt[N];//还能向下走几步 
ll vis[N];
ll val[N];
ll fl[N];
bool dfs(ll id,ll p)
{
	gt[id]=0;
	for(auto i:vt[id])
	{
		if(i==p) continue;
		if(!dfs(i,id)) return 0;//有一个不行就全部失败 
		if(!vis[i]) gt[id]=max(gt[id],gt[i]+1);
	}
	if(gt[id]>=val[id]) return 1;//能向下走就向下走
	if(p==-1||vis[p]) return 0;
	vis[p]=1;//标记走过 ,防止不同有标记的儿子抢一个父亲 
	val[p]=val[id]-1;
	return 1;
}
bool judge(ll x)
{
	for(int i=0;i<=n+10;++i) vis[i]=0,val[i]=0,gt[i]=0;
	for(int i=0;i<k;++i)
	{
		val[fl[i]]=x/k+(i<(x%k));
		vis[fl[i]]=1;
	}
//	cout<<x<<' 'x%k<<' ';
//	for(int i=1;i<=k;++i) cout<<val[fl[i]]<<' ';
//	cout<<endl;
	return dfs(0,-1);
}
void solve()
{
	cin>>n;
	for(int i=0;i<=n+10;++i)
	{
		vt[i].clear();
		gt[i]=vis[i]=val[i]=fl[i]=0;
	}
	for(int i=1;i<n;++i)
	{
		ll a,b;
		cin>>a>>b;
		a--;b--;
		vt[a].push_back(b);
		vt[b].push_back(a);
	}
	cin>>k;
	for(int i=0;i<k;++i) 
	{
		ll a;
		cin>>fl[i];
		fl[i]--;
	} 
	ll l=1,r=n;
	//cout<<"df ";
	while(l<=r)
	{
		ll mid=l+r>>1;
		//cout<<mid<<" ";
		if(judge(mid)) l=mid+1;
		else r=mid-1;
	}
	//cout<<endl;
	cout<<l-1<<endl;
}
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	ll t;cin>>t;while(t--)
	solve();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362239.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【免费教程】 高光谱遥感原理及地表主要信息提取及项目实战经验分享

高光谱分辨率遥感高光谱分辨率遥感是用很窄而连续的光谱通道对地物持续遥感成像的技术。在可见光到短波红外波段其光谱分辨率高达纳米(nm)数量级&#xff0c;通常具有波段多的特点&#xff0c;光谱通道数多达数十甚至数百个以上&#xff0c;而且各光谱通道间往往是连续的&#…

OpenGL ES上下文环境搭建

由于 OpenGL ES 一开始就是为跨平台设计的&#xff0c;所以它本身并不承担窗口管理以及上下文环境构建的职责&#xff0c;这个职责需要由各自的平台来承担。 Android 平台使用的是 EGL&#xff0c;EGL 是 Khronos 创建的一个框架&#xff0c;用来给 OpenGL 的输出与设备的屏幕…

手撸React组件库前必须清楚的9个问题

1. 组件库文档问题 以前常用的组件库文档storybook&#xff0c;包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢&#xff0c;虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很…

day37 动态规划 | 738、单调递增的数字 714、买卖股票的最佳时机含手续费 968、监控二叉树

题目 738、单调递增的数字 给定一个非负整数 N&#xff0c;找出小于或等于 N 的最大的整数&#xff0c;同时这个整数需要满足其各个位数上的数字是单调递增。 &#xff08;当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。&a…

什么是项目沟通管理? 借助系统软件管理项目沟通

在如今竞争激烈的市场环境中&#xff0c;很多企业内部往往有多个项目同时进行着&#xff0c;不同类别的项目需要项目负责人实时跟进&#xff0c;而成功的项目是离不开项目人员之间的日常互相沟通进行。只有良好的沟通&#xff0c;项目经理才能够获取到足够的信息&#xff0c;可…

国产真无线蓝牙耳机哪个好?国产半入耳蓝牙耳机推荐

近几年&#xff0c;生活中随处可见的有戴蓝牙耳机的人&#xff0c;而蓝牙耳机也因为使用更便捷、功能更先进受到了不少用户的喜爱。蓝牙耳机按照佩戴方式来划分&#xff0c;可以有入耳式、半入耳式、头戴式等。在此&#xff0c;我来给大家推荐几款国产半入耳蓝牙耳机&#xff0…

LeetCode 606.根据二叉树创建字符串,102.二叉树的层序遍历和牛客 二叉搜索树与双向链表

文章目录1. 根据二叉树创建字符串2. 二叉树的层序遍历3. 二叉搜索树与双向链表1. 根据二叉树创建字符串 难度 简单 题目链接 解题思路&#xff1a; 这里的意思就是&#xff1a;用前序遍历遍历这颗树。然后左子树和右子树分别在一个括号里。括号里的规则是&#xff1a; 1.左右都…

W800|WIFI|CDK|W80X SDK v1.00.10|官方demo|学习(2):t-connect

W800 SDK代码及相关文档获取地址&#xff1a; https://www.winnermicro.com/html/1/156/158/558.html 1、W800 SDK v1.00.10更新内容&#xff1a; 1. 驱动更新 1&#xff09;提供模组ADC校准功能接口 2&#xff09;修复PSRAM IO复用不完整问题 3&#xff09;Flash驱动修改不再…

CSDN每日一练非降序数组 C语言/C++

题目名称&#xff1a;非降序数组 时间限制&#xff1a;1000ms内存限制&#xff1a;256M 题目描述 写一个函数&#xff0c;传入两个非降序的整数数组&#xff08;A, B&#xff09;&#xff0c;将 A, B 合并成一个非降序数组 C&#xff0c;返回 C&#xff08;不要使用内置 sort 函…

新项目分析

1&#xff1a;数据类型处理 # sep‘\s‘ 这是正则表达式&#xff0c;通过一定规则的表达式来匹配字符串用的 \s 表示空白字符&#xff0c;包括但不限于空格、回车(\r)、换行(\n)、tab或者叫水平制表符(\t)等&#xff0c;这个根据编码格式不同代表的含义也不一样&#xff0c;感…

一文讲解系统调用与函数调用有什么区别?

作为程序员你肯定写过无数的函数&#xff0c;假设有这样两个函数&#xff1a; void funcB() { }void funcA() {funcB(); } 函数之间是可以相互调用的&#xff0c;这很简单很happy有没有。 要知道是代码、是函数就可以相互调用&#xff0c;不管你用什么语言写的。 假设funcB…

2023/02/21 事件循环-eventloop 宏任务 微任务 讲解

1 JS是单线程 js是单线程的。也就是说&#xff0c;同一个时间只能做一件事。作为浏览器脚本语言&#xff0c;与它的用途有关。JavaScript的主要用途是和用户互动&#xff0c;以及操作DOM&#xff0c;这决定了它只能是单线程。 js是单线程的。也就是说&#xff0c;同一个时间只…

如何使用 API 工具做 Websocket 测试

在 API 测试中&#xff0c;对 Websocket 协议的支持呼声越来越高&#xff0c;今天给大家推荐一款 开源的 API 管理工具——Postcat&#xff0c;以及教教大家&#xff0c;如何利用 API 管理工具做 Websocket 测试。 在线 Demo 链接&#xff1a;Postcat - Open Source API Ecosys…

17 个短代码,检验你 Python 基本功

Python 是一门非常优美的语言&#xff0c;其简洁易用令人不得不感概人生苦短。在本文中&#xff0c;蛋糕将带大家回顾 17个非常有用的 Python 技巧&#xff0c;例如查找、分割和合并列表等。这 17 个技巧都非常简单&#xff0c;但它们都很常用且能激发不一样的思路。 人生苦短&…

来一波骚操作,Java内存模型

文章整理自 博学谷狂野架构师 什么是JMM 并发编程领域的关键问题 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息。在编程中&#xff0c;线程之间的通信机制有两种&#xff0c;共享内存和消息传递。 ​ 在共享内存的并发模型里&#xff0c;线程之间共享程序的公共…

项目管理从需求管理开始--不懂需求管理还敢带项目?

分析报告指出&#xff0c;多达76%的项目失败是因为差劲的需求管理&#xff0c;这个是项目失败的最主要原因&#xff0c;比技术、进度失控或者混乱的变更管理还要关键。很多PMO和PM却没有把需求管理重视起来&#xff0c;甚至认为这只是产品经理的事情&#xff0c;自己只做交付即…

Spark RDD及内存计算

文章目录Spark RDD及内存计算性能调优RDD 的核心特征和属性内存计算Spark RDD及内存计算 性能调优 性能调优的本质&#xff1a; 性能调优不是一锤子买卖&#xff0c;补齐一个短板&#xff0c;其他板子可能会成为新的短板。因此&#xff0c;它是 一个动态、持续不断的过程&…

第51篇-某彩网登录参数分析-webpack【2023-02-21】

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析一、前言 今天我们看一个webpack的网站 aHR0cHM6Ly8xMGNhaTUwMC5jYy9sb2dpbg==二、网站分析 首先…

Springboot 全局异常处理类

全局异常处理 在开发过程中&#xff0c;不管是Dao、Servie、Controller,层都有可能发生异常&#xff0c;对于异常处理&#xff0c;通常是try&#xff0d;catch或者直接throw&#xff0c;这会让try&#xff0d;catch的代码在代码中任意出现&#xff0c;系统的代码耦合度高&…

Elasticsearch7.8.0版本进阶——数据更新流程

目录一、数据更新流程概述二、数据更新流程步骤2.1、数据更新流程图解2.2、部分更新一个文档的步骤2.3、数据更新流程注意事项一、数据更新流程概述 部分更新一个文档需要结合数据读取和写入流程。 二、数据更新流程步骤 2.1、数据更新流程图解 2.2、部分更新一个文档的步骤…