Codeforces Round #546 (Div. 2) 题解

博客包含几道算法题,如给定n个区间和位置k求未完全遍历区间,有n个井盖求捡完全部金币最小步数等。针对不同题目给出思路,如数据范围小直接暴力,考虑转置特性判断矩阵能否转换等,部分题还给出具体解法和复杂度分析。

A. Nastya Is Reading a Book
题意: n个区间,每个区间范围不超过100,n不超过100。给一个位置k,1-(k-1)是遍历过的位置,求没有完全遍历完的区间。
k处于区间中时,表示区间没有遍历完。
思路:数据范围小,直接暴力

#include <bits/stdc++.h>
using namespace std;
pair <int, int> p[102];
int main(){
    int n, k, ans = 0;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d%d", &p[i].first, &p[i].second);
    scanf("%d", &k);
    for(int i = 1; i <= n; i++){
        if(p[i].second < k){
            ans++;
        }
    }
    printf("%d\n", n-ans);
}

B. Nastya Is Playing Computer Games
题意:有n个井盖,每个井盖上有一个小石头。给出n和k,k表示刚开始在第k个井盖上方。有三种操作,左右移动,扔石头到任意一个井盖,下到井盖里拿金币。只有井盖上没有石头才能下井盖。求捡完全部金币的最小步数。
思路:规律题,我们考虑一下当前位置和相邻的任意一个位置,要想拿到这2个井盖的金币一共需要6次。其余的井盖需要(n-2)*3个步骤,还需要考虑这个人是从哪个方向走导致多出来的步数,两个取小即可。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    int ans = n * 3 + min(k - 1, n - k);
    printf("%d\n", ans);
    return 0;
}

C:题意,给了俩个矩阵,你可以无数次对A矩阵的子矩阵进行转置操作,问A是否可以变为B?
思路:考虑一下转置的特性,发现不在同一对角线的元素是无法交换位置的。我们直接对主对角线进行求和判断即可。

#include <bits/stdc++.h>
using namespace std;
int a[505][505], b[505][505];
map <int, int> mp[1010];
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &a[i][j]);
            mp[i+j-1][a[i][j]]++;
        }
    }
    bool flag = 1;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &b[i][j]);
            if(mp[i+j-1][b[i][j]] <= 0){
                flag = 0;
                break;
            }
            mp[i+j-1][b[i][j]]--;
        }
    }
    if(flag){
        puts("YES");
    }else{
        puts("NO");
    }
}

D:题意:给一个序列和一组交换序列(a,b),当且仅当a在b的前面(不允许有间隔),这两个数才能交换,问最后一个数最多能移动多少个位置。
解法:不会,阅读题解,用数组cnt[x]记录数x后面可以与其交换的数的数目,当这个数目刚好等于这个数和最后一个数的距离时,肯定有办法能把最后一个数换到x的位置,然后需要注意的是要一直更新最后一个数的位置,它的位置就是n-ans(即数列长度-已经移动的次数)。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int p[maxn], cnt[maxn];
vector <int> v[maxn];
int main(){
    int n, m, x, y;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        scanf("%d", &p[i]);
    }
    for(int i = 1; i <= m; i++){
        scanf("%d %d", &x, &y);
        v[y].push_back(x);
    }
    for(int i = 0; i < v[p[n]].size(); i++){
        cnt[v[p[n]][i]]++;
    }
    int ans = 0;
    for(int i = n - 1; i >= 1; i--){
        if(n - i - ans == cnt[p[i]]){
            ans++;
        }else{
            for(int j = 0; j < v[p[i]].size(); j++){
                cnt[v[p[i]][j]]++;
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

E. Nastya Hasn’t Written a Legend
题意:有一个数组a和一个数组k,数组a一直保持一个性质:a[i + 1] >= a[i] + k[i]。有两种操作:1,给某个元素加上x,但是加上之后要保持数组a的性质。比如a[i]加上x之后,a[i + 1]<a[i] + k[i],那么a[i + 1]就变成a[i] + k[i],否则不变。同理,若a[i + 2]小于了现在的a[i + 1] + k[i + 1],那么a[i + 2]也变成a[i + 1] + k[i + 1],一直保持这个性质。第二章操作,询问数组a的区间[l, r]的区间和。
题解:https://www.cnblogs.com/pkgunboat/p/10527569.html
题解:来自上面的博主,容易发现,假设更改了x位置之后,恰好到位置y不需要更改元素,那么及其之后的位置肯定就不用更改了。所以,在查找这个位置的时候,我们可以二分查找。找到之后,对区间[x, y]进行操作。我们可以发现,区间[x, y]的数有规律。假设x位置的数是a[x],那么a[x + 1]是a[x] + k[x], a[x + 2]是a[x] + k[x + 1] + k[x + 2],以此类推。这个区间的和可以分为2部分:[y - x + 1]个a[x],和关于k的部分。可以发现,k[x]出现了(r - l + 1)次,k[x + 1]出现了(r - l)次,以此类推。那么怎么O(1)获得这个东西呢?我们先预处理k数组的前缀和(假设前缀和数组是b),那么我们再求b的前缀和(假设是数组c),那么c[i]就是出现了i次k[1],i - 1次k[2],以此类推。那么区间[x, y]中关于k的和就可以得出了:c[y] - c[x - 1] - [y - x + 1] * b[x]。前面c[y] - c[x - 1]可以O(1)算出,而后面的部分比较麻烦。怎么维护后面[y - x + 1] * b[x]这个部分呢?我们发现a[x]正好出现[y - x + 1]次,这样我们可以把a[x] - b[x]用一个懒标记维护,下放标记的时候区间的和为c[y] - c[x - 1] + (r - l + 1) * val (val是a[x] - b[x])。
这个算法的复杂度是nlog(n),我当时在想分块是不是可以维护,但估计会T。太菜了,完全没有状态了,只能看看题解写写,告辞。

#include <stdio.h>
#include <iostream>
using namespace std;
const int maxn = 1e6 + 10;
const long long INF = 1e18;
long long a[maxn], b[maxn], c[maxn];
struct node {
	int l, r;
	long long sum, tag;
}Tree[maxn << 2];

void push_up(int rt) {
	Tree[rt].sum = Tree[rt * 2].sum + Tree[rt * 2 + 1].sum;
}

void push_down(int rt) {
	if (Tree[rt].tag != -INF) {
		Tree[rt * 2].sum = (Tree[rt * 2].r - Tree[rt * 2].l + 1) * Tree[rt].tag + (c[Tree[rt * 2].r] - c[Tree[rt * 2].l - 1]);
		Tree[rt * 2].tag = Tree[rt].tag;
	}
	if (Tree[rt].tag != -INF) {
		Tree[rt * 2 + 1].sum = (Tree[rt * 2 + 1].r - Tree[rt * 2 + 1].l + 1) * Tree[rt].tag + (c[Tree[rt * 2 + 1].r] - c[Tree[rt * 2 + 1].l - 1]);
		Tree[rt * 2 + 1].tag = Tree[rt].tag;
	}
	Tree[rt].tag = -INF;
}

void build(int l, int r, int rt) {
	Tree[rt].l = l, Tree[rt].r = r;
	Tree[rt].tag = -INF;
	if (l == r) {
		Tree[rt].sum = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid, rt * 2);
	build(mid + 1, r, rt * 2 + 1);
	push_up(rt);
}

void update(int L, int R, int l, int r, int rt, long long val) {
	if (L <= l && r <= R) {
		if (val != -INF) {
			Tree[rt].sum = (Tree[rt].r - Tree[rt].l + 1) * val + (c[Tree[rt].r] - c[Tree[rt].l - 1]);
			Tree[rt].tag = val;
		}
		return;
	}
	push_down(rt);
	int mid = (l + r) >> 1;
	if (L <= mid) update(L, R, l, mid, rt * 2, val);
	if (mid < R) update(L, R, mid + 1, r, rt * 2 + 1, val);
	push_up(rt);
}

long long query(int L, int R, int l, int r, int rt) {
	if (L <= l && r <= R) {
		return Tree[rt].sum;
	}
	push_down(rt);
	int mid = (l + r) >> 1;
	long long ans = 0;
	if (L <= mid) ans += query(L, R, l, mid, rt * 2);
	if (mid < R) ans += query(L, R, mid + 1, r, rt * 2 + 1);
	return ans;
}

int main() {
	int n, q;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
	}
	for (int i = 2; i <= n; i++) {
		scanf("%lld", &b[i]);
		b[i] += b[i - 1];
	}
	for (int i = 2; i <= n; i++) {
		c[i] = c[i - 1] + b[i];
	}
	build(1, n, 1);
	scanf("%d", &q);
	char op[10];
	int x, y;
	while (q--) {
		scanf("%s%d%d", op + 1, &x, &y);
		if (op[1] == '+') {
			int l = x;
			int r = n;
			long long temp = query(x, x, 1, n, 1);
			while (l < r) {
				//int mid = (l + r) >> 1;
				int mid = (l + r + 1) >> 1;
				long long temp1 = query(mid, mid, 1, n, 1);
				if ((temp + y + b[mid] - b[x]) > temp1) {
					l = mid;
				}
				else {
					r = mid - 1;
				}
			}
			//printf("************%d %d %d*********\n", x, l, temp + y - b[x]);
			update(x, l, 1, n, 1, temp + y - b[x]);
		}
		else {
			printf("%lld\n", query(x, y, 1, n, 1));
		}
	}
	//system("pause");
	return 0;
}
### 完美排列问题分析 对于 Codeforces Round 1007 (Div. 2) 中的 **B. Perfecto** 问题,目标是找到一个长度为 \( n \) 的完美排列。如果这样的排列存在,则输出该排列;否则输出 `-1`。 #### 题目解析 题目定义了一个“完美排列”,其条件如下: - 对于任意位置 \( i \),满足 \( |p_i - p_{i+1}| = 1 \) 或者 \( |p_i - p_{i+1}| = n-1 \)[^2]。 这意味着相邻两个元素之间的差值要么等于 1(即连续),要么等于 \( n-1 \)(即首尾相连)。 #### 解题方法 通过观察和归纳可以得出以下结论: - 当 \( n \% 3 == 0 \) 或 \( n \% 3 == 2 \) 时,无法构建出符合上述条件的完美排列。 - 而当 \( n \% 3 == 1 \) 时,可以通过特定构造方式生成所需排列。 具体实现逻辑如下: ```cpp #include <bits/stdc++.h> using namespace std; void solve() { int n; cin >> n; if (n % 3 != 1) { // 如果不符合模数条件 cout << "-1\n"; return; } vector<int> res(n); bool flag = true; // 控制交替模式 for(int i = 0;i < n;i++) { if(flag){ res[i] = i + 1; } else{ res[i] = ((n-i)+1)%n; if(res[i]==0)res[i]=n; } flag=!flag; } for(auto num : res){ cout<<num<<" "; } } int main(){ ios::sync_with_stdio(false); cin.tie(0); int t=1; while(t--){ solve(); } } ``` 此代码片段实现了基于输入大小 \( n \) 来判断是否存在合法解并输出相应结果的功能。 #### 关键点说明 - 判断依据来源于对不同余数值下能否形成循环结构的研究成果。 - 构造过程中采用交替填充策略来确保最终序列能够满足绝对差的要求。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值