[USACO 2018 US Open Platinum]Out of Sorts

壹、题目描述 ¶

传送门 to LOJ.

贰、题解 ¶

◆ 前言

以前学得也太水了吧......原题都不会做。

◆ 考试の思考

PS:以下大量无效内容,可以略读或者跳过。

看到冒泡排序,十分容易让人联想到 反序表,不过在此反序表好像并没有什么用,并且无法很好地翻译 关键点 这个家伙,很快就放弃对它的思考。

然后,大概想了一下,这个所谓 关键点 其实就是在冒泡过程中去掉了一些不必要的比较,并且,当一个点成为关键点之后,它将始终是关键点,于是,我开始从 正难则反 方向入手:一般的冒泡比较次数为 \((n-1)\times n\),一共 \(n\) 轮,每轮进行 \(n-1\) 次,那么,当一个点成为关键点之后,每一轮将会减少一次比较,也即,如若我求出某个点在第 \(i(i\in [0,n])\) 轮之后成为关键点那么它会减少一共 \(n-i\) 次比较,现在问题变成,如何求出某个点在几轮之后会成为关键点。

假如我们目前正在考察第 \(i\) 个点,那么,它成为关键点的充要条件为,前面不存在大于 \(i\) 的数,后面不存在小于等于 \(i\) 的数(实际上两者之一即可)然后,我们需要一些观察来解决这个问题,接下来,我们称 \([1,i]\) 为关键区间。

  • 每一轮,将会有恰好一个 \(x>i\) 被移出关键区间;
  • 如果当前 \(i\) 不是关键点,每一轮,所有 \(\le i\) 并且不在关键区间的数都会向左移动一格。

那么,我们要做的就是,在 将所有大于 \(i\) 的数移出关键区间的轮数将所有小于等于 \(i\) 的元素移进关键区间的轮数 中选择一个最大值。其实还可以省略一下,就是 \([1,i]\) 的元素距离关键区间的最远距离。这个可以使用 \(\rm BIT\) 扫一遍解决。

结果样例都过不了,分析了一下原因,发现求的并不严格是执行比较器的次数,因对于一个区间执行排序的贡献是 \(R-L+1\),它比严格执行比较器的次数多一,这正好让样例输出少掉 \(3\),不过,这并不是简单加上轮数最大值就可以解决的问题,因为每一轮少掉了并非 \(1\),而是当前轮数的连续段数。能否使用某些方法补上这个空缺?赶脚好像不能做?

然后我就不想做了。

◆ 考后の思考+题解

考试之后梳理到此,想了一下,其实还是可以做的,并且和 [NOIP2018]道路铺设 的过程挺像的,我们可以将题目抽象为:

你有一个长度为 \(n\) 的序列 \(\{a_i\}\),每次你可以将一个极长连续段整体减去 \(1\)(当某个 \(a_i=0\) 时,将其视作断点),规定对区间 \([L,R]\) 操作一次的花费为 \(R-L+2\). 现在,对于该序列反复操作之后,将所有数变为 \(0\) 的操作花费是多少?

答案就是 操作次数 + \(\sum a_i\),操作次数可以直接使用道路铺设的结论,然后就过了......

原来自己会做啊

第二种方法,在这里:

我自己也觉得害怕 于是我去看了一下我的代码:

using namespace Elaina;

const int maxn=1e5;

pii a[maxn+5];int n;

inline void Init(){
    cin>>n;
    rep(i,1,n)cin>>a[i].fi,a[i].se=i;
}

int cnt[maxn+5];ll ans,x;

signed main(){
    Init(),sort(a+1,a+n+1);
    int maxx=0;
    rep(i, 1, n) printf("%d %d\n", a[i].fi, a[i].se);
    rep(i,1,n-1){
        maxx=Max(maxx,a[i].se);
        cnt[i]=maxx-i;
        printf("cnt[%d] == %d\n", i, cnt[i]);
    }rep(i,1,n){
        x=Max(cnt[i],cnt[i-1]);
        ans+=x?x:1;
        printf("When i == %d, x == %d\n", i, x);
    }cout<<ans<<'\n';
	return 0;
}

怎么那么短! 那些输出是我打的,我在看我以前的代码在干什么......

发现代码实质是一样的,那么就不解释了,最后再贴一发刚刚写的。

叁、参考代码 ¶

# include <cstdio>
# include <algorithm>
using namespace std;

namespace Elaina {

# define rep(i, l, r) for(int i=l, i##_end_=r; i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=l, i##_end_=r; i>=i##_end_; --i)

typedef long long ll;

inline int readint() {
    int x=0; char c; bool f=0;
    while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}

} using namespace Elaina;

const int maxn=1e5;

int a[maxn+5], n;
int t[maxn+5];

inline void input() {
    n=readint();
    rep(i, 1, n) t[i]=a[i]=readint();
}

inline void getHash() {
    sort(t+1, t+n+1);
    rep(i, 1, n) a[i]=lower_bound(t+1, t+n+1, a[i])-t;
}

namespace saya {

int mx[maxn+5];

#define lowbit(i) ((i)&(-(i)))

inline void modify(int i, int v) {
    for(; i<=n; i+=lowbit(i)) mx[i]=max(mx[i], v);
}

inline int query(int i, int ret=0) {
    for(; i; i-=lowbit(i)) ret=max(ret, mx[i]);
    return ret;
}

} // using namespace saya;

int turn[maxn+5];

signed main() {
    // freopen("sort.in", "r", stdin);
    // freopen("sort.out", "w", stdout);
    input();
    getHash();
    saya::modify(a[n], n);
    for(int i=n-1; i; --i) {
        int mx=saya::query(i);
        if(mx) turn[i]=mx-i;
        saya::modify(a[i], i);
    }
    ll ans=0;
    rep(i, 1, n-1) {
        if(turn[i]>turn[i-1]) ans+=turn[i]-turn[i-1];
        ans+=turn[i];
    }
    printf("%lld\n", ans);
    return 0;
}

肆、关键の地方 ¶

道路铺设,也是对极长连续段操作,我居然没想到它也可以用来求这道题的操作次数,我真的是个傻逼......

posted @ 2021-08-27 17:08  Arextre  阅读(43)  评论(0编辑  收藏  举报