【CF】Day140——杂题 (图论 + 贪心 | 二分 + 贪心 | 数学 | 动态规划)

发布于:2025-09-11 ⋅ 阅读:(12) ⋅ 点赞:(0)

D. 2+ doors

题目:

思路:

有多种解法,这里选了一个稍微好理解的解法

题目的条件给的很简单,给你 q 个限制,在确保 a[i] | a[j] = x 的限制下构造出字典序最小的数组 a

显然我们可以分位考虑,因为每个位都是不影响的,那么考虑如何构造

假如 a[i] | a[j] = x 中某一位是 0,那么 a[i] 和 a[j] 这一位就必须都是 0,否则如果是 1,那么二者可以任意一个这一位是 1 另一个是 0,但是当 i = j 时,a[i] 这一位就必须是 1 了

我们定义 must0[i][j] 为 a[i] 的第 j 位是否必须为 0,如果 must0[i][j] = 1,则这位只能是 0,must1[i][j] 同理

然后考虑建图,由于有其中一个是 0 一个是 1 的情况,那么显然我们可以让下标小的那个数为 0,所以定义 g[i][j] 其中储存着当 i 的第 j 位为 0 时有哪些下标的该位必须为 1

随后就直接暴力模拟即可,具体实现可以看代码,还是很好理解的

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());

int must0[100005][30], must1[100005][30];
// 当 i 的第 j 位为 0 时其孩子必须为 1
vector<vector<vector<int>>> g(100005, vector<vector<int>>(30));
void solve()
{
    int n, q;
    cin >> n >> q;
    while (q--)
    {
        int i, j, x;
        cin >> i >> j >> x;
        if (i > j)
            swap(i, j);
        for (int w = 0; w < 30; w++)
        {
            if (x >> w & 1)
            {
                if (i == j)
                    must1[i][w] = 1;
                else
                    g[i][w].push_back(j);
            }
            else
            {
                must0[i][w] = must0[j][w] = 1;
            }
        }
    }
    for (int i = 1; i <= n; i++)
    {
        int res = 0;
        for (int w = 0; w < 30; w++)
        {
            if (must0[i][w])
            {
                for (auto &son : g[i][w])
                {
                    must1[son][w] = 1;
                }
                continue;
            }
            int flag = must1[i][w];
            for (auto &son : g[i][w])
            {
                if (must0[son][w])
                    flag = 1;
            }
            if (!flag)
            {
                for (auto &son : g[i][w])
                {
                    must1[son][w] = 1;
                }
            }
            else
            {
                res |= 1 << w;
            }
        }
        cout << res << " ";
    }
    cout << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

C. Min-Max Array Transformation

题目:

思路:

感觉有点难想

不难发现 d_min 是很简单的,我们直接在 b 中找到第一个小于等于 a[i] 的下标即可,则有 d_min = b[it] - a[i]

但是 d_max 略显复杂,我们画图理解一下

假设我们的 a[i] 选了 j 这个位置,那么 i ~ j 的位置就需要重新排列了,考虑最极限情况,即

为什么呢?因为我们两个数组都是递增的,所以选择相邻的比较肯定是最好的,否则如果往前选,那么配对失败的概率越高

那为什么不用管其余位置呢?显然其余位置直接配对即可

那为什么 j 不往右边选呢?显然如果选了右边的,那么 j + 1 就面临着 j 的同样情况,所以显然不行

所以我们显然现在的目标就是找到一个点 j,使得对于每一个 k,都有 b[k-1] >= a[k],如何去找呢?

我们不妨考虑倒序枚举,一开始 j 为 n,那么显然当某个点 i 的 d_min 的位置也是 i 时就不行了,因为此时就如下图了

此时紫线配对是不成立的,因此此时就要更新 j 了

具体实现看代码

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n), b(n), mi(n), mx(n);
    for (int i = 0; i < n; i++)
        cin >> a[i];
    for (int i = 0; i < n; i++)
        cin >> b[i];
    int j = n - 1;
    for (int i = n - 1; i >= 0; i--)
    {
        auto it = lower_bound(b.begin(), b.end(), a[i]) - b.begin();
        mi[i] = b[it] - a[i];
        mx[i] = b[j] - a[i];
        if (it == i)
        {
            j = i - 1;
        }
    }
    for (int i = 0; i < n; i++)
        cout << mi[i] << " ";
    cout << endl;
    for (int i = 0; i < n; i++)
        cout << mx[i] << " ";
    cout << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

D. Madoka and The Corruption Scheme

题目:

思路:

转换为数学问题

题目乍一看一点都不知道怎么写,我们不妨先考虑左线全胜利,那么就是下图所示

那么我们考虑最后获胜的人有什么特点,不难发现,如果一个数 x 能走 n 次红边,那么就能获胜

那么现在我能将 k 条边重新定向,我们不妨考虑 3节点 要赢差几条边,显然就差一条,所以 3 节点只需要 一次 重新定向即可,我们定义 f[i] 为需要修改 i 次才能获胜的节点数量,不难发现其就等于 \binom{n}{i}

所以我们只需要求 0 ~ min(k,n) 的  \binom{n}{i} 即可,假设求出来的结果为 sum,也就是说在修改 k 条边的情况下能强行使得 sum 个节点获胜,那么为了小的获胜,我们将 1 ~ sum 分给这 sum 个节点即可,所以最后结果就是 sum

具体实现看代码

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());

const int MOD = 1e9 + 7;
int fac[100005], inv_fac[100005];

int qp(int a, int b)
{
    int res = 1;
    a %= MOD;
    while (b)
    {
        if (b & 1)
            res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

int C(int n, int m)
{
    return fac[m] * inv_fac[n] % MOD * inv_fac[m - n] % MOD;
}

void solve()
{
    int n, k;
    cin >> n >> k;
    fac[0] = 1;
    for (int i = 1; i <= n; ++i)
        fac[i] = fac[i - 1] * i % MOD;
    inv_fac[n] = qp(fac[n], MOD - 2);
    for (int i = n - 1; i >= 0; i--)
        inv_fac[i] = inv_fac[i + 1] * (i + 1) % MOD;
    int ans = 0;
    for (int i = 0; i <= min(n, k); i++)
    {
        ans = (ans + C(i, n)) % MOD;
        ans = (ans + MOD) % MOD;
    }
    cout << ans << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

D1. Xor-Subsequence (easy version)

题目:

思路:

题意真的很麻烦,为什么不能说人话

题目大意:

让你求一个 a 的最长的子序列 b,其中 b 需要满足任意两个相邻的元素都要满足 a[i] ^ j < a[j] ^ i,其中 j > i

那么显然可以按照 LIS 的思想直接双重枚举,对于 dp[i] 就有 dp[i] = max{ dp[j] + 1 },其中 j < i,且 a[j] ^ i < a[i] ^ j

但是对于 n 这个 1e5 数据显然会爆,不妨观察到题目中的 a[i] 很小只有 200,再看到转移条件中的异或操作,不难想到我们最多只能改变 a[i] 的二进制的八位,所以 j 其实不用从 0 开始枚举,只需要从 i - 256 开始枚举即可

然后就是套 LIS 即可,具体实现看代码

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n);
    vector<int> dp(n, 1);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    int mx = 0;
    for (int i = 0; i < n; i++)
    {
        for (int j = max(i - 256, 0LL); j < i; j++)
        {
            if ((a[j] ^ i) < (a[i] ^ j))
            {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        mx = max(dp[i], mx);
    }
    cout << mx << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到