Educational Codeforces Round 143 (Rated for Div

Educational Codeforces Round 143 (Rated for Div. 2)

Problem - B Ideal Point

给定n个线段区间\([l,r]\),我们定义\(f(x)\)为覆盖点\(x\)的线段数,我们每次操作可以删除任意一条线段,并且操作数不限,给出q次询问,每次询问点x能否通过操作使得\(f(x)\)严格最大

题解:贪心

我们通过模拟发现只要把不包含x的线段全部删除,那么仍然存在的线段都会限制\(f(x)\),然后如果存在其他点y使得\(f(y)>=f(x)\)或者存在,那么说明我们无论如何都不能使得\(f(x)\)最大,反之一定可以,由于数据比较小,直接通过计数数组\(cnt\)判断即可

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n, k;
pii a[55];

void solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> a[i].first >> a[i].second;
    vector<int> cnt(55, 0);
    int ok = 0;
    for (int i = 1; i <= n; ++i)
    {
        if (a[i].first <= k && k <= a[i].second)
        {
            for (int j = a[i].first; j <= a[i].second; ++j)
                cnt[j]++;
        }
    }
    int maxx = -INF, num = 0;
    for (int i = 1; i <= 50; ++i)
        maxx = max(cnt[i], maxx);
    for (int i = 1; i <= 50; ++i)
        if (cnt[i] == maxx)
            num++;
    if (cnt[k] == maxx && num == 1)
        cout << "YES" << endl;
    else
        cout << "NO" << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Problem - C Tea Tasting

给定n杯茶,每杯茶有\(a_i\)毫升,每杯茶面前都有一个品茶人,每次能够喝\(b_i\)毫升的茶,每一轮品茶结束后所有人都会往左移动,也就是说第一回合结束后第一个人不会再喝茶,第二回合结束后第二个人不会再喝茶,那么每个人在每回合能够喝到茶的数量为\(min(a_i,b_i)\),请你求出所有人分别能够喝到的茶的数量

题解:二分+前缀和+差分

首先我们可以知道对于第\(i\)杯茶来说,只有\([i+1,n]\)这些人能够喝到,所以我们可以先求出每个人喝茶\(b_i\)的前缀和\(pre\),对于每杯茶我们只需要二分前缀和找到后面第一个喝不满\(b_j\)这个茶的人\(j\),那么我们只要让\(j\)喝完剩下所有茶,然后利用差分数组记录中间都能喝满自己能喝的茶的人即可;

注意:这边在二分前缀和的时候有个技巧,我们对于每个\(a_i\)都要去往\(i\)的后面找,也就是说所有\(i\)后面的前缀和都要减去\(pre[i-1]\),比较麻烦,那么我们这边大佬有个技巧就是我们直接\(a_i+pre[i-1]\),这样效果也是一样,我只能说太秀

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;

int n;
int pre[N];

void solve()
{
    cin >> n;
    vector<int> ans(n + 10);
    vector<int> a(n + 10), b(n + 10);
    vector<int> dif(n + 10);
    for (int i = 1; i <= n; ++i)
        cin >> a[i];
    for (int i = 1; i <= n; ++i)
        cin >> b[i];
    for (int i = 1; i <= n; ++i)
        pre[i] = pre[i - 1] + b[i];
    for (int i = 1; i <= n; ++i)
    {
        int pos = upper_bound(pre + 1, pre + n + 1, a[i] + pre[i - 1]) - pre;
        ans[pos] += a[i] - (pre[pos - 1] - pre[i - 1]); 
        dif[i]++;
        dif[pos]--;
    }
    for (int i = 1; i <= n; ++i)
        dif[i] = dif[i - 1] + dif[i];
    for (int i = 1; i <= n; ++i)
    {
        ans[i] += b[i] * dif[i];
        cout << ans[i] << " ";
    }
    cout << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Problem - D Triangle Coloring

给定n个节点和n条边,每条边存在权值,并且\(6|n\),将每三个节点变成一个三元组,并用三条边连接,现在需要将n个点中一半的点涂成红色,一半的点涂成蓝色,如果两个点颜色不一样,那么连接这两个点的边的权值会被记录在sum中,最后你需要使得sum最大,并且求出使得sum最大的方案数

题解:组合计数+思维

首先n个点中一半为红色,一半为蓝色,很明显在一个三元组内,我们只有两种涂色方式:

  1. 全为红色或蓝色;
  2. 一红二蓝或者一蓝二红;

显然后者对于sum的贡献更大,我们取后者

假设一个三元组需要涂一红二蓝,也就是说我们在每个三元组中会选择两条边,那么存在以下几种情况:

  1. 每条边的权值都不一样,我们肯定会选择最大的两条,那么只有一种选择
  2. 最小两条边的权值相同,那么我们肯定会选择最大的以及任意一条最小边,有两种选择;
  3. 如果三条边的权值都相同,我们任意选择两条边即可,有3种选择;

那儿我们还需要知道哪些三元组涂了一红二蓝,哪些涂了一蓝二红,答案乘上\(C_{n/3}^{n/6}\)即可

注意:因为取模运算没有除法,所以我们需要利用快速幂求出乘法逆元,\(C_{n/3}^{n/6} = \frac{A_{n/3}^{n/6}}{A_{n/6}^{n/6}}\)

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-9;
const int N = 3e5 + 10, M = 4e5 + 10;

int qpow(int x, int y)
{
    int res = 1;
    while (y)
    {
        if (y & 1)
            res = res * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return res;
}

int A(int n, int m)
{
    int res = 1;
    for (int i = m; i > m - n; --i)
        res = res * i % mod;
    return res;
}

int n;
int a[4];

void solve()
{
    cin >> n;
    int ans = 1;
    for (int i = 1; i <= n; i += 3)
    {
        cin >> a[1] >> a[2] >> a[3];
        sort(a + 1, a + 4);
        if (a[1] == a[2] && a[2] == a[3])
            ans = ans * 3 % mod;
        else if (a[1] == a[2])
            ans = ans * 2 % mod;
    }
    ans = ans * A(n / 6, n / 3) % mod;
    ans = ans * qpow(A(n / 6, n / 6), mod - 2) % mod;
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}

Problem - E Explosions?

给定n个怪兽,每个怪兽血量为\(h_i\),你现在有两种魔法:

  1. 消耗1MP,对一个怪兽的血量减去1,可以释放无数次
  2. 消耗xMP,对于一个怪兽的血量减去x,如果该怪兽死亡,会引起连锁反应,他会对它相邻两边的怪兽产生爆炸伤害\(h_i-1\),如果该伤害继续造成死亡,会继续往旁边造成爆炸伤害,直到没有造成死亡为止,只能在最后一次释放

注意:怪物的位置不能移动,即使怪物死了,也不会消失

请你求出最少需要多少MP才能消灭所有怪物

题解:维护二元单调栈+贪心 :好题目

根据贪心思想,我们最好在最后一步爆炸解决掉所有的怪兽,所以我们可以枚举在每一个怪兽位置爆炸需要的MP,最后取min即可

那么为了实现最后的爆炸能够清除所有的怪兽,我们需要保证数组h要形成一个单峰,引爆位置左边严格递增,引爆位置右边严格递减,那么假设使得左边严格递增的花费为\(L[i]\),右边严格递减的花费为\(R[i]\),那么选择i位置作为引爆点的全部花费为\(L[i]+R[i]+h[i]\)

现在的关键就是如何求出L[i]和R[i],我们先对于L[i]进行讨论,R[i]可以同理得出:

我们可以维护一个单调递增栈,栈中元素是个二元组\((val,cnt)\)表示一个集合:val代表集合中最大值,cnt代表集合中有cnt个数,并且该集合是一个严格递增且差值为1的集合,那么很明显我们可以知道集合中的最小值为\(val-cnt+1\)

那么我们遍历数组\(h\)维护单调递增栈,我们讨论以下情况对单调栈内的集合进行合并和插入:

  1. 如果当前集合的最小值\(val-cnt+1>\)栈顶集合的最大值,直接将插入集合\((h[i],1)\)

  2. 如果当前集合的最小值\(val-cnt+1<=\)栈顶集合的最大值,那么我们需要将集合进行合并,合并的过程需要代价,并将栈顶不断出栈,直到不能合并为止,比如栈顶为(3,1),待插入的集合为(2,1),那么合并后的集合应该为(2,2),所以花费应该为3-2=1,因为我们需要的是一个单调递增且差值为1的集合

再举个例子,(3,3)和(3,2)合并,也就是(1,2,3)和(2,3)合并,那么我们需要的代价就是为了方便先把(1,2,3)变为(0,0,0)然后再变成(0,0,1),所以需要的代价为(1+2+3-1),那么这个代价我们可以利用等差数列求和快速实现

根据这样的操作我们就能够计算出L[i],同理我们从后往前遍历维护单调递增栈,即可求出\(R[i]\)

下面给出一个样例,并附上图示,便于理解集合的合并和为什么这样能够得出L[i]和R[i]:

#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 3e5 + 10, M = 4e5 + 10;

int n;
pii stk[N];
int h[N], L[N], R[N];
int tt;

int cal(int x, int n)
{
    int p = max(0ll, x - n + 1);
    return n * (p + x) / 2;
}

void solve()
{
    cin >> n;
    tt = 0;
    for (int i = 1; i <= n; ++i)
        cin >> h[i];
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        int cnt = 1;
        while (tt && h[i] - cnt + 1 <= stk[tt].first)
        {
            int minn = max(0ll, h[i] - cnt + 1); //防止最小值为负数
            sum += cal(stk[tt].first, min(stk[tt].first, stk[tt].second));
            sum -= cal(max(0ll, minn - 1), min(max(0ll, minn - 1), stk[tt].second));
            cnt += stk[tt].second;
            tt--;
        }
        L[i] = sum;
        stk[++tt] = mpk(h[i], cnt);
    }
    tt = 0;
    sum = 0;
    for (int i = n; i >= 1; --i)
    {
        int cnt = 1;
        while (tt && h[i] - cnt + 1 <= stk[tt].first)
        {
            int minn = max(0ll, h[i] - cnt + 1);
            sum += cal(stk[tt].first, min(stk[tt].first, stk[tt].second));
            sum -= cal(max(0ll, minn - 1), min(max(0ll, minn - 1), stk[tt].second));
            cnt += stk[tt].second;
            tt--;
        }
        R[i] = sum;
        stk[++tt] = mpk(h[i], cnt);
    }
    int ans = INF;
    for (int i = 1; i <= n; ++i)
        ans = min(ans, h[i] + L[i] + R[i]);
    cout << ans << endl;
}
signed main(void)
{
    Zeoy;
    int T = 1;
    cin >> T;
    while (T--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-03-01 12:21  Zeoy_kkk  阅读(13)  评论(0编辑  收藏  举报