1099Div2-构造+模拟+贪心

2794 字
14 分钟
1099Div2-构造+模拟+贪心

1099Div2#

Dashboard - Codeforces Round 1099 (Div. 2) - Codeforces

Easy:A

Mid:B

Hard-Mid:C

Hard:D(Gemini3.5Flash)

A-构造

题意:

给定一个整数n,构造出一个整数数组a1,…an,该数组具有如下性质:

数组中所有元素及相邻元素之和两两不同

题解:

一种较为愚蠢的做法是用一个set记录插入数组中的元素:

set()
for _ in range(int(input())):
n = int(input())
ans = [0]*n
s = set()
j = 0
i = 1
while j < n:
if i not in s:
ans[j] = i
j += 1
i += 1
s.add(i)
else:
i += 1
continue
print(*ans)

实际上,只需要数组中所有元素均为奇数即可

全奇数
for tt in range(int(input())):
n = int(input())
ans = [2 * i + 1 for i in range(n)]
print(*ans)

还有一种可行的构造是,该数组中的所有元素均不能被3整除,在这个数组中,相邻元素的和是可以被3整除的,模3的余数形成1,2,1,2的规律

B-贪心

题意:

给定一个数组,可以最多对该数组执行如下的一次操作:

选择一个整数kk,以及一个子序列bb,将kk加到该子序列的每个元素上

判断是否能够是数组按非递减顺序排序

题解:

当数组中相邻元素出现递减a[i] < a[i-1]时,如果a[i-1] < a[i-2],那么显然,无法通过给定的操作实现非递减;反之,用一个maxadd = max(maxadd,a[i-1] - a[i]),记录最大的kk

正向遍历数组,为每个a[i] < a[i-1]a[i] += maxadd

正向遍历数组,如果该数组中仍然出现递减,则无法通过指定操作实现目的。

贪心-py
# 对于下降的地方,记录需要添加的最小值,最大化这个最小值
# 为每个下降的地方加上这个最大化的最小值
# 对于连续下降的直接NO
t = int(input())
for _ in range(t):
n = int(input())
a = list(map(int, input().split()))
ok = 1
maxadd = 0
for i in range(1,n):
if a[i] < a[i-1]:
# 如果连续下降,则一个无法完成
if i - 1 > 0 and a[i-1] < a[i-2]:
ok = 0
maxadd = max(maxadd,a[i-1] - a[i])
if not ok:
print("NO")
else:
for i in range(1,n):
if a[i] < a[i-1]:
a[i] += maxadd
for i in range(1,n):
if a[i] < a[i-1]:
ok = 0
if ok:
print("YES")
else:
print("NO")
贪心-c
#include<stdio.h>
#include<stdlib.h>
void solve(){
int n;
if(scanf("%d",&n) != 1){
return;
}
long long *a = (long long *)malloc(n*sizeof(long long));
for(int i = 0;i < n;i++){
scanf("%lld",&a[i]);
}
int ok = 1;
long long maxadd = 0;
for(int i = 1;i < n;i++){
if(a[i] < a[i-1]){
if(i-1 > 0 && a[i-1] < a[i-2]){
ok = 0;
}
long long diff = a[i-1] - a[i];
if(diff > maxadd){
maxadd = diff;
}
}
}
if (!ok){
printf("NO\n");
}
else{
for(int i = 1;i<n;i++){
if(a[i] < a[i-1]){
a[i] += maxadd;
}
}
for(int i=1;i<n;i++){
if(a[i] < a[i-1]){
ok = 0;
}
}
if(ok){
printf("YES\n");
}
else{
printf("NO\n");
}
}
free(a);
}
int main(){
int t;
if(scanf("%d",&t) != 1){
return 0;
}
while(t--){
solve();
}
return 0;
}
C-模拟+思维

题意:

给定一个序列,选择一个元素进行如下操作:

1.如果所选的元素是偶数,则将其除以2

2.如果所选的元素是奇数,则将其加1

求出最小的操作次数,使得给定的序列中所有数都相等

题解:

注意到,对一个数进行上次多次操作后,该数会达到1,随后在进行操作,数在[1,2,1,2…]变换,

因此只需要保留该数在达到1之前的前缀部分即可

用一个字典reach记录数组中所有数字经过操作后得到的数字,包括两个字段:某个演变出的数字num[went, steps],分别代表有多少个原始数组中的元素经过操作可以到达num;所有可到达该数的总操作次数

最后比较所有went == n的情况下,最小的steps

字典
for _ in range(int(input())):
n = int(input())
a = list(map(int,input().split()))
reach = {}
for num in a:
went_to = set()
step = 0
while num not in went_to:
went_to.add(num)
if num not in reach:
reach[num] = [1,step]
else:
reach[num][0] += 1
reach[num][1] += step
if num%2 == 0:
num //= 2
else:
num += 1
step += 1
ans = float('inf')
for went,step in reach.values():
if went == n and step < ans:
ans = step
print(ans)
defaultdict
from collections import defaultdict
for _ in range(int(input())):
n = int(input())
a = list(map(int,input().split()))
reach = defaultdict(lambda: [0, 0])
for x in a:
step = 0
s = set()
while x not in s:
s.add(x)
reach[x][0] += 1
reach[x][1] += step
if x % 2 == 0:
x//=2
else:
x+=1
step += 1
ans = float('inf')
for cnt,tot in reach.values():
if cnt == n and tot < ans:
ans = tot
print(ans)
c-手写哈希
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INF 1000000000000000000LL
typedef struct {
long long key;
long long went;
long long step_sum;
int is_used;
} ReachNode;
#define REACH_SIZE 5000005
ReachNode reach[REACH_SIZE];
int used_slots[REACH_SIZE];
int used_slots_cnt = 0;
void reset_reach_fast() {
for (int i = 0; i < used_slots_cnt; i++) {
reach[used_slots[i]].is_used = 0;
reach[used_slots[i]].key = 0;
reach[used_slots[i]].went = 0;
reach[used_slots[i]].step_sum = 0;
}
used_slots_cnt = 0;
}
unsigned int hash_p68(unsigned long long key) {
key ^= key >> 33;
key *= 0xff51afd7ed558ccdULL;
key ^= key >> 33;
return key % REACH_SIZE;
}
void add_to_reach(long long num, long long step) {
unsigned int hash = hash_p68((unsigned long long)num);
while (reach[hash].is_used && reach[hash].key != num) {
hash = (hash + 1) % REACH_SIZE;
}
if (!reach[hash].is_used) {
reach[hash].key = num;
reach[hash].went = 1;
reach[hash].step_sum = step;
reach[hash].is_used = 1;
used_slots[used_slots_cnt++] = hash;
} else {
reach[hash].went += 1;
reach[hash].step_sum += step;
}
}
#define SET_SIZE 205
long long went_to[SET_SIZE];
int set_used[SET_SIZE];
void reset_set() {
memset(set_used, 0, sizeof(set_used));
}
int check_and_add_set(long long num) {
unsigned long long hash = (unsigned long long)num % SET_SIZE;
while (set_used[hash] && went_to[hash] != num) {
hash = (hash + 1) % SET_SIZE;
}
if (set_used[hash]) {
return 1;
}
went_to[hash] = num;
set_used[hash] = 1;
return 0;
}
void solve(){
int n;
if (scanf("%d", &n) != 1) return;
long long *a = (long long *)malloc(n * sizeof(long long));
for (int i = 0; i < n; i++) {
scanf("%lld", &a[i]);
}
reset_reach_fast();
for (int i = 0; i < n; i++) {
long long num = a[i];
reset_set();
long long step = 0;
while (check_and_add_set(num) == 0) {
add_to_reach(num, step);
if (num % 2 == 0) {
num /= 2;
} else {
num += 1;
}
step += 1;
}
}
long long ans = INF;
for (int i = 0; i < used_slots_cnt; i++) {
int idx = used_slots[i];
if (reach[idx].went == n && reach[idx].step_sum < ans) {
ans = reach[idx].step_sum;
}
}
if (ans == INF) {
printf("-1\n");
} else {
printf("%lld\n", ans);
}
free(a);
}
int main(){
int t;
if (scanf("%d", &t) != 1) return 0;
while (t--) {
solve();
}
return 0;
}
D-构造+贪心+模拟(Gemini3.5Flash)

题意:

给定一个长度为nn的原数组aa,我们定义:

  1. 前缀和数组bbbi=j=1iajb_i = \sum_{j=1}^{i} a_j
  2. 前缀最大值数组ccci=max(b1,b2,,bi)c_i = \max(b_1, b_2, \dots, b_i)

现在,原数组aa的部分元素丢失了。输入会给你一个二进制字符串ss和不完整的数组aa

  • 如果si=1s_i = 1,说明aia_i没丢,给你的是它的真实值
  • 如果si=0s_i = 0,说明aia_i丢失了(此时输入给出的ai=0a_i = 0作为占位符),你可以将其填入任意整数

另外,完整的前缀最大值数组cc 是保存完好的。

你的任务: 判断能否通过合理地填补si=0s_i=0的那些aia_i,来恢复出一个满足前缀最大值刚好为cc的原数组aa。如果能,输出Yes并打印出完整的aa数组;否则输出No

题解:

代码的核心切入点在于利用ci=max(ci1,bi)c_i = \max(c_{i-1}, b_i) 的性质。

1. 核心观察与关键推导

前缀最大值数组cc具有天然的单调递增性。当cic_i发生变化时,即ci>ci1c_i > c_{i-1},这意味着bib_i 刷新了历史最高纪录。因此:

当 cici1 时,必然有 bi=ci\text{当 } c_i \neq c_{i-1} \text{ 时,必然有 } b_i = c_i

一旦bib_i的确切值被固定了,我们可以往前逆推。如果si=1s_i = '1'si1=1s_{i-1} = '1',那么根据前缀和关系,有bi1=biaib_{i-1} = b_i - a_i。以此类推,只要前面连续的ss都是'1',那么这一段的bb 链条全部可以被唯一确定。

2. 代码变量的职责

  • b[i]:代表推导出的前缀和bib_i 的确切值。
  • d[i]:状态标记布尔数组。d[i] = 1表示bib_i的值已经被唯一锁定了;d[i] = 0表示bib_i的值目前还是未知的(可以由si=0s_i=0 的空位来调整)。

3. 双阶段逻辑拆解

  • 第一阶段:贪心逆推锁定(从前向后遇到突变,向后逆推)

    遍历整个数组,一旦发现cici1c_i \neq c_{i-1}

    1. 如果ci<ci1c_i < c_{i-1},不符合前缀最大值的单调性,直接判定为No
    2. 否则,令bi=ci,d[i]=1b_i = c_i, d[i] = 1。然后从ii开始往左回溯:只要s[j]==1s[j] == '1',说明这一步的变换贡献aja_j是已知的,因此bj1b_{j-1}也随之被唯一锁定为bjajb_j - a_j
    3. 冲突检查:如果在左推过程中发现bj1b_{j-1}早已被另一个链条锁定过(即d[j-1] == 1),且新算出的值不等于旧值,说明产生逻辑悖论,直接判定为No
  • 第二阶段:正向顺推填补与合法性检验

    1. 11nn正向遍历。如果s[i]==1s[i] == '1'且前一步的bi1b_{i-1}是已知的,那么当前位置也可以顺推出来:bi=bi1+aib_i = b_{i-1} + a_i
    2. 如果到某一步d[i]==0d[i] == 0(即bib_i还没有被强制规定),说明这是一个“自由位”,由于它不影响cic_i刷新,为了最大安全化,我们把它设为一个极小值(代码里用了1016-10^{16}),使其不干扰后续的最大值。
    3. 终极校验:根据补全后的bb数组还原出最终的ai=bibi1a_i = b_i - b_{i-1}。并检查每一个位置是否满足ci=max(ci1,bi)c_i = \max(c_{i-1}, b_i)。如果完全吻合,则构造成功输出Yes;否则输出No
py
for _ in range(int(input())):
n = int(input())
s = ' ' + input().strip()
a = [0] + list(map(int,input().split()))
c = [0] + list(map(int,input().split()))
b = [0] * (n + 1)
d = [0] * (n + 1)
d[0] = 1
b[0] = 0
c[0] = -10 ** 18
ok = True
# 第一阶段:逆推锁定
for i in range(1, n + 1):
if c[i] != c[i - 1]:
if c[i] < c[i - 1]:
ok = False
break
b[i] = c[i]
d[i] = 1
j = i
while j >= 0:
if s[j] == '1':
if d[j - 1] and b[j - 1] + a[j] != b[j]:
ok = False
break
d[j - 1] = 1
b[j - 1] = b[j] - a[j]
j -= 1
else:
break
if not ok:
break
if not ok:
print("No")
continue
# 第二阶段:正向顺推与最终检验
for i in range(1, n + 1):
if s[i] == '1':
b[i] = b[i - 1] + a[i]
d[i] = 1
if not d[i]:
b[i] = -10 ** 16
a[i] = b[i] - b[i - 1]
if max(c[i - 1], b[i]) != c[i]:
ok = False
break
if not ok:
print("No")
else:
print("Yes")
print(' '.join(map(str, a[1:n + 1])))
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 500005
long long a[N], c[N], b[N], d[N];
char s[N];
long long max_ll(long long x, long long y) {
return x > y ? x : y;
}
void solve() {
int n;
if (scanf("%d", &n) != 1) return;
// 读入字符串并存放在 s[1] 开始的位置
scanf("%s", s + 1);
d[0] = 1;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
d[i] = 0;
}
for (int i = 1; i <= n; i++) {
scanf("%lld", &c[i]);
}
c[0] = -1000000000000000000LL; // -1e18
b[0] = 0;
// 第一阶段:逆推锁定
for (int i = 1; i <= n; i++) {
if (c[i] != c[i - 1]) {
if (c[i] < c[i - 1]) {
printf("No\n");
return;
}
b[i] = c[i];
d[i] = 1;
for (int j = i; j >= 0; j--) {
if (s[j] == '1') {
if (d[j - 1] && b[j - 1] + a[j] != b[j]) {
printf("No\n");
return;
}
d[j - 1] = 1;
b[j - 1] = b[j] - a[j];
} else {
break;
}
}
}
}
// 第二阶段:正向顺推与最终检验
for (int i = 1; i <= n; i++) {
if (s[i] == '1') {
b[i] = b[i - 1] + a[i];
d[i] = 1;
}
if (!d[i]) {
b[i] = -10000000000000000LL; // -1e16
}
a[i] = b[i] - b[i - 1];
if (max_ll(c[i - 1], b[i]) != c[i]) {
printf("No\n");
return;
}
}
printf("Yes\n");
for (int i = 1; i <= n; i++) {
printf("%lld%c", a[i], (i == n) ? '\n' : ' ');
}
}
int main() {
// 优化标准 C 的输入输出性能
setvbuf(stdin, NULL, _IOFBF, 16384);
setvbuf(stdout, NULL, _IOFBF, 16384);
int t;
if (scanf("%d", &t) == 1) {
while (t--) {
solve();
}
}
return 0;
}
1099Div2-构造+模拟+贪心
http://blog.7a7a68.xyz/posts/1099div2/
作者
Waning
发布于
2026-05-24
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Waning
愿你明日如绚丽之花.
公告
还活着.
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
56
分类
8
标签
20
总字数
125,693
运行时长
0
最后活动
0 天前

目录