• #415 Div2 Problem C Do you want a data? (math && 前后缀和 && 快速幂)


    题意: 首先定义集合的F值为  这个集合里面最大值和最小值的差。 现给出一个拥有n个数的集合(没有相同的元素), 要求求出这个集合内所有子集的F的值的和。例如: {4、7}这个集合里面有子集{4}、{7}、{4, 7}, 则这些子集的F值分别为4-4=0、7-7=0、7-4=3, 所以最后的结果就是0+0+3 = 3!

    以下分析引用至 : http://blog.csdn.net/dragon60066/article/details/72599167

    分析: 不难想到要先使数组升序方便计算和思考, 然后观察发现如果选定任意一头一尾两个元素, 假如为ai、aj (ai<aj) 那这个两个元素之间围起来的区间可以看成ai....aj这样的数集, 且这集合里面所有子集贡献的F值都等于 aj - ai, 那这个区域包含了多少个这样的F值的集合呢?用笔简单列出表后不难发现规律, 这个和为个(需要用到快速幂), 至于具体为什么, 可以看自己多加思考一下且在参考博客有说明。当时想到这个后马上打出了枚举程序

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int mod = 1e9+7;
    LL arr[300005];
    LL mi[300005];
    LL qmod(LL a, LL b, LL c)
    {
        LL ans = 1;
        while(b){
            if(b&1) ans = (ans*a)%c;
            a = (a*a)%c;
            b >>= 1;
        }
        return ans;
    }
    int main(void)
    {
        int n;
        scanf("%d", &n);
        for(int i=0; i<n; i++){
            scanf("%lld", &arr[i]);
        }
        sort(arr, arr+n);
        LL sum = 0;
        LL temp;
        for(int i=0; i<n; i++){
            mi[i] = qmod(2, i, mod);
        }
        for(int i=0; i<n-1; i++){
            for(int j=n-1; j>i; j--){
                LL sub = arr[j] - arr[i];
                if(j-i-1!=0)temp = mi[j-i-1];
                else temp = 1;
                temp%=mod;
                sum += (((temp%mod)*(sub%mod))%mod)%mod;
                sum%=mod;
            }
        }
        printf("%lld
    ", sum);
        return 0;
    }
    View Code

    然后就可以顺利超时了, 因为像我这样做的复杂度是O(n^2)!需要优化!于是上网搜索看到了前缀和的解法, 甚是巧妙, 而且貌似榜上大佬大多也是这样做!

    引用一下——>进一步思考,对于每个i, j,取值可能有,第一种前面系数是(a[2]-a[1]+a[3]-a[2]+...+a[n]-a[n-1])=a[n]-a[1],同理可以发现第二种是a[n]+a[n-1]-a[1]-a[2]......,那么就推出来了:

    那这个直观一点的话到底是在干什么玩意呢?看下面根据描述打出来的表

    看到规律了吧!发现系数是对称的, 那我们根据系数的规律枚举从0到n-2的2次方就行了!不过细想的话, 虽然是找到了系数的规律, 但是好像还是不好实现, 参考别人的程序看到别人使用了前后缀和的做法, 巧妙的完成了依据上面规律进行的枚举操作, 具体的话不好说, 看一下程序便知!

    以下为拙略表达, 可以跳过: 不过我还是说一下, 按我的理解就是从前面到中间的系数(以n=6为例, 枚举从2^0到2^2的系数)倒是不难枚举出来, 但是后面的就略微麻烦了, 解决方法——>定义前后缀和(例:sumf[i]是从1到i的前缀和, sumb[i]是n-1到i的后缀和), 那还是以n=6为例, 2^0到2^2的系数分别可以用sumb[6] - sum[1] 、 sumb[5]-sumf[2]、 sumb[4]-sumf[3]来解决, 后面的2^3 可以 sumb[3] - sumf[4], 停!有没有发现 计算这个差的过程中实际就是 a6+a5+a4+a4-a4-a3-a1-a2! a3和a4巧妙的被约去了, 2^4的计算也是同样道理!类似于回文?好思想!

    #include<bits/stdc++.h>
    #define LL long long
    using namespace std;
    const int mod = 1e9+7;
    const int maxn = 300005;
    LL arr[maxn], sumf[maxn], sumb[maxn], mi[maxn];
    LL quick_mod(LL a, LL b, LL c)
    {
        LL ans = 1;
        while(b){
            if(b&1) ans = (ans*a)%c;
            a = (a*a)%c;
            b >>= 1;
        }
        return ans;
    }
    int main(void)
    {
        int n;
        scanf("%d", &n);
        for(int i=1; i<=n; i++)
            scanf("%d", &arr[i]);
        sort(arr+1, arr+1+n);///使序列单调
        sumf[0] = sumb[n+1] = 0;
        mi[0] = 1;
        for(LL i=1; i<n-1; i++)
            mi[i] = quick_mod(2, i, mod);///二的次方数组
        for(int i=1; i<=n; i++)
            sumf[i] = sumf[i-1] + arr[i];///前缀和
        for(int i=n; i>=1; i--)
            sumb[i] = sumb[i+1] + arr[i];///后缀和
        LL ans = 0;
        for(int i=1; i<n; i++){ //实际就是0 -- n-2 次循环
            LL temp = (-sumf[i] + sumb[n-i+1])%mod;
            ans = (ans%mod + (temp * mi[i-1])%mod)%mod;
        }
        printf("%lld
    ", ans);
        return 0;
    }
    View Code
  • 相关阅读:
    Docker入门之docker-compose [转]
    防火墙和iptables
    MariaDB/MySQL备份和恢复(三):xtrabackup用法和原理详述
    Veritas NetBackup™ for VMware 介绍 (NBU版本 8.2)
    RMAN备份恢复所需要了解的Oracle术语
    mac 下使用命令行打开项目
    ORACLE 11g RAC-RAC DG Duplicate 搭建(生产操作文档)
    15-vuex
    14-Promise
    13-vue-router2
  • 原文地址:https://www.cnblogs.com/qwertiLH/p/6901477.html
Copyright © 2020-2023  润新知