天天看点

HDU 5884 Sort 二分+优先队列/单调队列Sort

Sort

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Total Submission(s): 4449    Accepted Submission(s): 1098

Problem Description

Recently, Bob has just learnt a naive sorting algorithm: merge sort. Now, Bob receives a task from Alice.

Alice will give Bob N sorted sequences, and the i-th sequence includes ai elements. Bob need to merge all of these sequences. He can write a program, which can merge no more than k sequences in one time. The cost of a merging operation is the sum of the length of these sequences. Unfortunately, Alice allows this program to use no more than T cost. So Bob wants to know the smallest k to make the program complete in time.

Input

The first line of input contains an integer t0, the number of test cases. t0 test cases follow.

For each test case, the first line consists two integers N (2≤N≤100000) and T (∑Ni=1ai<T<231).

In the next line there are N integers a1,a2,a3,...,aN(∀i,0≤ai≤1000).

Output

For each test cases, output the smallest k.

Sample Input

1

5 25

1 2 3 4 5

Sample Output

3

Source

2016 ACM/ICPC Asia Regional Qingdao Online

题意

给出一个n个数的序列,现在要求最小的k满足 每次最多取出k个数进行合并且合并的总代价小于等于t

合并的代价被认为是合并序列的和。

题解

我们考虑二分来解决这个问题

二分一个k

如果我们使用优先队列每次选出k个数进行合并,这是最优的合并方式。

当然这必须满足每次取数恰好为k个。否则需要把多出来的数提前合并了。

通过(n-1) % (k-1) 进行判断

使用优先队列的check需要O(nlogn)时间复杂度。

总共需要O(nlognlogn)的时间复杂度,对于题给数据优化下输入勉强能卡过去。

代码

/**
     * 二分 mid O(logn) check O(nlogn) 时间复杂度 O(nlognlogn) 
     * 100000 * log(100000) * log(100000) = 2e7 
     * 1s TLE
     * 
     * 同时需要注意的是贪心的时候,对于不能每次都保证取k次能取光的时候,我们需要将多余的先优先合并了。
     * 如 (n-1) % (k-1) == 0 表示每次都能保证只取k个。
     * 否则需要先将 (n-1) % (k-1) + 1 个先合并了。
     * 为什么是这样的呢,我们这么来考虑这个问题,每次取k个合并完后又要放进一个,相当于每次只取走了k-1个。
     * 同时合并只剩下1个的时候停止,于是是(n-1)
     * 综上 考虑 (n-1) % (k-1) 即可 
     * 
     * 考虑单调队列优化这个问题
     *
     */
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    priority_queue<ll,vector<ll>,greater<ll> > qu;
    const int maxn = 1e5+10;
    ll arr[maxn],t;
    int n;
    namespace IO
    {
        template <typename T>
        inline bool scan_d (T &ret)
        {
            char c;
            int sgn;
            if (c = getchar(), c == EOF)return false; //EOF
            while (c != '-' && (c < '0' || c > '9') )
                if((c = getchar()) == EOF) return false;
            sgn = (c == '-') ? -1 : 1;
            ret = (c == '-') ? 0 : (c - '0');
            while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
            ret *= sgn;
            return true;
        }

        template<typename T>
        void print(T x)
        {
            static char s[33], *s1;
            s1 = s;
            if (!x) *s1++ = '0';
            if (x < 0) putchar('-'), x = -x;
            while(x) *s1++ = (x % 10 + '0'), x /= 10;
            while(s1-- != s) putchar(*s1);
        }

        template<typename T>
        void println(T x)
        {
            print(x);
            putchar('\n');
        }
    };
    bool check(int k) 
    {
        ll ans = 0;
        while(!qu.empty()) qu.pop();
        for(int i=1;i<=n;i++) qu.push(arr[i]);
        int ma = (n-1) % (k-1);
        if(ma) {
            ll temp = 0;
            for(int i=1;i<=ma+1 && !qu.empty();i++) temp += qu.top(),qu.pop();
            ans += temp;
            qu.push(temp);
        }
        while(!qu.empty()) {
            ll temp = 0;
            for(int i=1;i<=k && !qu.empty();i++) temp += qu.top(),qu.pop();
            ans += temp;
            if(!qu.empty()) qu.push(temp);
            //if(ans > t) return false;
        }
        return ans <= t;
    }
    int main()
    {
        int caset;IO::scan_d(caset);
        while(caset--) {
            IO::scan_d(n);IO::scan_d(t);
            for(int i=1;i<=n;i++) IO::scan_d(arr[i]);
            std::sort(arr+1,arr+1+n); //先排序,减少建堆时间
            int l = 2,r = n;
            while(l <= r) {
                int mid = (l+r)>>1;
                if(check(mid)) r = mid - 1;
                else l = mid + 1;
            }
            IO::println(l);
        }
        return 0;
    }
           

HDU 5884 Sort 二分+优先队列/单调队列Sort

题解

由于合并后的生成的数具有单调性,那么我们采用单调队列的思路,我们用两个队列,一个是原数列,另一个存放合并和生成的数。

每次从两个队列取小合并。check()时间复杂度O(n) 总时间复杂度O(nlogn)

代码

/**
 * 前面说到优先队列的做法是 O(nlognlogn)的显然这里明显是会TLE的
 * 而单调队列在这里做的事情就是优化掉一个log
 * 使其变成O(nlogn)即在O(n)的时间进行check
 * 
 * 即设立两个队列 一个是排好序的原数列,另一个是合并后的数的序列.(因为合并生成的数满足单调递增性)
 * 那么我们只需要取数时,从两个序列取小取出即可.
 */
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
// struct Queue
// {
//     int a[maxn],head,tail;
//     Queue() {
//         head = tail = 0;
//     }
//     void init() {
//         head = tail = 0;
//     }
//     bool empty() {
//         return head == tail;
//     }
//     int front() {
//         return a[tail-1];
//     }
//     void pop() {
//         head++;
//     }
//     void push(int x) {
//         a[tail++] = x;
//     }
// }qu[2];
ll arr[maxn];
int n;ll t;
bool check(int mid) {
    ll ans = 0;
    // qu[0].init();qu[1].init();
    queue<int> qu[2];
    for(int i=1;i<=n;i++) qu[0].push(arr[i]);
    int ma = (n-1) % (mid-1);
    if(ma) {
        ll temp = 0;
        for(int i=1;i<=ma+1 && !qu[0].empty();i++) temp += qu[0].front(),qu[0].pop();
        ans += temp;
        qu[1].push(temp); 
    }
    while(1) {
        if(qu[0].empty() && qu[1].empty()) break;
        ll temp = 0;
        for(int i=1;i<=mid;i++) {
            if(!qu[0].empty()) {
                if(!qu[1].empty()) {
                    if(qu[0].front() < qu[1].front()) {
                        temp += qu[0].front();qu[0].pop();
                    }
                    else {
                        temp += qu[1].front();qu[1].pop();
                    }
                }
                else {
                    temp += qu[0].front();qu[0].pop();
                }
            }
            else {
                temp += qu[1].front();qu[1].pop();
            }
        }
        ans += temp;
        if(qu[0].empty() && qu[1].empty()) break;
        qu[1].push(temp);
        if(ans > t) return false;
    }
    return ans <= t;
}
int main()
{
    int caset;scanf("%d",&caset);
    while(caset--)
    {
        scanf("%d%lld",&n,&t);
        for(int i=1;i<=n;i++) scanf("%lld",&arr[i]);
        std::sort(arr+1,arr+n+1);
        int l=2,r=n;
        while(l <= r) 
        {
            int mid = (l+r)>>1;
            if(check(mid)) r = mid - 1;
            else l = mid + 1;    
        }
        printf("%d\n",l);
    }
    return 0;
}
           
HDU 5884 Sort 二分+优先队列/单调队列Sort

明显时间优化了不少。