Bessie and MEX

题目链接

CodeTON Round 8 (Div. 1 + Div. 2, Rated, Prizes!)

B. Bessie and MEX


思路1(逆推):

注意看, p p p 数组是个排列,所以 0 ∼ n − 1 0\sim n-1 0n1 的所有数都会且只会出现一次。最后的 m e x mex mex 值一定是 n n n。我们正着推过去很难,但是倒推就会很简单,因为我们不知道 p i p_i pi 但是知道 m e x { p 1 , p 2 , … , p i } mex\{p_1,p_2,\dots,p_i\} mex{p1,p2,,pi} 的,所以直接倒退即可。

具体来说,我们知道了 m e x { p 1 , p 2 , … , p i } mex\{p_1,p_2,\dots,p_i\} mex{p1,p2,,pi},要去推 p i p_i pi,可以直接通过 a i a_i ai 算出 p i = m e x { p 1 , p 2 , … , p i } − a i p_i=mex\{p_1,p_2,\dots,p_i\}-a_i pi=mex{p1,p2,,pi}ai。然后去掉 p i p_i pi 算出 m e x { p 1 , p 2 , … , p i − 1 } mex\{p_1,p_2,\dots,p_{i-1}\} mex{p1,p2,,pi1} 就可以继续逆推了。

code1:

队友的代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pb push_back
#define endl '\n'
#define VI vector<int>
#define pii pair<int, int>
#define rep(i, j, k) for (int i = j; i <= k; ++i)
#define per(i, j, k) for (int i = j; i >= k; --i)
#define print(a) cout << a << endl;
#define NO return cout << "No\n", void()
#define YES return cout << "Yes\n", void()

const int inf = 0x3f3f3f3f;
const long long llinf = (long long)0x3f3f3f3f3f3f3f3f;
const long long MOD = (long long)1e9 + 7LL;
const size_t N = (size_t)1e6 + 5;

#define IO                       \
    ios::sync_with_stdio(false); \
    std::cin.tie(0);             \
    std::cout.tie(0)

#define debug(a) std::cerr << "\033[34m" << #a << ':' << a << "\033[0m" << endl

#define debugList(list)                        \
    std::cerr << "\033[34m" << #list << ": ["; \
    for (auto& e : list) {                     \
        std::cerr << e << ", ";                \
    }                                          \
    std::cerr << "\b\b]\033[0m" << endl

#define otto auto

int in() {
    int x;
    cin >> x;
    return x;
}

void solve(int cs) {
    int n = in();
    VI a(n + 1);
    int m = n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    VI ans;
    for (int i = n; i > 0; i--) {
        int t = m - a[i];
        ans.pb(t);
        m = min(m, m - a[i]);
    }
    reverse(ans.begin(), ans.end());
    for (auto i : ans) {
        cout << i << ' ';
    }
    cout << endl;
}   

signed main() {
    IO;
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int T, TT = 1;
    T = 1; 
    cin >> T;
    while (T--) {
        solve(TT++);
    }
    return 0;
}

思路2(正推):

正推是比较难的,因为我们只知道 m e x { p 1 , p 2 , … , p i − 1 } mex\{p_1,p_2,\dots,p_{i-1}\} mex{p1,p2,,pi1},只能猜测 p i = m e x { p 1 , p 2 , … , p i − 1 } − a i p_i=mex\{p_1,p_2,\dots,p_{i-1}\}-a_i pi=mex{p1,p2,,pi1}ai ,不过有可能新加入的 p i = m e x { p 1 , p 2 , … , p i − 1 } p_i=mex\{p_1,p_2,\dots,p_{i-1}\} pi=mex{p1,p2,,pi1} m e x { p 1 , p 2 , … , p i } − a i mex\{p_1,p_2,\dots,p_{i}\}-a_i mex{p1,p2,,pi}ai 就会变化,就错了。

所以一种解决方案是:找到 p 1 , p 2 , … , p i − 1 p_1,p_2,\dots,p_{i-1} p1,p2,,pi1 中最小未出现的数 t t t(也就是mex值)。如果 t − a i t-a_i tai 得到的数不符合条件(<0,或者已经存在了),就加入 p i = t p_i=t pi=t,否则加入 t − a i t-a_i tai

为什么这么做是对的: p i p_i pi 要么等于其他没有出现的数,这时 p i = m e x { p 1 , p 2 , … , p i − 1 } − a i = t − a i p_i=mex\{p_1,p_2,\dots,p_{i-1}\}-a_i=t-a_i pi=mex{p1,p2,,pi1}ai=tai 就是对的( a i a_i ai 可能是负数),如果不对,那么变成 t t t m e x mex mex 发生变化就还有机会。因为题目保证一定有解,所以前者不成立,后者一定成立,验证前者成不成立就行了。

另外,两种情况一定不会同时成立,所以不会有二选一的情况出现。因为如果前者成立,那么 p i = t − a i p_i=t-a_i pi=tai 一定是没有出现过的数,一定 ≥ t \ge t t,因此 a i ≤ 0 a_i\le0 ai0。而后者成立则需要 m e x { p 1 , … , p i } − a i = t mex\{p_1,\dots,p_i\}-a_i=t mex{p1,,pi}ai=t,因为 m e x { p 1 , … , p i } > t mex\{p_1,\dots,p_i\}>t mex{p1,,pi}>t ,因此 a i > 0 a_i>0 ai>0。两种情况成立条件显然互斥。

时间复杂度方面,虽然用了 s e t set set 暴力求 m e x mex mex。因为随着数的加入, m e x mex mex 值是单调变大的,最后变成 n n n。我们可以不重置 t t t,让它从上一次的值继续向后找,这样找 n n n 次最后到达 n n n。时间复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn) 了。

code2:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
const int maxn=2e5+5;

int T,n;
int a[maxn];

int main(){
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n;i++)cin>>a[i];
		set<int> S;
		for(int i=1,t=0,x;i<=n;i++){
			while(S.find(t)!=S.end())t++;//前i-1个数的mex 
			if(t-a[i]<0 || S.find(t-a[i])!=S.end())x=t;
			else x=t-a[i];
			cout<<x<<" \n"[i==n];
			S.insert(x);
		}
	}
	return 0;
}
  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!根据题目描述,我们需要帮助 Bessie 计算最小成本逃生计划的数量,并且要对结果进行取模操作。为了解决这个问题,我们可以使用动态规划的方法来求解。 首先,我们定义一个二维数组 dp,其中 dp[i][j] 表示在位置 (i, j) 时的最小成本逃生计划数量。我们可以使用动态规划的思想进行状态转移。 根据题目要求,所有的牛必须聚集在同一个单元格中才能逃生,因此我们可以将问题分解为两个子问题: 1. 选择一个单元格 (i, j),使得牛能够聚集在该单元格中。 2. 计算到达单元格 (i, j) 的最小成本逃生计划数量。 对于第一个子问题,假设我们选择了单元格 (i, j) 作为聚集点,那么该单元格一定有一个入口单元格 (x, y),其中 (x, y) 是 (i, j) 的上方或左方单元格。因此,我们可以通过遍历所有可能的入口单元格来计算第二个子问题的结果。 对于第二个子问题,我们可以使用动态规划的方法进行求解。假设我们已经计算了 dp[x][y] 的结果,那么到达单元格 (i, j) 的最小成本逃生计划数量可以通过以下方式计算: 1. 如果 (i, j) 的上方单元格 (x, y) 存在,那么 dp[i][j] += dp[x][y]。 2. 如果 (i, j) 的左方单元格 (x, y) 存在,那么 dp[i][j] += dp[x][y]。 最后,我们需要遍历所有的单元格,找到其中最小成本逃生计划数量的最小值,并将结果对 10^9+7 取模。 下面是对应的 C++ 代码实现: ```cpp #include <iostream> #include <vector> using namespace std; const int MOD = 1e9 + 7; int countEscapePlans(vector<vector<int>>& gates) { int N = gates.size(); int K = gates[0].size(); vector<vector<int>> dp(N, vector<int>(K, 0)); // 初始化边界条件 dp[0][0] = 1; // 动态规划求解 for (int i = 0; i < N; i++) { for (int j = 0; j < K; j++) { if (i > 0) { dp[i][j] += dp[i - 1][j]; dp[i][j] %= MOD; } if (j > 0) { dp[i][j] += dp[i][j - 1]; dp[i][j] %= MOD; } } } return dp[N - 1][K - 1]; } int main() { int N, K; cin >> N >> K; vector<vector<int>> gates(N, vector<int>(K, 0)); for (int i = 0; i < N; i++) { for (int j = 0; j < K; j++) { cin >> gates[i][j]; } } int result = countEscapePlans(gates); cout << result << endl; return 0; } ``` 这段代码首先读取了输入的矩阵大小,然后读取了矩阵中每个单元格的解锁成本。最后,将调用 `countEscapePlans` 函数计算最小成本逃生计划的数量,并输出结果。 希望这可以帮助到你!如果你有任何其他问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值