2 条题解

  • 2
    @ 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
      @ 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

      信息

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