洛谷P3586 [POI 2015] LOG Logistyka 分析与解答

68 阅读4分钟

 操作1是对序列的修改,重点来看如何实现操作2

维护一个长度为 n 的序列,一开始都是 0,支持以下两种操作:

  1. U k a 将序列中第 k 个数修改为 a。
  2. Z c s 在这个序列上,每次选出 c 个正数,并将它们都减去 1,询问能否进行 s 次操作。

每次询问独立,即每次询问不会对序列进行修改。

 “减去1”的操作如何直观得被感受呢,可以把一个数写成很多个1累积而成的形式

例如,现在有一组数 1 3 2 4 3,可以表示成下面这样

********************
********************
************4****
****2****45
****2345
12345
13243

1是第一个数,所以其上为1,之后类推

每次操作取c个数,取s次,这样看取数很乱,为了看得清楚一些,把每次取出来的c个数放在一排,一共放s排

例如,取4个数,取3次,以上表格通过移动格子里的数可以变成如下情况:

4
****
第1次取2145
第2次取2345
第3次取2345

 此时,每次取的4个数没有出现重复的,并且我们的数据可以满足取3次数

右上角的4没有用到,原因是一共取3次,第4个数出现了4次,4次比3次大,就算每次都取4,也只能取3个,不会取完

所以如果一个数是n,取s次的话,若n>s,他最多在这个c*s的表格中贡献的格子数量最多就是s

如果n<=s,在这个表格中贡献的格子数量最多是n

如果所有数能贡献的格子数量的最大加和>=cs,是不是就一定能构成这样的cs的网格,从而满足题意呢?

答案是肯定的,如需说明见后Prof1.

现在问题转化为,每一个数有一个贡献值,这个贡献值是数的值,但最大是s,如果所有数贡献值的和加起来(此时得到的是最大贡献值),大于等于了c*s,就可以满足取数要求

以上面的情况举例:

********************
********************
************4****
****2****45
****2345
12345
13243

取c=4,s=3,每个数的最大贡献值依次是:1 3 2 3 3,和为12,12>=c*s,所以可以满足取数要求


现在,问题转化为,给定s,求数列中所有数的贡献值的和

可以用所有数的总和减去超出s部分的和,超出s部分的和可以用大于等于s的数的总和,减去(s*大于等于s的数的数目)

大于等于s的数的总和好解决,大于等于s的数的数目也好解决,两者都使用树状数组维护。

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

#define lowbit(x) (x&(-x))
#define ll long long

const int N=1e6,maxn=1e6+5;
ll a[maxn],b[maxn],n,m;
ll tree1[maxn],tree2[maxn],old[maxn];

struct TempType{
    ll id,v;
    bool operator < (const TempType& rhs) const {
        return v<rhs.v;
    }
}dat[maxn];

struct operation{
    char op;ll x,y;
}opt[maxn];

void update(ll *tree,ll x,ll d){
    if(x==0){
        return;
    }
    while(x<=m){
        tree[x]+=d;
        x+=lowbit(x);
    }
}

ll sum(ll *tree,ll x){
    ll res=0;
    while(x)
    {
        res+=tree[x];
        x-=lowbit(x);
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(nullptr);

    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>opt[i].op>>opt[i].x>>opt[i].y;
        dat[i].id=i;
        dat[i].v=opt[i].y;
    }
    //离散化
    stable_sort(dat+1,dat+1+m);
    dat[0].v=-1;
    dat[0].id=0;
    for(int i=1;i<=m;i++){
        if(dat[i].v==dat[i-1].v){
            b[dat[i].id]=b[dat[i-1].id];
        }else{
            b[dat[i].id]=b[dat[i-1].id]+1;
        }
    }
    /*离散化测试
    for(int i=1;i<=m;i++){
        cout<<b[i]<<" ";
    }*/
    //翻转
    for(int i=1;i<=m;i++){
        b[i]=m+1-b[i];
    }
    //操作
    ll s=0;
    for(int i=1;i<=m;i++){
        char op=opt[i].op;
        ll x=opt[i].x,y=opt[i].y;

        if(op=='U'){
            update(tree1,old[x],-a[x]);
            update(tree2,old[x],-1);
            s-=a[x];
            update(tree1,b[i],y);
            update(tree2,b[i],1);
            old[x]=b[i];
            a[x]=y;
            s+=y;
        }else{
            //printf("s=%d sum1=%d sum2=%d\n",s,sum(tree1,b[i]),sum(tree2,b[i]));
            if(s-(sum(tree1,b[i])-sum(tree2,b[i])*y)>=x*y){
                cout<<"TAK"<<"\n";
            }else{
                cout<<"NIE"<<"\n";
            }
        }
    }
    return 0;
}

prof1

如果能说明每次取数不会取到两个或多个同样的数,就可以了

从简单情况开始:要取c个数,取s次,如果现在只有c个数,那必须要每个数都大于等于s,才能满足题目条件,此时每次取这不同的c个数,一定是没有重复的

进一步,如果数的个数超过了c,但是贡献的格子数量的最大值大于等于了cs,说明在简单情况的cs网格中,每一行的c格中,有一些格子被替换成了原本的c种数以外的数,替换后,与同行相比,因为是c种数以外的数,所以不会有与同行中的数种类相同的数,所以不会出现重复