AtCoder Regular Contest 197 (Div. 2) Tutorial for B~D

B - Minimum Cost Sort

考虑有什么性质。交换的位置越靠右,花费就越大,这启示我们要尽量避免在右边交换。

如何避免呢?考虑贪心,先把 \(n\) 放到最右边,然后是 \(n-1\)……让大数先归位,减少了之后在右边的交换,就能减少花费。

使用树状数组标记排列中有哪些位置上的数被移走了,使用等差数列求和即可统计答案。

// B - Minimum Cost Sort
#include <cstdio>
#include <iostream>
#define ll long long
#define rep(i, s, t) for(int i=s; i<=t; ++i)
#define debug(x) cerr<<#x<<":"<<x<<endl;
const int N=200010;
using namespace std;
char buf[1<<21], *p1=buf, *p2=buf;
#define gc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
    int x=0, f=1; char c=gc();
    while(c<'0' || c>'9') c=='-' && (f=-1), c=gc();
    while('0'<=c && c<='9') x=(x<<3)+(x<<1)+c-'0', c=gc();
    return x*f;
}

int n, a[N], pos[N]; ll res;
int c[N];
inline void upd(int x) {for(; x<=n; x+=x&-x) ++c[x];}
inline int sum(int x)
{
    int res=0; for(; x; x-=x&-x) res+=c[x];
    return res;
}
inline ll Range(int l, int r) {return (r+l)*(r-l+1ll)>>1;}

int main()
{
#ifdef Jerrywang
    freopen("E:/OI/in.txt", "r", stdin);
#endif
    n=read();
    rep(i, 1, n) a[i]=read(), pos[a[i]]=i;
    for(int x=n; x; x--)
    {
        int i=pos[x], p=i-sum(i);
        res+=Range(p, x-1); upd(i);
    }
    printf("%lld", res);

    return 0;
}

C - Cost to Flip

很有意思的题目!给一个全程无需树状数组的解法。

首先有一个很简单的思路:设两个序列 \(v_1=\{c_i|a_i=1\land b_i=0\},v_2=\{c_i|a_i=0\land b_i=1\}\)。把 \(v_1\) 从大到小排序,\(v_2\) 从小到大排序,依次翻转 \(v_1\),然后翻转 \(v_2\)

不难发现这样是错的。假设 \(c_x\) 非常大,然而 \(a_x=1\land a_y=1\),这也就意味着按上述操作,\(c_x\) 这么一个非常大的累赘会在每一步中贡献代价,不优。

再设序列 \(v=\{c_i|a_i=1\land b_i=1\}\),从大到小排序。考虑增量法求答案。也就是说,考虑 \(v_1\sim v_i\) 这些 \(1\) 都是先删后加回,其余的 \(1\) 保持不变。这会对答案有如下图的贡献:

1 2 3 4 5        1 2 3 4 5
* * * * *        * * * * *
  * * * *          * * * *
                     * * *    <- 把 2 删掉
  * * *            ? * *
  *   *            ?   *
              =>
                   *   *      <- 再把 2 加回来
  *   *   *        *   *   *

上图中用 * 表示 \(1\),假设位置 \(2\) 上这个 \(1\) 改成 \(0\) 后又改成 \(1\),多出了箭头所指的两步。但是,? 的两处原来是 \(1\),现在 \(1\) 缺席了。

在什么时间删,又在什么时间加回来呢?考虑结合上原来的思路,即可二分找到位置。注意内置的二分函数的使用。细节很多。

// C - Cost to Flip
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#define ll long long
#define rep(i, s, t) for(int i=s; i<=t; ++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
const int N=200010;
using namespace std;
char buf[1<<21], *p1=buf, *p2=buf;
#define gc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read()
{
    int x=0, f=1; char c=gc();
    while(c<'0' || c>'9') c=='-' && (f=-1), c=gc();
    while('0'<=c && c<='9') x=(x<<3)+(x<<1)+c-'0', c=gc();
    return x*f;
}

int n, a[N], b[N], c[N]; ll s, tmp1[N], tmp2[N], cur, ans, delta;
int v1[N], n1, v2[N], n2, v[N], m;

int main()
{
#ifdef Jerrywang
    freopen("E:/OI/in.txt", "r", stdin);
#endif
    n=read();
    rep(i, 1, n) a[i]=read();
    rep(i, 1, n) b[i]=read();
    rep(i, 1, n) c[i]=read();
    rep(i, 1, n)
    {
        if(a[i]) s+=c[i];
        if(a[i] && !b[i]) v1[++n1]=c[i]; // 删除多余的1
        if(!a[i] && b[i]) v2[++n2]=c[i]; // 添加没有的1
        if(a[i] && b[i]) v[++m]=c[i];
    }
    sort(v1+1, v1+n1+1, greater<int>());
    sort(v2+1, v2+n2+1);
    sort(v+1, v+m+1, greater<int>());
    tmp1[0]=s;
    rep(i, 1, n1) s-=v1[i], tmp1[i]=s, ans+=s;
    tmp2[0]=s;
    rep(i, 1, n2) s+=v2[i], tmp2[i]=s, ans+=s;
    cur=ans;
    rep(i, 1, m)
    {
        int p1=lower_bound(v1+1, v1+n1+1, v[i], greater<int>())-v1-1;
        cur+=tmp1[p1]-delta-v[i]-(ll)(n1-p1)*v[i];
        int p2=upper_bound(v2+1, v2+n2+1, v[i])-v2-1;
        delta+=v[i];
        cur+=tmp2[p2]-delta+v[i]-(ll)p2*v[i];
        ans=min(ans, cur);
    }
    printf("%lld", ans);

    return 0;
}
posted @ 2025-05-17 21:20  JosephusWang  阅读(13)  评论(0)    收藏  举报