【题解】Educational Codeforces Round 152(CF1849)
A.Morning Sandwich
题目描述:
吃货小 C 喜欢三明治,他有三种材料:面包、芝士、火腿。正确的三明治组合应为一层面包和一层芝士或火腿之一轮流放置,以面包为结尾,例如面包-芝士-面包-火腿-面包就是合法的三明治。
给定每次做三明治的材料: 面包, 芝士, 汉堡,求出小 C 最多做多少层三明治。
多测, 组数据。
。
题目分析:
可以将三明治的结构理解为:一层芝士/汉堡对应一层面包,在最后还要多来一个面包。
所以只需要 与 取 后加 即可。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int T;scanf("%d",&T);
while(T--){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
if(a < 2) printf("0\n");
else{
printf("%d\n",2 * min(a-1,b+c) + 1);
}
}
return 0;
}
B.Monsters
题目描述:
小 C 在玩一款全新开放世界冒险游戏。
本关卡有 个怪物,小 C 一刀 血,第 怪物初始 血。
怪物 0 血或负血会死掉。
小 C 每次打击目前剩余血量最多的怪物,如有多个打击编号最小的。
按照小 C 击杀怪物的顺序输出怪物编号。
多测, 组数据。
。
题目分析:
考虑这个过程是什么样的。
一样是先通过减 将每一个 变为 的数,然后一击必杀。
所以就直接对 取模然后按剩下的血量排序即可,注意的细节就是若 ,则相当于减到 而不是 。
代码:
点击查看代码
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N = 1e6+5;
int a[N];
PII b[N];
bool cmp(PII a,PII b){
if(a.first != b.first) return a.first > b.first;
return a.second < b.second;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;scanf("%d",&T);
while(T--){
int n,k;scanf("%d%d",&n,&k);
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
for(int i=1; i<=n; i++){
if(a[i] % k == 0) a[i] = k;
else a[i] %= k;
}
for(int i=1; i<=n; i++) b[i] = {a[i],i};
sort(b+1,b+n+1,cmp);
for(int i=1; i<=n; i++) printf("%d ",b[i].second);
printf("\n");
}
return 0;
}
C.Binary String Copying
题目描述:
小 C 获得了一个长度为 的 01 串 。
小 C 有 次操作,每次操作形如 ,代表将 复制,生成一个 的副本,将副本 区间内数字字符从小到大排序,第 次操作生成的新串记为 。操作间互不影响,互相独立。
求出有多少不同的 。
多测, 组数据。
。
题目分析:
这是一个 串,所以对于 从小到大排序之后其前缀 和后缀 的位置都不会改变,设 代表从前到后第一个 的位置 代表从后到前第一个 的位置,排序影响的只有 。
也就是说我们可以将区间 用区间 替代,会发现的是当 相同则字符串相同,当 不同则字符串不同。
所以对于 的种类计数即可。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int nxt[N],pre[N];
map<int,int> mp[N];
char s[N];
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;scanf("%d",&T);
while(T--){
int n,m;scanf("%d%d",&n,&m);
scanf("%s",s+1);
nxt[n+1] = n+1;
for(int i=n; i>=1; i--){
if(s[i] == '1') nxt[i] = i;
else nxt[i] = nxt[i+1];
}
pre[0] = 0;
for(int i=1; i<=n; i++){
if(s[i] == '0') pre[i] = i;
else pre[i] = pre[i-1];
}
int ans = 0;
while(m--){
int l,r;scanf("%d%d",&l,&r);
l = nxt[l],r = pre[r];
if(l >= r) ans += !mp[0][0],mp[0][0] = true;
else ans += !mp[l][r],mp[l][r] = true;
}
printf("%d\n",ans);
for(int i=0; i<=n; i++) mp[i].clear();
}
return 0;
}
D.Array Painting
题目描述:
有一个长度为 的数组 ,满足 ,一开始所有元素均为蓝色。
可以有如下操作:
-
用一枚硬币,把一个蓝色元素涂成红色;
-
选择一个不等于 的红色元素和一个与其相邻的蓝色元素,将所选的红色元素减少 ,并将所选的蓝色元素涂成红色。
要将所有元素涂红,最少需要多少硬币?
题目分析:
考虑一次操作最优就是让极长的 全部被染红。
若区间最大值为 则可以选择 或 额外染红。
若区间最大值为 则可以选择 和 额外染红。
所以就可以考虑将所有的极长段拿出来,先考虑最大值为 的区间再考虑最大值为 的区间染色即可。
如果我们是从左到右依次考虑,那么对于最大值为 的区间优先将 染色最优,因为可以给后面更大的发挥空间。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+5;
struct node{
int l,r,opt;
}b[N];
int a[N];
bool flag[N];
bool cmp(node a,node b){
if(a.opt != b.opt) return a.opt > b.opt;
return a.l < b.l;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
int tot = 0;
for(int i=1; i<=n;){
if(a[i] != 0){
int pos = i;
int mx = 0;
for(;pos <= n && a[pos] != 0; ++pos);
--pos;
for(int j=i; j<=pos; j++) mx = max(mx,a[j]);
b[++tot] = {i,pos,mx};
i = pos + 1;
}
else i++;
}
sort(b+1,b+tot+1,cmp);
int ans = 0;
for(int p=1; p<=tot; p++){
++ans;
int l = b[p].l,r = b[p].r;
for(int i=l;i <= r; i++) flag[i] = true;
if(l != 1 && !flag[l-1] && b[p].opt) b[p].opt--,flag[l-1] = true;
if(r != n && !flag[r+1] && b[p].opt) b[p].opt--,flag[r+1] = true;
}
for(int i=1; i<=n; i++){
if(!flag[i]) ++ans;
}
printf("%d\n",ans);
return 0;
}
E.Max to the Right of Min
题目描述:
现有一长度为 的排列,求有多少子区间 满足区间最大值出现在区间最小值的右侧。
题目分析:
做法一:
一个显然的想法就是枚举点东西,第一感觉就是枚举最大值和最小值,然后看贡献是个怎么样的式子,这个式子肯定可以快速对一个区间统计。
设 分别表示左边/右边第一个比 大的数, 表示左边/右边第一个比 小的数。
令 为最大值, 为最小值,其满足条件的区间的左右端点就是:。
造成的贡献显然就是:
这个东西可以对于 的取值分类讨论,这样就可以转化为三维偏序就可以直接做了。
需要注意如果区间长度 则不应统计,而不是作为负数统计。
做法二:
我们不直接枚举最大值和最小值,而是转化为枚举最大值和该区间的左端点/右端点。
设最大值位置为 ,则可能的左端点 ,可能的右端点 。
那么我们到底是枚举左端点还是右端点呢,一个结论就是选择可能的区间长度最小的区间枚举即可。
这样的话考虑一下复杂度是什么,也就是一个点会被枚举到多少次。
若对应的区间为 且我们选择了 ,则对于 在下一次被枚举到时其区间长度一定不超过 ,因为必然存在 且 ,也就是区间长度会减半,所以一个点最多被枚举 次,时间复杂度 。
需要特判枚举的端点为 的情况。
做法三:
我们不搞那么多花里胡哨的,因为要我们统计多少个区间 满足条件就直接考虑枚举 然后判断有多少个 符合条件。
直接维护很难所以考虑是不是可以增量地维护,也就是当 变为 时有什么变化,分类讨论:
- 若 为最大值,则 都是合法的
- 若 为最小值,则 都是不合法的
- 若 不为最大值和最小值,则不会对原来的合法性产生任何影响。
所以就直接使用线段树维护区间赋值 就可以了。
这里会有的疑问就是 与 相交的部分怎么办呢。
其实它们不可能相交,考虑一定存在 或 ,也就是必然存在 或 ,也就是肯定有一个区间为空,就不可能相交。
代码:
(做法三的代码)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
int sum[4 * N],tag[4 * N],a[N],lmn[N],lmx[N];
void update(int now,int now_l,int now_r,int val){
sum[now] = val * (now_r - now_l + 1);
tag[now] = val;
}
void pushup(int now){
sum[now] = sum[now<<1] + sum[now<<1|1];
}
void pushdown(int now,int now_l,int now_r){
if(tag[now] == -1) return;
int mid = (now_l + now_r)>>1;
update(now<<1,now_l,mid,tag[now]);
update(now<<1|1,mid+1,now_r,tag[now]);
tag[now] = -1;
}
void modify(int now,int now_l,int now_r,int l,int r,int val){
if(l > r) return;
if(l <= now_l && now_r <= r){
update(now,now_l,now_r,val);
return;
}
pushdown(now,now_l,now_r);
int mid = (now_l + now_r)>>1;
if(l <= mid) modify(now<<1,now_l,mid,l,r,val);
if(r > mid) modify(now<<1|1,mid+1,now_r,l,r,val);
pushup(now);
}
void build(int now,int now_l,int now_r){
tag[now] = -1;
if(now_l == now_r) return;
int mid = (now_l + now_r)>>1;
build(now<<1,now_l,mid);build(now<<1|1,mid+1,now_r);
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;scanf("%lld",&n);
for(int i=1; i<=n; i++) scanf("%lld",&a[i]);
stack<pair<int,int> > st;
for(int i=1; i<=n; i++){
lmx[i] = 0;
while(!st.empty() && st.top().first < a[i]) st.pop();
if(!st.empty()) lmx[i] = st.top().second;
st.push({a[i],i});
}
while(!st.empty()) st.pop();
for(int i=1; i<=n; i++){
lmn[i] = 0;
while(!st.empty() && st.top().first > a[i]) st.pop();
if(!st.empty()) lmn[i] = st.top().second;
st.push({a[i],i});
}
int ans = 0;
build(1,1,n);
for(int i=1; i<=n; i++){
modify(1,1,n,lmx[i]+1,i-1,1);
modify(1,1,n,lmn[i]+1,i-1,0);
ans += sum[1];
}
printf("%lld\n",ans);
return 0;
}
F.XOR Partition
题目描述:
小 C 认为,一个数集 的价值是这个数集中所有不同的数两两异或的最小值。数集大小不足 则价值记为 。
小 C 又认为,一个数集 的一种分割方案的分割价值是这个数集分割成两个数集 和 , 和 的价值的最小值。
求出使得分割价值取到最大的分割方案,如果多种输出任意一种。
。
题目分析:
一个想法就是每次找到 最小的数对,将这两个数分到不同的集合中,这样最后分出来就是最优解。
所以其实可以将最小异或的数对看成一条边,这样只要图为二分图就可以直接二分图染色。
那么只要图刚刚联通的时候,也就是为一棵树的时候就可以。
那么这个问题就是可以转化为一个类似瓶颈生成树的问题,也就是任意一条非树边权值均大于树边权值,其中 的权值定义为 ,而所以直接求最小生成树就一定对应这样的一棵瓶颈生成树。
那么对于最小异或生成树该怎么求呢。
异或尽可能小相当于在 trie 树上其 lca 的深度尽可能大,而最可能大的就是一条链最底部的也就是同时拥有两个儿子的点。
而我们每插入一个点只会最多新增一个同时拥有两个儿子的点,所以符合条件的点数为 。
就可以考虑枚举每一个符合条件的点,令子树大小最小的子树为左子树,然后枚举其左子树内的点,然后让它去和右子树匹配使得异或尽可能小。
这里可能就有问题就是如果点数达不到 也就是最后不连通怎么办,这种情况的出现就是因为出现了异或为 的情况,不会对答案产生任何影响。
分析一下复杂度,因为下一次枚举到某个点时,其对应子树的大小必然翻倍,而对于每一次枚举匹配的复杂度为 ,所以总时间复杂度为
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3e6+5;
const int INF = 1e9+5;
struct edge{
int nxt,to;
edge(){}
edge(int _nxt,int _to){
nxt = _nxt,to = _to;
}
}e[2 * N];
struct node{
int id,val;
}a[N];
int cnt,sz=1,id,head[N],col[N],ch[N][2],l[N],r[N];
bool vis[N];
bool cmp(node a,node b){
return a.val < b.val;
}
void add_edge(int from,int to){
e[++cnt] = edge(head[from],to);
head[from] = cnt;
}
void insert(int now,int pos,int dep){
if(dep == -1) return;
int s = (a[pos].val >> dep) & 1;
if(ch[now][s]) r[ch[now][s]] = pos;
else{
ch[now][s] = ++sz;
l[sz] = r[sz] = pos;
}
insert(ch[now][s],pos,dep-1);
}
int query(int now,int val,int dep){
if(dep == -1){
id = l[now];
return 0;
}
int s = (val >> dep) & 1;
if(ch[now][s]) return query(ch[now][s],val,dep-1);
return query(ch[now][s^1],val,dep-1) + (1 << dep);
}
void solve(int now,int dep){
if(dep == -1 || !now) return;
int x = ch[now][0],y = ch[now][1];
if(x && y){
if(r[x] - l[x] + 1 > r[y] - l[y] + 1) swap(x,y);
int ans = INF,u = 0,v = 0;
for(int i=l[x]; i<=r[x]; i++){
int tmp = query(y,a[i].val,dep-1);
if(ans > tmp) ans = tmp,u = i,v = id;
}
add_edge(a[u].id,a[v].id);add_edge(a[v].id,a[u].id);
// printf("%d %d\n",a[u].id,a[v].id);
}
solve(ch[now][0],dep-1);
solve(ch[now][1],dep-1);
}
void dfs(int now,int val){
col[now] = val;
for(int i=head[now]; i; i=e[i].nxt){
int to = e[i].to;
if(col[to] != -1) continue;
dfs(to,val^1);
}
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i].val),a[i].id = i;
sort(a+1,a+n+1,cmp);
for(int i=1; i<=n; i++) insert(1,i,30);
for(int i=1; i<=n; i++) col[i] = -1;
solve(1,30);dfs(1,0);
for(int i=1; i<=n; i++) printf("%d",col[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律