Codeforces-1659 D: Reverse Sort Sum 【双指针、排序】

Codeforces-1659 D: Reverse Sort Sum

题目链接:Codeforces-1659 D

题目

题目截图

在这里插入图片描述

样例描述

在这里插入图片描述

题目大意

  给定一个只包含 n n n 个值为 0 0 0 1 1 1 的数组 A A A
  设函数 f ( k , A ) f(k, A) f(k,A) 代表将数组 A A A 的前 k k k 个元素进行排序,剩余 n − k n-k nk 个元素位置不变的操作。设 B 1 , B 2 , ⋯ B n B_1,B_2,\cdots B_n B1,B2,Bn 代表 f ( 1 , A ) , f ( 2 , A ) , ⋯   , f ( n , A ) f(1,A),f(2,A),\cdots,f(n, A) f(1,A),f(2,A),,f(n,A)。又设 C C C B 1 , B 2 , ⋯   , B n B_1,B_2,\cdots,B_n B1,B2,,Bn 的对应位置相加。现给出 C C C,问原始的 A A A 是什么。(题目保证 A A A 存在)

题目解析

  我们很容易注意到, A n A_n An 的值非常好确定,因为若其为 1 1 1,那么 A n = n A_n=n An=n,若其为 0 0 0,那么 A n = 1 / 0 A_n = 1/0 An=1/0(只有在全为 0 0 0 时, A n A_n An 0 0 0)。那么,若我们能够忽略 A n A_n An 带来的影响,我们就可以用类似的方法知道 A n − 1 A_{n-1} An1 的值了。可惜,我们不能直接忽略 A n A_n An 的影响,因为我们并不知道 B n B_n Bn B n − 1 B_{n-1} Bn1 位置上是 0 0 0 还是 1 1 1
  同时,我们应该注意到,由于操作了 n n n 次, A A A 中每个 1 1 1 元素都会在 B 1 , B 2 , ⋯ B n B_1,B_2,\cdots B_n B1,B2,Bn 中出现 n n n 次,也就是说,若设 C C C 的所有元素和为 s u m sum sum,那么 A A A 1 1 1 的数量应该为 k = s u m n k=\frac{sum}{n} k=nsum
  现在我们知道了 1 1 1 的数量,我们来观察下 f f f 操作会产生一个怎样的序列。首先,我们将 B B B 序列依次横着放好(如上图 Note 中的示例)。若我们以对角线分开看这个矩阵,我们会发现第一个显然的性质,即每一行直到对角线, 1 1 1 是连续的一段(因为已经排好序)。
  那么一个直观的问题变成,我们能不能知道某一行,从对角线开始,这个 1 1 1 会向左延续到哪里呢?可以!例如,在最后一行,我们知道最左边 1 1 1 的位置是 n − k + 1 n-k+1 nk+1。更一般地,若 n − 1 n-1 n1 位元素是 1 1 1,那么其上一行 n − 2 n-2 n2 行最左边 1 1 1 的位置将会不变(因为 1 1 1 的数量减少了一个),若 n − 1 n-1 n1 位元素是 0 0 0,显然,由于 1 1 1 的数量没变,那么对应左区间将会向左推进一位。这样,我们知道了每一行应该更新的区间,可以用树状数组或者线段树进行对应区间 C l ⋯ i C_{l\cdots i} Cli 1 1 1,以此消除掉删除当前最后一个元素的影响,在判断时单点查询新的 C i C_i Ci,这样我们就能根据第一段的方法判断每一个 A i A_i Ai 具体的值了。
  但我们更近一步观察,会发现整个示例有很多以对角线为斜边的等腰直角三角形。这是因为若我们竖着看,当已经排序过的位置,某一行值开始为 0 0 0 时,之后的每一行,其值都为 0 0 0(因为从上向下,每多一个 1 1 1,完成排序的位置数量也会多一个,每多一个 0 0 0 1 1 1 的左端点会向右移),同时,我们知道若横着连续的 1 1 1 和对应竖着的应该是等长的(因为每向上一行,左端点最多向左一个单位)。那么,我们设某一左端点位置 l l l 对角线之下的 1 1 1 的个数为 h [ l ] h[l] h[l],当遍历到位置 i i i 时,我们可以直接得到当前连续 1 1 1 的左端点(通过 A i A_i Ai 0 / 1 0/1 0/1 来判断当前的 l l l 该向左还是不变)。这样,我们就能在倒着遍历的同时,顺带知道该位置对角线下 1 1 1 的数量是多少了。于是我们可以直接根据 C i − h i C_i-h_i Cihi 是否等于 i i i 来进行判断 A i A_i Ai 的值。(原理类似于直线段右端沿着和它呈 45 ° 45° 45° 的斜线,位置为 0 0 0 向左上平移,位置为 1 1 1 则直接向上,且线段长度减 1 1 1
  需要注意的是,在求和处需要做 long long 处理,以及,有可能 A i A_i Ai 的前几位都是 0 0 0,到表现为 l > i l > i l>i(没有从对角线向左延续的 1 1 1)。

Code

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int maxn = 2e5 + 7;
int c[maxn], ans[maxn], h[maxn];

int main() {
    int t, n, k, shift, l;
    LL sum = 0;
    cin >> t;
    while(t--) {
        sum = 0;
        cin >> n;
        for(int i=1; i<=n; ++i)
            cin >> c[i], sum += c[i], ans[i] = 0;
        k = sum / n, l = n - k + 1;
        for(int i=l; i<=n; ++i) h[i] = n - i;
        for(int i=n; i>=1 && l<=i; --i) {
            shift = c[i] - h[i];
            if(shift == i) ans[i] = 1; 
            else {
                ans[i] = 0, --l;
                h[l] = i - l - 1;
            };
        }
        for(int i=1; i<=n; ++i)
            cout << ans[i] << (i==n?'\n':' ');
    }    
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值