CF 907 (A—D题解)

CF 907 (A—D题解)

A:

思路:要修改区间 [ 2 i , 2 i + 1 − 1 ] [2^i,2^{i+1}-1] [2i,2i+11] 中的某个数,必须修改整个区间的数,也就是说区间 [ 2 i , 2 i + 1 − 1 ] [2^i,2^{i+1}-1] [2i,2i+11] 上的数,他们之间相对大小不会变,所以,我们要让整个数组都变成不下降的,当且仅当所有的这样的区间都是不下降的才可以达到目的

AC_code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 22;
int n;
int a[N];
void solve()
{
    cin >> n;
    for(int i=1;i<=n;i++) cin >> a[i];
    int l = 1 , r = 1;
    bool flag = true;
    while(l<=n)
    {
        for(int i=l;i<r;i++)
        {
            if(a[i]>a[i+1]) flag = false;
        }
        l = r + 1;
        r *= 2;
        r = min(r,n);
    }
    if(flag) cout << "YES\n";
    else cout << "NO\n";
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        solve();
    }
    return 0;
}
B:
思路:

a < b a<b a<b ,则如果整数 x 满足 x ∣ 2 a x|2^a x2a,则必然有 x ∣ 2 b x|2^b x2b ,且 KaTeX parse error: Undefined control sequence: \centernot at position 13: (x+2^{a-1}\ \̲c̲e̲n̲t̲e̲r̲n̲o̲t̲ ̲|\ 2^{b}),那么如果 x 数组中出现了 6 那么 6 后面的大于 6 的数字都不会再对原始数组产生影响,所以,我们只要从 x 数组开头开始,只保留递减的数字即可,由于 x 数组中每个数在 [ 1 , 30 ] [1,30] [1,30] 之间,所以我们最多只剩下 30 个数,接下里暴力模拟即可。

Ac_code:
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 100;

int n,q;
int a[N],x[N],p[33],cnt;

void solve()
{
    cnt = 0;
    cin >> n >> q;
    for(int i=1;i<=n;i++) cin >> a[i];
    for(int i=1;i<=q;i++) cin >> x[i];
    for(int i=1;i<=q;i++)
    {
        if(!cnt)
        {
            p[++cnt] = x[i];
        }
        if(p[cnt]>x[i])
        {
            p[++cnt] = x[i];
        }
    }
    for(int i=1;i<=cnt;i++)
    {
        int mod = pow((int)2,p[i]);
        for(int j=1;j<=n;j++)
        {
            if(a[j]%mod==0) a[j] += (mod/2);
        }
    }
    for(int i=1;i<=n;i++) cout << a[i] << ' ';
    cout << '\n';
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        solve();
    }
    return 0;
}
C:
思路:

在操作次数相同的情况下,技能放的越少(但总归要放掉),我们产生的总伤害就越多,我们的总伤害必须要大于等于怪物的总血量才能获胜,如果总伤害大于怪物的总血量(此时肯定是普攻积累的能量有剩余),那么我们必然会有更优的方案(先把多余能量用来放技能),所以最优的方案,总伤害是恰好等于怪物总血量的,那么在总伤害一样的情况下,要使得操作次数最少,就要让每次放技能消耗的能量尽可能多,然而,我们放技能的能量局限于怪的总血量,那么我们优先考虑用技能来对付高血量的怪物,利用小怪来攒能量。当攒的能量刚好能够消灭当前血量最多的怪物时,则使用技能消灭他,如果当前只剩一种怪物,则先积攒能量,当积攒的能量等于剩余血量或者 剩余血量 -1 时,先使用技能,再普攻打死。

AC_code:
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N = 2e5 + 100;
int n;
int a[N];
int ans = 0;
void solve()
{
    ans = 0;
    cin >> n;
    for(int i=1;i<=n;i++) cin >> a[i];
    sort(a+1,a+n+1);
    int l = 1 , r = n;
    int x = 0;
    while(l<r)
    {
        int temp = min(a[r]-x,a[l]);
        ans += temp;
        a[l] -= temp;
        x += temp;
        if(a[l]==0) l++;
        if(x==a[r]) a[r] = 0 ,ans ++ , r -- , x = 0;
    }
    if(l==r&&a[l])
    {
        if(x<a[l]&&a[l]>1)ans += (a[l]-x+1)/2 + 1;
        else ans ++;
    }
    cout << ans << '\n';
}

signed main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;
    cin >> t;
    while(t--)
    {
        solve();
    }
    return 0;
}
D:
思路:

根据题意归纳出 f[x]g[x],的公式为:
f ( x ) = l o g 2   x g ( x ) = l o g ( l o g 2 x )   x f(x) = log_2\ x \\ g(x) = log_{(log_2x)}\ x f(x)=log2 xg(x)=log(log2x) x
这两个函数有很多 x 对应的值是相同的,比如当 x ∈ [ 2 i , 2 i + 1 − 1 ] x\in[2^i,2^{i+1}-1] x[2i,2i+11] 时, f ( x ) = i f(x)=i f(x)=i g ( x ) = l o g i x g(x)=log_ix g(x)=logix。所以我们考虑分块,将相同的值放在一块考虑。

  1. 将区间 [ l , r ] [l,r] [l,r] 分成若干个区间 [ l , 2 i − 1 ] [l,2^i-1] [l,2i1] [ 2 i , 2 i + 1 − 1 ] [2^i,2^{i+1}-1] [2i,2i+11] [ 2 i + 1 , 2 i + 2 − 1 ] [2^{i+1},2^{i+2}-1] [2i+1,2i+21] [ 2 k , r ] [2^k,r] [2k,r]
  2. 对于上述的每个区间内 l o g 2 x log_2x log2x 都是定值,假设区间 [ s l , s r ] [sl,sr] [sl,sr] 是上述区间中的一个,且 l o g 2   s l = l o g 2   s r = i log_2\ sl =log_2\ sr = i log2 sl=log2 sr=i
  3. 则对于任意 x ∈ [ s l , s r ] x\in[sl,sr] x[sl,sr],有 g ( x ) = l o g i   x g(x)=log_i\ x g(x)=logi x
  4. 我们再考虑将区间 [ s l , s r ] [sl,sr] [sl,sr] 分成若干个区间 [ s l , i j − 1 ] , [ i j , i j + 1 − 1 ] , . . . . . . , [ i k , s r ] [sl,i^j-1],[i^j,i^{j+1}-1],......,[i^k,sr] [sl,ij1],[ij,ij+11],......,[ik,sr](在本题中,由于 l o g 2   x ≥ 2 log_2\ x \ge 2 log2 x2 ,所以 [ s l , s r ] [sl,sr] [sl,sr] 最多分成两块。
  5. 4 中划分的区间, g ( x ) g(x) g(x) 的值均相同,只要算出左端点对应的 g ( x ) g(x) g(x) ,在乘上区间长度,就可以得到这一段区间 g ( x ) g(x) g(x) 的和了。
  6. 时间复杂度 O ( 2 q l o g 2 l e n ) O(2qlog_2len) O(2qlog2len)
AC_code:
#include<bits/stdc++.h>
using namespace std;
std::ostream& operator<<(std::ostream& os, __int128 p) {
    vector<char> a;
    if(p<0)os<<'-',p=-p;
    if(p==0)a.push_back(0);
    while (p)a.push_back(p % 10), p /= 10;
    for (int i = a.size() - 1; i >= 0; i--)os << (signed)(a[i]);
    return os;
}
std::istream& operator>>(std::istream& os, __int128& p) {
    p = 0;
    string s;
    os >> s;
    for(int i=0;i<s.size();i++)if(s[i]!='-')p=p*10+s[i]-'0';
    if(s[0]=='-')p=-p;
    return os;
}
#define int __int128
const int mod = 1e9 + 7;
int get_e(int a,int b) // 返回最大的满足 a^x <= b 的 x;
{
    int res = 0;
    int temp = 1;
    while (temp < b) {
        temp *= a;
        res++;
    }
    if (temp > b) return res - 1;
    return res;
}
int get_num(int l,int r) // g(x) 在 [l,r] 上的和
{
    int ans = 0, sr, sum = 1;
    int i = get_e(2, l);
    int s = get_e(i, l);
    while (l <= r) {
        while (sum <= l) sum *= i;
        sr = min(sum - 1, r);
        ans = (ans + (sr - l + 1) * s) % mod;
        s++;
        l = sr + 1;
    }
    return ans;
}
void solve() {
    int ans = 0, l, r, sum = 1, sr;
    cin >> l >> r;
    while (l <= r) // 先按f(x)的值分块
    {
        while (sum <= l) sum *= 2;
        sr = min(sum - 1, r);
        ans = (ans + get_num(l, sr)) % mod; // 再次分块计算 g(x) 的和
        l = sr + 1;
    }
    cout << ans << '\n';
}
signed main() {
    ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很高兴为您提供 Mathor Cup 2022 D 题的解题思路。 题目描述: 给定一个 $n\times n$ 的矩阵 $A$,其中 $A_{i,j}\in\{0,1\}$。你可以进行任意次以下操作: 1. 将第 $i$ 行取反(即 $A_{i,j}\rightarrow 1-A_{i,j}$); 2. 将第 $j$ 列取反(即 $A_{i,j}\rightarrow 1-A_{i,j}$)。 请你计算通过若干次操作后,能够使得矩阵 $A$ 的每一行和每一列的 $1$ 的个数相等的最小操作次数。 解题思路: 本题可以使用贪心和二分图匹配的思想来解决。具体步骤如下: 1. 统计每一行和每一列的 $1$ 的个数,设 $row_i$ 表示第 $i$ 行的 $1$ 的个数,$col_j$ 表示第 $j$ 列的 $1$ 的个数。 2. 如果每一行和每一列的 $1$ 的个数都相等,那么无需进行任何操作,直接输出 $0$。 3. 如果某一行 $i$ 的 $1$ 的个数多于其他行的 $1$ 的个数,那么可以将该行取反,将 $row_i$ 减一,将 $col_j$ 加一。 4. 如果某一列 $j$ 的 $1$ 的个数多于其他列的 $1$ 的个数,那么可以将该列取反,将 $col_j$ 减一,将 $row_i$ 加一。 5. 重复步骤 3 和步骤 4,直到每一行和每一列的 $1$ 的个数都相等。 6. 计算进行的操作次数,输出结果。 需要注意的是,为了避免重复计算,我们可以使用二分图匹配的思想来进行操作。将每一行和每一列看做二分图的两个部分,如果某一行 $i$ 的 $1$ 的个数多于其他行的 $1$ 的个数,那么可以将第 $i$ 行和所有 $1$ 的个数比该行少的列建立一条边;如果某一列 $j$ 的 $1$ 的个数多于其他列的 $1$ 的个数,那么可以将第 $j$ 列和所有 $1$ 的个数比该列少的行建立一条边。最后,将二分图的最小路径覆盖数乘以 $2$ 就是最小操作次数。 时间复杂度:$O(n^3)$。 完整代码:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值