题解:P17716 [HexOI Round 1] 变魔术 / magic

· · 题解

题意简述

给定数组 a_1\sim a_n,对每个下标 i,问是否存在操作策略,最后只保留第 i 块积木。 操作规则:

  1. 将当前手上积木任意分成若干堆;
  2. 保留总和最大的一堆,其余淘汰;
  3. 重复直到只剩一块。

思路

1、暴力

如果考试遇到,我肯定优先考虑暴力(正解想不出 QwQ)。我们先将数组排序从小到大排序,总和 cnt=\sum a_i。我们尝试从大到小不断扔掉元素:

设当前手里总和为 now,现在想扔掉一个元素 y:

Q:那我们什么时候可以把 y 淘汰掉呢?

A:把 y 单独成一堆,剩下所有东西作为另一堆。剩下一堆总和是 now-y,只要 now-y \ge y,就可以保留剩下那堆,丢掉 y。

now-y \ge y \iff (now-y)\times 2 \ge now

从最大的元素开始依次尝试丢弃:

::::info[60 分暴力]

#include<bits/stdc++.h>
#define int long long
using namespace std;
struct S{int v,id;};
bool cmp(S x,S y){
    return x.v<y.v;
}
signed main(){
    int n,cnt=0;
    cin>>n;
    vector<S> a(n);
    vector<int> pre(n+1,0);
    vector<int> ans(n,0);
    for(int i=0; i<n; i++) {
        cin>>a[i].v;
        a[i].id=i;
        cnt+=a[i].v;
    }
    sort(a.begin(),a.end(),cmp);
    for(int i=0; i<n; i++) pre[i+1]=pre[i]+a[i].v;
    for(int i=0; i<n; i++) {
        int now=cnt;
        for(int j=n-1; j>=0; j--) {
            if(j==i) continue;
            int y=a[j].v;
            if((now-y)*2>=now)now-=y;
            else break;
        }
        if(now==a[i].v) ans[a[i].id]=1;
    }
    for(int i=0; i<n; i++) cout<<ans[i]<<" ";
}
60 分记录
恭喜你成功的获得了 60 分的好成绩,但接下来是 O(n \log n) 满分做法。

2、AC 做法

考虑前缀和,并且使用双指针优化。仍然是将数组从小到大排序,得到 b_0\le b_1\le\cdots\le b_{n-1},将数组升序排序,定义前缀和:

s[0]=0,\quad s[k]=\sum_{j=0}^{k-1} b_j

记 C_i=\sum_{k=0}^i b_k,则目标 b_i 可行的充要条件:

我们选定目标元素 $b_i$: 1. 初始化我方集合总和:$cnt_0 = b_i + s[i] = C_i$(把所有小于等于 $b_i$ 的元素全部纳入我方集合)。 2. 从 $t=i+1$ 开始,依次考察后续元素 $b_{i+1},b_{i+2},\dots,b_{n-1}$: 若当前总和 $cnt \ge b_t$,则更新 $cnt \leftarrow cnt+b_t,\quad t\leftarrow t+1$。 若 $cnt<b_t$,判定该目标不可行,终止。 3. 若最终 $t=n$(全部元素处理完毕),则 $ok[i]=1$;否则 $ok[i]=0$。 终于得出了满分代码: ::::info[AC code] ```cpp #include<bits/stdc++.h> #define int long long using namespace std; int n,t,ok[100005],s[100005]; signed main() { cin>>n; vector<int> a(n),b(n); for(int i=0; i<n; i++) cin>>a[i],b[i]=a[i]; sort(b.begin(),b.end()); for(int i=0; i<n; i++) s[i+1]=s[i]+b[i]; for(int i=0; i<n; i++) { int cnt=b[i]+s[i]; t=max(t,i+1); while(t<n&&cnt>=b[t])cnt+=b[t++]; if(t==n)ok[i]=1; } for(int x:a) { int p=lower_bound(b.begin(),b.end(),x)-b.begin(); cout<<ok[p]<<' '; } } ``` [AC记录](https://www.luogu.com.cn/record/304214557) :::: 这篇题解到这里就结束了,如果对你有帮助,就点个赞吧。