2021暑假 HDU中超 第九场 1006


2021暑假 HDU中超 第九场 1006

Guess the weight

*Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 235 Accepted Submission(s): 55*

题意

有一个初始含 \(n\) 张牌的牌堆,每张牌的费用 \(a_i \le 2\times10^5\)

有一种法术:从牌堆中抽取一张牌,然后猜测下一张牌的费用大于它还是小于它,若猜中即可抽取第二张牌(等于算没猜中)

现在有 \(q\) 个操作,分两种:

  • 往牌堆里塞 \(x\) 张费用为 \(w\) 的牌,\(w\le 2\times10^5\)
  • 使用法术,要求输出能抽到第二张牌的概率(最简分数形式)
Sample Input
2
2 5
1 1
2
1 1 2
2
1 1 2
2
2 5
1 2
2
1 1 3
2
1 100 4
2
Sample Output
0/1
2/3
2/3
1/1
5/6
201/3502

\(\quad\)

常数爆炸并且最后也没卡过去的思路:

这部分写着留念一下 愿天下没有卡常

因为牌的费用在 \(2\times 10^5\)?? 之内,所以可以考虑维护一个 \(cnt\) 序列,加入 \(x\)? 张费用为 \(w\)? 的牌就是在序列第 \(w\)? 位上加 \(x\)?

查询的时候,显然我们会比较第一张牌的费用左边和右边那边牌数更多,选择较多的那一边

显然存在一个分界线,抽中左边都猜大于,抽中右边都猜小于

当抽到第一张牌的费用为 \(w\)????????? 时,选小于猜中概率即为 \((sum(w)-cnt(w)) /(cnt_总-1)\)????????? ,\(sum\)??????? 表示前缀和,选大于同理。选择这张牌的概率是 \(cnt(w)/cnt_总\)??

计算答案时分母的 \(cnt_总\times(cnt_总-1)\) 保持不变,只需计算所有位置的 \((sum(w)-cnt(w))\times cnt(w)\) 之和

考虑用线段树维护

线段树维护区间?卡牌总数,区间前缀和之和,后缀和之和,区间所有点的答案之和

把修改操作分成三部分:单点修改,右边前缀和区间加,左边后缀和区间加

为了便于查找分界线(前缀和大于后缀和的第一个位置),还需要维护区间前缀和的最小值和后缀和的最大值

10M输入的大样例测完是对的,可惜这题卡常。。(代码扔最后)

\(\quad\)

正解:

上面这种思路完全想复杂了属于是

总情况数的角度考虑,从 \(n\) 张牌里抽两张有 \(n \times(n-1)\)? 种方案

还是把牌按费用分成左右两份,策略为:第一张牌在左时,猜大于;第一张牌在右则猜小于

那么:

  • 两张牌分别在左右两边,一定能猜对
  • 两张牌费用相同,一定猜错
  • 两张牌在同一边,并且费用不相同时,有一半能猜对

前两种很好理解,第三种就是两张牌离 \(mid\) 远的先出现就能猜对,否则猜错

先二分找到前缀和大于后缀和的第一个位置,按此划分之后计算就好了

维护前缀和,计算重复数个数方法有很多,树状数组就可以(话说这个 \(O(nlog^2_n)\)? 都过了,上面那棵 \(nlogn\) 的线段树还TLE,果然人傻常数大

\(\quad\)

AC代码(轻度压行):

#include
using namespace std;
#define ll long long
const ll maxn = 200100;

ll a[maxn], mxn = 200001, cnt = 0;
struct BitTree{
    ll t[2*maxn];
    ll lb(int x) {return x&-x;}
    void add(int p, ll ad){for(int i=p;i<=mxn*2;i+=lb(i)) t[i]+=ad;}
    ll sum(int p){ return p?t[p] + sum(p-lb(p)):0;}//ps:迭代比递归快,卡常时勿贪压行
    void clear(){memset(t, 0, sizeof(t));}
}bt, btcf;

int get_mid(){//前缀和大于后缀和的第一个位置
    int l = 1, r = mxn, mid;
    while(l < r){
        mid = (l + r)/2;
        if(bt.sum(mid)*2 > cnt+a[mid]) r = mid;
        else l = mid + 1;
    } return r;
}
ll gcd(ll x, ll y){ return (y==0) ? x : gcd(y, x%y);}
inline void add(int p, ll d){
    bt.add(p, d), btcf.add(p, 2*a[p]*d + d*d - d);
    a[p] += d, cnt += d;
}

signed main(){
    ll T = 1, n, Q, opt, x, w;
    cin >> T;
    while(T--){
        for(int i=1;i<=mxn;i++) a[i] = 0;
        bt.clear(); btcf.clear(); cnt = 0;
        cin >> n >> Q;
        for(int i=1;i<=n;i++) scanf("%d", &x), add(x, 1);
        for(int i=1;i<=Q;i++){
            cin >> opt;
            if(opt == 1) scanf("%d%d", &x, &w), add(w, x);
            else{
                int mid = get_mid();
                ll l = bt.sum(mid - 1), r = cnt - l;
                ll lf = btcf.sum(mid - 1), rf = btcf.sum(mxn) - lf;
                ll fm = cnt * (cnt - 1);
                ll fz = fm - (l * (l - 1) + lf)/2 - (r * (r - 1) + rf)/2;
                ll g = gcd(fz, fm);
                cout << fz/g << '/' << fm/g << '\n';
            }
        }
    }
}

\(\quad\)

TLE代码:

int n, q;
int mxn = 2e5 + 10;

struct SegTree{
    int l, r;
    ll sw, sum[2];//区间左右前缀答案
    ll sl[2];//单点前后缀,区间最小前缀和最大后缀
    int tg[2];//前缀和加tag
}t[maxn<<2];

inline void push_up(int p){
    t[p].sum[0] = t[p<<1].sum[0] + t[p<<1|1].sum[0];
    t[p].sum[1] = t[p<<1].sum[1] + t[p<<1|1].sum[1];
    t[p].sl[0] = min(t[p<<1].sl[0], t[p<<1|1].sl[0]);
    t[p].sl[1] = max(t[p<<1].sl[1], t[p<<1|1].sl[1]);
    t[p].sw = t[p<<1].sw + t[p<<1|1].sw;
}
inline void push_sum(int p, int ad, int dir){
    t[p].sl[dir] += ad;
    t[p].sum[dir] += ad * t[p].sw;
    t[p].tg[dir] += ad;
}
inline void push_down(int p){
    if(t[p].tg[0]){
        push_sum(p<<1, t[p].tg[0], 0);
        push_sum(p<<1|1, t[p].tg[0], 0);
        t[p].tg[0] = 0;
    }
    if(t[p].tg[1]){
        push_sum(p<<1, t[p].tg[1], 1);
        push_sum(p<<1|1, t[p].tg[1], 1);
        t[p].tg[1] = 0;
    }
}

void build(int p, int l, int r){
    t[p].l = l; t[p].r = r;
    t[p].sw = t[p].sum[0] = t[p].sum[1] = 0;
    t[p].tg[0] = t[p].tg[1] = t[p].sl[0] = t[p].sl[1] = 0;
    if(l == r) return;
    int mid = (l+r)/2;
    build(p<<1, l, mid);
    build(p<<1|1, mid+1, r);
    push_up(p);
}

void add_w(int p, int pos, int w){
    if(t[p].l == t[p].r){
        t[p].sw += w;
        t[p].sl[0] += w; t[p].sl[1] += w;
        t[p].sum[0] = (t[p].sl[0] - t[p].sw) * t[p].sw;
        t[p].sum[1] = (t[p].sl[1] - t[p].sw) * t[p].sw;
        return;
    }
    push_down(p);
    int mid = (t[p].l + t[p].r)/2;
    if(pos <= mid) add_w(p<<1, pos, w);
    else add_w(p<<1|1, pos, w);
    push_up(p);
}

void add_sum(int p, int l, int r, int ad, int dir){//区间前缀和加
    if(l > r) return;
    if(l <= t[p].l && t[p].r <= r){
        push_sum(p, ad, dir);
        return;
    }
    push_down(p);
    int mid = (t[p].l + t[p].r)/2;
    if(l <= mid) add_sum(p<<1, l, r, ad, dir);
    if(mid < r) add_sum(p<<1|1, l, r, ad, dir);
    push_up(p);
}

ll ask_sum(int p, int l, int r, int dir){
    if(l > r) return 0;
    if(l <= t[p].l && t[p].r <= r) return t[p].sum[dir];
    push_down(p);
    ll res = 0, mid = (t[p].l + t[p].r)/2;
    if(l <= mid) res += ask_sum(p<<1, l, r, dir);
    if(mid < r) res += ask_sum(p<<1|1, l, r, dir);
    return res;
}

int find_mid(int p){//寻找前缀小于后缀的第一个位置
    if(t[p].l == t[p].r) return t[p].l;
    push_down(p);
    if(t[p<<1|1].sl[0] >= t[p<<1|1].sl[1]) return find_mid(p<<1);//右边能满足走左边
    else return find_mid(p<<1|1);//右边不满足走右边
}

ll gcd(ll x, ll y){
    while(y > 0){
        swap(x, y);
        y = y%x;
    }
    return x;
}

void solve(){
    cin >> n >> q;
    build(1, 1, mxn);
    for(int i=1;i<=n;i++){
        int x; cin >> x;
        add_w(1, x, 1);
        add_sum(1, x+1, mxn, 1, 0);
        add_sum(1, 1, x-1, 1, 1);
    }
    for(int i=1;i<=q;i++){
        int opt, x, w;
        cin >> opt;
        if(opt == 1){
            cin >> x >> w;
            add_w(1, w, x);
            add_sum(1, w+1, mxn, x, 0);
            add_sum(1, 1, w-1, x, 1);
        }else{
            int r = find_mid(1);
            ll fz = 0, fm = 1ll * t[1].sw * (t[1].sw - 1);
            fz += ask_sum(1, 1, r, 1);
            fz += ask_sum(1, r+1, mxn, 0);
            ll g = gcd(fz, fm);
            cout << fz/g << '/' << fm/g << '\n';
        }
    }
}