Educational Codeforces Round 96 (Rated for Div. 2) (A

A - Number of Apartments

题意

给一个数n,输出一组a,b,c,使得 (3 imes a+5 imes b+7 imes c=n) ,输出任意一组解。((1≤t≤1000)(1≤n≤1000)

分析

先考虑枚举,先枚举 5b 和 7c,判断 (n-5b-7c)%3==0。复杂度完全可以。

code

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    scanf("%d", &n);
    while (n--) {
        int x;
        scanf("%d", &x);
        bool flag = false;
        for (int i = 0; i <= x; i += 5) {
            for (int j = 0; j <= x; j += 7) {
                int sum = i + j;
                if (sum > x) break;
                if ((x - sum) % 3 == 0) {
                    printf("%d %d %d
", (x - sum) / 3, i / 5, j / 7);
                    flag = true;
                    break;
                }
            }
            if (flag) break;
        }
        if (!flag) printf("-1
");
    }
    return 0;
}

分析

简单的做法,观察一下会发现 3%3=0,7%3=1,5%3=2,这样除了1,2,4这三个数没有答案,其他数字都可表示出来。

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
	int T;
	cin >> T;
	while (T--) {
		int n;
		cin >> n;
		if (n == 1 || n == 2 || n == 4) {
			cout << -1 << endl;
		} else if (n % 3 == 0) {
			cout << n / 3 << ' ' << 0 << ' ' << 0 << endl;
		} else if (n % 3 == 1) {
			cout << (n - 7) / 3 << ' ' << 0 << ' ' << 1 << endl;
		} else {
			cout << (n - 5) / 3 << ' ' << 1 << ' ' << 0 << endl;
		}
	}
	return 0;
}

B - Barrels

题意

有n个桶,编号从1开始。你可以选择两个不同的桶x和桶y,将桶x中的水倒入桶y中,桶的容量是无限的。最多可以倒水k次,计算桶中最大和最小水量之间的最大可能差。

分析

贪心。每次将两个水最多的桶合并,倒一次后就会出现空桶(水量为0),答案就是水最多的桶的水量。所以答案就是水最多的 k+1 个桶的和。

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[200005];
bool cmp(int x, int y) { return x > y; }
int main() {
	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]);
		}
		sort(a + 1, a + n + 1, cmp);
		ll ans = 0;
		for (int i = 1; i <= k + 1; ++i) {
			ans += a[i];
		}
		printf("%lld
", ans);
	}
	return 0;
}

C - Numbers on Whiteboard

题意

给你1到n的数列,你可以删去两个数a、b,然后添加一个数 (lceilfrac{a+b}{2} ceil) ,显然 n-1 次操作后会剩下一个数,使这个数最小。输出最后一个数,并且输出 n-1 次操作中选择删除的两个数a和b。

分析

最后剩余的那个数最小是2,因为,如果我们合并两个正数,其中至少有一个是大于等于2的数,新的数字总是大于1。我的做法:考虑到合并相邻的数会让合并后的数四舍五入到加一,所以我第一次就合并最大的数m和次次大的数m-2,然后第二次合并的两个数肯定是m-1,m-1这两个数,然后后面就是每次合并剩余的最大的数和次次大的数,这个过程写几个数模拟一下就很明白了。官方题解:直接每次合并两个最大的数。这样竟然真的可以!!

code

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        printf("2
");
        if (n == 2) {  // 我的写法需要特判 n==2 的时候
            printf("2 1
");
            continue;
        }
        for (int i = 1; i < n; ++i) {
            if (i == 1) {
                printf("%d %d
", n, n - 2); //第一次合并
            } else if (i == 2) {
                printf("%d %d
", n - 1, n - 1); //第二次合并
            } else {
                printf("%d %d
", n - i + 2, n - i); //第三次以后的合并
            }
        }
    }
    return 0;
}

D - String Deletion

题意

一串01序列,给定一次操作为:删除任意一位0或1,如果串不为空,接着删除由相同字符组成的最长前缀。直到变为空串。求最多可以操作多少次。

分析

贪心,首先把01串统计出连续的0和连续的1的个数,比如111010统计结果为3,1,1,1,如果第一个数大于1,则删除第一位,如果第一个数等于1,则让第一个不为1的数减一。

code

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
char s[N];
int a[N], cnt; //统计
int pos[N], tot; //记录大于1的数在a数组中的下标
int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        int n;
        scanf("%d", &n);
        scanf("%s", s + 1);
        cnt = 0, tot = 0;
        for (int i = 1; i <= n; ++i) {
            int k = i, t = 1;
            while (s[i + 1] == s[k]) {
                i++;
                t++;
            }
            a[++cnt] = t;
            if (a[cnt] > 1) {
                pos[++tot] = cnt;
            }
        }
        int ans = 0;
        int i = 1, j = 1;
        while (i <= cnt) {
            if (a[i] <= 1) {
                while (j <= tot && (pos[j] < i || a[pos[j]] <= 1)) {
                    j++;
                }
                if (j <= tot) {
                    a[pos[j]]--;
                } else {
                    i++;
                }
            }
            ans++;
            i++;
        }
        printf("%d
", ans);
    }
    return 0;
}

E - String Reversal

题意

给你一个字符串s,你可以交换相邻的两个字符,最后使这个字符串s翻转。求最小的交换次数。

分析

贪心地依次从右向左让每一位归位,设pos为下一个将要归位的位置,每次从pos开始往左选相同的离它最近的那个字母移动到pos位置,这样是最优的(不会证明…手动模拟猜的)。我们用栈或数组记录下每个字母的位置,每次取栈顶位置的字母就是距离最近的了。问题来了,当字母移动后,原来字母的位置会发生变化,又因为我们是把左边的字母移动到右边,所以原来字母的位置只会向左移动,也就是位置坐标只会减小。对任意字母设初始位置为p,只有它的左边移走了字母才会对它的位置有影响,我们用树状数组标记移走的字母的位置,那么p-c[p]就是它当前的位置。

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll c[200005];
void add(int x) {
	while (x < 200005) {
		c[x]++;
		x += (x & -x);
	}
}
ll sum(int x) {
	ll res = 0;
	while (x) {
		res += c[x];
		x -= (x & -x);
	}
	return res;
}
int main() {
	stack<int> s[30];
	int n;
	cin >> n;
	string str;
	cin >> str;
	for (int i = 0; i < n; ++i) {
		s[str[i] - 'a'].push(i + 1);  //树状数组要从1开始
	}
	ll ans = 0;
	for (int i = 0; i < n; ++i) {
		int pos = s[str[i] - 'a'].top();
		ans += (n - i - pos + sum(pos - 1));
		add(s[str[i] - 'a'].top());
		s[str[i] - 'a'].pop();
	}
	cout << ans << endl;
	return 0;
}

F - Realistic Gameplay

题意

你有一把最多可以装 (k) 发子弹的枪,有 (n) 波怪物,第 (i) 波怪物有 (a_i) 个,在 (l_i) 时间出现,必须在 (r_i) 时间之前消灭(包括 (r_i) 时刻),对于每两波连续的怪物,第二波开始不早于第一波结束时(尽管第二波可以在第一波结束时同时开始,(r_ile l_{i+1})),瞄准和射击是瞬间完成的,不消耗时间,一颗子弹能杀死一个怪物,但重新装弹需要消耗一秒时间,当你重新装弹时,你会扔掉弹匣里所有剩余的子弹(产生了浪费),在消灭所有怪物后,你不会扔掉剩下的子弹。问最少需要用掉多少子弹(包括射出的和扔掉的)如果无法清除所有的,输出 -1。

分析

参考博客
简单理解一下,一开始有K发子弹,假设在某一秒先瞄准射击再重新装弹,在每个时间段 ([l_i,r_i]) 内,最多能换上并且射出去的子弹是 ((r_i-l_i) imes k) 发,最后一秒 (r_i) 时刻换上的子弹用在了下一波中,就和第一波开始之前就已经有k发子弹一样,这样只需要判断每一波可用的子弹够不够 (a_i) ,如果上一波和下一波连着,那么下一波就失去了这个开始之前准备好的k发子弹,最多也就只能用前一波剩在枪里的子弹,所以下一波应该提前准备好的子弹就应该算在上一波提前准备好的子弹里。(dp[i]) 就表示每一波开始前至少需要准备好的子弹个数。求出dp来以后正着遍历一遍数组,就模拟射击和重新装弹的过程,每一波的射击过程肯定是射出先前准备在枪里的那些子弹后,再k发k发的射出,最后剩在枪里的就是 (((res-a[i])%k+k)%k) ,注意负数,判断够不够下一波需要提前准备的子弹数,如果不够就扔掉,然后重新装弹(每一波需要提前准备的子弹数肯定不会超过k,如果超过k那么在这个时间段内肯定不能全部消灭)。

code

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 2000 + 10;
int n, k, l[N], r[N], a[N];
LL dp[N];  // 表示第i波怪物来之前枪里至少还需要有dp[i]发子弹

int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i] >> a[i];
    }
    for (int i = n; i >= 1; i--) {
        LL need = a[i];
        if (r[i] == l[i + 1]) {
            need += dp[i + 1];
        }
        if (1ll * (r[i] - l[i] + 1) * k < need) {
            return cout << -1, 0;
        }
        dp[i] = max(0ll, need - 1ll * (r[i] - l[i]) * k);
    }
    int res = k; //初始有K发子弹
    LL ans = 0;
    for (int i = 1; i <= n; i++) {
        if (res < dp[i]) { //如果当前子弹数量不够那么肯定要把当前剩余子弹都扔了,然后装弹
            ans += res;    //浪费的子弹
            res = k;       //重新装弹
        }
        ans += a[i]; //打出ai发子弹消灭第i波怪兽
        res = ((res - a[i]) % k + k) % k; //消灭第i波怪兽后枪内剩余的子弹数量
    }
    cout << ans;
    return 0;
}
原文地址:https://www.cnblogs.com/liuzhihan/p/13832340.html