Codeforces Round 889.div2

文章详细阐述了三种不同的算法思路,包括检查元素与下标的匹配个数并计算平均值,寻找最长的非倍数区间,以及通过操作构造全负或全正序列以满足特定条件。每种方法都涉及到了数组遍历、条件判断和效率优化策略,如上取整、位运算和动态规划等技术。
摘要由CSDN通过智能技术生成

A. Dalton the Teacher

思路

找到元素与其下标不匹配的个数,答案即为 (个数 / 2 ) (个数/2) (个数/2的上取整。

int a[N];

void solve()
{
    int n;
    cin>>n;
 
    int cnt = 0;
    for(int i=1;i<=n;i++) 
    {
        cin>>a[i];
        if(i == a[i]) cnt++;
    }
 
    cout<<(cnt+1)/2<<endl;
}

B. Longest Divisors Interval

思路

假设 n n n不是 x x x的倍数,那么答案区间长度为 1 1 1~ x − 1 x-1 x1,显然从 1 1 1
开始是最优的,可以手玩小样例感受一下,那么枚举 i i i,从 1 1 1开始枚举即可。注意特判。

void solve()
{
    int n;
    cin>>n;
 
    if(n <= 2)
    {
        cout<<n<<endl;
        return ;
    }
 
    for(int i=1;i<=n;i++)
    {
        if(n%i!=0)
        {
            cout<<i-1<<endl;
            return ;
        }
    }
}

C2 - Dual (Hard Version)

思路

这是一种可行的方法,将序列变为全负或全正,全负数的话,求后缀和,因为为负数, a [ i ] a[i] a[i] + + + a [ i + 1 ] a[i+1] a[i+1] < = <= <= a [ i + 1 ] a[i+1] a[i+1],符合条件。全正数同理,只不过是求前缀和。

那么如何构造出全负或全正的呢?

设数组正数个数为 c n t cnt cnt,非正数个数为 c n t t cntt cntt(将 0 0 0视作负数)

如果 c n t < = 7 cnt<=7 cnt<=7,这样我们将一个负数不断加和,加和 5 5 5次,那么其值必然小于 − 20 -20 20,可以将任意一个正数变为负数,最大需要操作 5 + 7 = 12 5+7=12 5+7=12次,此时序列变为全负数,求后缀和,操作需要 19 19 19次,总共 31 31 31次。

如果 c n t t < = 7 cntt<=7 cntt<=7,同理即可,此时构造的序列为全正数。

其他情况,显然两者的个数的最大值小于等于 12 12 12,我们找出绝对值最大的数,假设为负数,让其他正数加上这个数,最多操作 12 12 12次,构造出全负数序列,求后缀和,操作 19 19 19次,总共 31 31 31次。

代码很多部分粘贴复制即可,带有注释。

int a[N];
 
void solve()
{
    int n;
    cin>>n;
 
    // 正数的个数 非正数的个数
    int cnt = 0 ,cntt = 0;
    int minv = 1e18, maxv = -1e18;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
 
        if(a[i] > 0) cnt++;
        else cntt++;
 
        minv = min(minv,a[i]), maxv = max(maxv,a[i]);
    }
 
    int res = 0; // 记录操作次数
    vector<pii> s; // 记录操作序列
 
    if(minv >= 0)
    {
        for(int i=2;i<=n;i++)
        {
            res++;
            a[i] += a[i-1];
            s.push_back({i,i-1});
        }
        cout<<res<<endl;
        for(auto [x,y] : s) cout<<x<<" "<<y<<endl;
        return ;
    }
 
    if(maxv <= 0)
    {   
        for(int i=n-1;i>=1;i--)
        {
            res++;
            a[i] += a[i+1];
            s.push_back({i,i+1});
        }
        cout<<res<<endl;
        for(auto [x,y] : s) cout<<x<<" "<<y<<endl;
        return ;
    }
 
    // 正数的个数小,考虑把全部的正数变为负数
    // 最大操作次数: 5 + 7 + 19 最小负数不断加和=5, 将正数变为负数=7, 求后缀和=19
    if(cnt <= 7)
    {
        int id;
        for(int i=1;i<=n;i++) if(a[i] == minv) id = i;
        // 只需要最小的负数累加5次,它的值一定会小于 -20 的,可以将任意正数变为负
        for(int i=1;i<=5;i++)
        {
 
            res++;
            a[id] += a[id];
            s.push_back({id,id}); 
        }
        // 让正数变为负数
        for(int i=1;i<=n;i++)
        {
            if(a[i]>0)
            {
                res++;
                a[i] += a[id];
                s.push_back({i,id});
            }
        }
        // 倒序求后缀和即可
        // 因为序列是非正的,a[i] + a[i+1] <= a[i+1]
        for(int i=n-1;i>=1;i--)
        {
            res++;
            a[i] += a[i+1];
            s.push_back({i,i+1});
        }
    }
 
    // 粘贴复制即可
    // 负数的个数小,考虑把全部的负数变为正数
    else if(cntt <= 7)
    {
        int id;
        for(int i=1;i<=n;i++) if(a[i] == maxv) id = i;
        for(int i=1;i<=5;i++)
        {
 
            res++;
            a[id] += a[id];
            s.push_back({id,id}); 
        }
        for(int i=1;i<=n;i++)
        {
            if(a[i]<0)
            {
                res++;
                a[i] += a[id];
                s.push_back({i,id});
            }
        }
 
        for(int i=2;i<=n;i++)
        {
            res++;
            a[i] += a[i-1];
            s.push_back({i,i-1});
        }
    }
 
    // cnt > 7 && cntt > 7
    // 即 max(cnt,cntt) <= 12
    // 我们需要找出绝对值最大的数,假如为负数,那么需要将其余的正数全部变为负
    // 最大操作次数: 12 + 19 
    else 
    {
        int id;
        // 目标序列 全负数
        if(abs(minv) > abs(maxv))
        {   
            for(int i=1;i<=n;i++) if(a[i] == minv) id = i;
            for(int i=1;i<=n;i++) 
                if(a[i] > 0)
                {
                    res++;
                    a[i] += a[id];
                    s.push_back({i,id});
                }
            for(int i=n-1;i>=1;i--) 
            {
                res++;
                a[i] += a[i+1];
                s.push_back({i,i+1});
            }
        }
        else 
        {
            for(int i=1;i<=n;i++) if(a[i] == maxv) id = i;
            for(int i=1;i<=n;i++) 
                if(a[i] < 0)
                {
                    res++;
                    a[i] += a[id];
                    s.push_back({i,id});
                }
            for(int i=2;i<=n;i++) 
            {
                res++;
                a[i] += a[i-1];
                s.push_back({i,i-1});
            }
        }
    }
 
    cout<<res<<endl;
    for(auto [x,y] : s) cout<<x<<" "<<y<<endl;
}

D. Earn or Unlock

思路

发现这样一个性质 : : 当最后一张解锁的牌是 i i i时,此时的收益是 s u m sum sum [ [ [ i i i ] ] ] − - ( ( i i i − - 1 1 1 ) ) 。 。
这样,我们考虑每个位置是否可以到达,用 d p dp dp来求解。常规的方法是 n 2 n^2 n2的复杂度,会超时。可以用 b i e s e t bieset bieset进行优化,由于最后一次用来解锁其他牌的数字可能会很大,因此数组开到 2 n 。 2n。 2n

int a[N], s[N];
bitset<N> f;
 
void solve()
{
    int n;
    cin>>n;
 
    for(int i=1;i<=n;i++) cin>>a[i], s[i] = s[i-1] + a[i];
 
    int ans = 0;
    f[1] = 1;
    for(int i=1;i<=n;i++)
    {
        if(s[i] < i) break;
        f |= (f<<a[i]);
        if(f[i]) ans = max(ans,s[i]-(i-1)), f[i] = 0;
    }
 
    for(int i=n+1;i<=2*n;i++)
    {
        if(f[i]) ans = max(ans,s[n]-(i-1)), f[i] = 0;
    }
 
    cout<<ans<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值