3 条题解

  • 3
    @ 2024-7-4 16:39:19

    MXOJ Next Problem 3 Solution

    题目

    here

    思路

    询问离线和双树状数组解法还是太复杂了。

    首先,题面的意思就是完全不用考虑糖果的顺序,只考虑每一个糖果种类的数量。

    愤怒值的本质,就是分给一个小朋友的糖果数量减去种数。

    但是,这种方法前人已经提过了,考虑换一个角度。

    kk 个小朋友分糖果,本质上相当于最多有 kk 个同种糖果不会对答案产生贡献。

    也就是说,令 m=n+qm=n+qcic_i 为目前第 ii 种糖果的个数,则所有糖果中最多有 i=1mmin(k,ci)\sum_{i=1}^m \min(k,c_i) 个不对答案做贡献。

    将糖果总数减去上述表达式的值即为答案。

    解题方法

    使用树状数组对 i=1mmin(k,ci)\sum_{i=1}^m \min(k,c_i) 进行维护。

    复杂度

    时间复杂度

    m=n+qm=n+q

    每次增加、减少、查询时都需要对树状数组进行操作,故单次操作时间复杂度为 O(logm)O(\log m)

    总时间复杂度 O(qlogm)O(q\log m)

    空间复杂度

    m=n+qm=n+q

    最多可能有 O(m)O(m) 个糖果,故树状数组的大小为 O(m)O(m)

    总空间复杂度 O(m)O(m)

    Code

    #include <iostream>
    using namespace std;
    const int N = 2e6 + 10;
    int n, q, op, x;
    int tr[N], buc[N];
    inline void update(int x, int y)
    {
        for (; x < N; x += (x & -x))
            tr[x] += y;
    }
    inline int query(int x)
    {
        int res = 0;
        for (; x; x -= (x & -x))
            res += tr[x];
        return res;
    }
    int main()
    {
        scanf("%d%d", &n, &q);
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &x);
            buc[x]++;
            update(buc[x], 1);
        }
        for (int i = 1; i <= q; i++)
        {
            scanf("%d%d", &op, &x);
            if (op == 3)
            {
                printf("%d\n", n - query(x));
                continue;
            }
            if (op == 1)
            {
                buc[x]++, n++;
                update(buc[x], 1);
                continue;
            }
            update(buc[x], -1);
            buc[x]--, n--;
        }
    }
    
    • 1
      @ 2024-6-30 13:45:39

      T2:催化剂

      思路

      我们能发现,对于每次查找,若有一种糖果类型的糖果数超过 kk,则它一定会积累最终的答案。

      所以,我们需要将每类糖果先给每个小朋友一个,其余的都给一个小朋友即可。

      这样一来,问题就转变为了计算:

      • aa 为糖果数量大于 kk 的糖果类型的总糖果数量有多少。
      • bb 为糖果数量大于 kk 的糖果类型的糖果数量之和。
      • kk 与题意相同。
      ans=bakans=b-a*k

      解题方法

      我们使用树状数组进行维护,一个负责找到超过 kk 的糖果类型有几个,另一个负责找到超过 kk 的糖果类型的总糖果数量有多少。

      简单的说,一个在变动时只是 +1-1 的变化,另一个则是 +x-x 的变化。

      Code

      #include <bits/stdc++.h> 
      #define ll long long
      using namespace std;
      long long n,m;
      long long c[1000011*6],a;
      ll p[1000011];
      ll cs[1000011*6];
      long long lowbit(long long x){
      	return x&(-x);
      }
      long long getsun(long long x){
      	if(!x) return 0;
      	long long sum=0;
      	for(long long i=x;i;i-=lowbit(i)) sum+=c[i];
      	return sum;
      }
      void update(long long x,long long y){
      	if(!x) return ;
      	for(long long i=x;i<=2300000;i+=lowbit(i)){
      		c[i]+=y;
      	} 
      }
      long long lowbits(long long x){
      	return x&(-x);
      }
      long long getsuns(long long x){
      	if(!x) return 0;
      	long long sum=0;
      	for(long long i=x;i;i-=lowbits(i)) sum+=cs[i];
      	return sum;
      }
      void updates(long long x,long long y){
      	if(!x) return ;
      	for(long long i=x;i<=2300000;i+=lowbits(i)){
      		cs[i]+=y;
      	} 
      }
      // 直接粘贴,懒
      int main(){
      	cin >>n >>m;
      	for(long long i=1;i<=n;i++){
      		ll x;
      		scanf("%lld",&x);
      		updates(p[x]+1,p[x]+1);
      		update(p[x]+1,1);
      		updates(p[x],-p[x]);
      		update(p[x],-1);
      		p[x]++;
      	}
      	while(m--){
      		int op;
      		scanf("%d",&op);
      		if(op==1){
      			long long x;
      			scanf("%lld",&x);
      			update(p[x]+1,1);
      			updates(p[x]+1,p[x]+1);
      			update(p[x],-1);
      			updates(p[x],-p[x]);
      			p[x]++;
      		}else if(op==2){
      			ll x;
      			cin >>x;
      			update(p[x],-1);
      			updates(p[x],-p[x]);
      			update(p[x]-1,1);
      			updates(p[x]-1,p[x]-1);
      			p[x]--;
      		}else{
      			long long x;
      			scanf("%lld",&x);
      			cout <<getsuns(2000000)-getsuns(x)-(getsun(2000000)-getsun(x))*x <<'\n';
      		}
      	}
      	return 0;
      }
      
      • 1
        @ 2024-6-29 19:57:33

        S001B. 催化剂

        Statement\mathsf{\color{Thistle}Statement}

        给定一个长度为 nn 的序列 aaii 位置表示有一个种类为 aia_i 的糖果(aia_i 可以重复,即同一个种类有多个糖果),接下来有 qq 次查询:

        • 1 x:表示种类 xx 多了 11 个糖果
        • 2 x:表示种类 xx 少了 11 个糖果
        • 3 k:输出将所有糖果分给 kk 个小朋友的最小愤怒值之和
          • 愤怒值:令第 ii 个小朋友所拥有的第 jj 种糖果的数量为 bi,jb_{i,j},则愤怒值之和为 i=1kj=1nmax(0,bi,j1)\sum_{i=1}^k \sum_{j=1}^n \max(0,b_{i,j}-1)

        Solution\mathsf{\color{Thistle}Solution}

        若第 ii 种糖果的数量为 cic_i,则对于 kk 个小朋友,最小愤怒值为 i=1nmax(0,cik)\sum_{i=1}^n \max(0,c_i-k),其实就是每种糖果平均分给 kk 个小朋友,可以证明这是最优的。比如说:糖果为 2,2,2,2,3,5,52,2,2,2,3,5,522 个小朋友,划分方案如下。

        第一个小朋友 第二个小朋友
        2,2,3,5,52,2,3,5,5 2,2,52,2,5

        那么,问题转化为了动态求出现所有出现次数 k\ge k 的数的和 - k×k\times 出现次数 k\ge k 的数的个数。这个是可以通过平衡树来维护的,不过难写且麻烦。

        不妨考虑将询问离线,对于一次更改,相当于对所有 cnt\le cnt 询问作统一的更改(cntcnt 表示当前更改后该糖果种类的数量)。故,考虑将询问排序,每次更改通过树状数组实现即可。

        Code\mathsf{\color{Thistle}Code}

        signed main() {
        	cin.tie(0);
        	cout.tie(0);
        	ios::sync_with_stdio(0);
        
        	cin >> n >> q;
        	int op, x;
        	for (int i = 1; i <= n; i ++)
        		cin >> x, opr[ ++ m] = {x, 1};
        	while (q -- ) {
        		cin >> op >> x;
        		if (op == 1) opr[ ++ m] = {x, 1};
        		else if (op == 2) opr[ ++ m] = {x, -1};
        		else k ++, qry[k] = {x, k}, upd[m].push_back(k);
        	}
        	sort(qry + 1, qry + 1 + k);
        	for (int i = 1; i <= k; i ++) pos[qry[i].se] = i;
        
        	for (int i = 1; i <= m; i ++) {
        		sum.add(lst[opr[i].fi], -tot[opr[i].fi]);
        		tot[opr[i].fi] += opr[i].se;
        		cnt.add(lst[opr[i].fi], -1);
        		int l = 1, r = k, ans = -1;
        		while (l <= r) {
        			int mid = l + r >> 1;
        			if (qry[mid].fi < tot[opr[i].fi]) l = mid + 1, ans = mid;
        			else r = mid - 1;
        		}
        		if (ans == -1) lst[opr[i].fi] = 0;
        		else lst[opr[i].fi] = k - ans + 1, sum.add(lst[opr[i].fi], tot[opr[i].fi]), cnt.add(lst[opr[i].fi], 1);
        		for (auto v : upd[i])
        			res[v] = sum.sum(k - pos[v] + 1) - qry[pos[v]].fi * cnt.sum(k - pos[v] + 1);
        	}
        
        	for (int i = 1; i <= k; i ++)
        		cout << res[i] << endl;
        
        	return 0;
        }
        
        • 1

        信息

        ID
        3
        时间
        1000ms
        内存
        512MiB
        难度
        4
        标签
        递交数
        465
        已通过
        95
        上传者