[USACO 2018 US Open Platinum]Out of Sorts
壹、题目描述 ¶
贰、题解 ¶
◆ 前言
以前学得也太水了吧......原题都不会做。
◆ 考试の思考
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;
}
肆、关键の地方 ¶
道路铺设,也是对极长连续段操作,我居然没想到它也可以用来求这道题的操作次数,我真的是个傻逼......