2018.1.27 T2 尤格萨隆的合并石子大作战

版权声明:文章纯属版主手敲,请同学们尊重版主的知识产权。 https://blog.csdn.net/GGN_2015/article/details/79184835

今天的题好像并没有那么难,可惜就两个人得分了。


【题面】

题目描述1
题目描述2
数据范围


【思路】

显然是一个多岔哈弗曼树,我们知道哈弗曼树的时间复杂度最优可以做到O(n),但是这次我们显然要做n次哈弗曼树,理论上的时间复杂度是O(n2)的,不过看到数据范围感觉好像只有O(nlogn)才能过。

引理:

limn+i=1n1i=lnn+γ

其中:γ0.57721566490153286060651209...

也就是有:

limn+i=1nni=nlnn+γn=O(nlogn)

也就是说,如果我们能把k岔哈弗曼树的时间复杂度优化到O(nk)那么就有总时间复杂度为O(nlogn)

单调队列法,考虑到单调队列里的元素最多只有O(nk)个,那么我们可以先假定只在原序列中取k个元素,然后把这k个元素中的最后一个元素与队首比较。如果队首更优,那就把队首统计到本次合并的答案中,这样的话我们还需要在原序列中回退一个元素…依此类推。

为什么这样找时间复杂度能更优呢?因为原序列永远不变,所以我们最开始可以先把它排个序,然后求一下前缀和,用以维护区间和。这个预处理时间复杂度是O(nlogn)的(sort排序+前缀和)。然后假定在某种状态下单调队列里没有元素,那么我们只需要取原序列中的前k个元素。因为我们预处理了前缀和,我们可以O(1)求出前k个元素的和。

因为我们最多执行O(nk)此操作就能把所有元素合并成一堆,所以单调队列中的元素最多只会有O(nk)个,我们最多回退O(nk)次。制造一个k岔哈弗曼树的时间复杂度就是O(nk)的。

因此,程序的总时间复杂度就如引理中所叙述的O(nlogn)

另外要注意的一点是,k岔哈弗曼树在运行之前需要对原数据进行补“0”。k岔哈弗曼树每次减少(k-1)个元素,那么我们就得把元素个数补成刚好大于等于原长度的第一个除(k-1)余1的数。


【代码】

#include <queue>
#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 200000 + 10, inf = 0x7f7f7f7f;
bool icmp(int a,int b){return a < b;}

class array_item{
    int a[maxn], pre[maxn], n;
    inline int getint(){
        int ans=0;bool flag=0;char c=getchar();
        while(!isdigit(c)){flag|=c=='-';c=getchar();}
        while( isdigit(c)){ans=ans*10+c-'0';c=getchar();}
        return flag?-ans:ans;
    }
    public:
        inline int operator[](int idx){
            if(idx > n || idx <= 0) return inf;
            return a[idx];
        }
        inline int sum(int L,int R){
            return pre[R] - pre[L-1];
        }
        inline void input(const int n){
            this->n = n;
            pre[0]=0;
            for(int i=1; i<=n; i++) a[i]   = getint();
            sort(a+1, a+n+1, icmp);
            for(int i=1; i<=n; i++) pre[i] = pre[i-1] + a[i];
        }
}array;

class minor{
    int ahead,k,n,ans;
    queue<int>Q;
    inline int size(){return n - ahead + 1 + Q.size();}
    inline int run(){
        int lst = ahead + k - 1;
        int sum = 0;
        while(!Q.empty() && array[lst] > Q.front()){
            sum += Q.front(); Q.pop(); lst--;
        }
        sum += array.sum(ahead,lst); ahead = lst + 1;
        Q.push(sum); ans += sum;
        return sum;
    }
    public:
        void init(const int k,const int n){
            this->k = k; this->n = n;
            int zcnt =((k-1) - n%(k-1) + 1)%(k-1); //put zero
            int first = k - zcnt; ahead = first + 1;
            if(first){
                ans = array.sum(1,first);
                Q.push(ans);
            }else ans = 0;
        }
        int solve(){
            while(size()>1) run();
            while(!Q.empty()) Q.pop();
            return ans;
        }
}launcher;

int main(){
    freopen("stone.in", "r",stdin);
    freopen("stone.out","w",stdout);
    int n; scanf("%d", &n);
    array.input(n);
    for(int i=2; i<=n; i++){
        launcher.init(i,n);
        int ans=launcher.solve();
        printf("%d\n",ans);
    }
    return 0;
}

这道题我一遍过样例,然后就A了,非常开心。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页