1. 程式人生 > >codeforces E. 專題競賽 +二分or倒序優化(陣列離散化)

codeforces E. 專題競賽 +二分or倒序優化(陣列離散化)

題目連結:http://codeforces.com/contest/1077/problem/E
題目大意:有 n 個競爭性程式設計問題,每個問題必須屬於某個專題。
現在要舉辦幾場主題比賽。每個比賽中的所有問題應該具有相同的主題,並且所有比賽應該具有兩兩不同的主題。他可能不會使用所有問題。

Polycarp希望連續幾天舉辦比賽,每天一場比賽。Polycarp希望通過以下方式舉辦一系列比賽:

1.每場比賽的問題數量正好是前一次比賽(一天前)的兩倍,第一2.每場比賽可以包含任意數量的問題;
3.應該最大化所有比賽中的問題總數。

在這裡插入圖片描述
專題1有4個問題
專題2有5個問題
專題10有9個問題
最大化問題總數的舉辦方法:2->4->8

思路:但時沒有寫到這道題,但聽說是二分。開始寫的時候準備二分列舉第一天的題目數目,寫完了才發現不對,S=f(x) x與S不是相關關係(正相關or負相關),就是說第一天的題目數目與題目總數沒有相關關係。所以二分不了。但是想到暴力的複雜度不是O(n·n),因為題目的專題和某個專題的最大題目數不可能同時為n,可以暴力試試。T54了。

int er(int mid)/*mid:第一天的題目數*/
{
    long long ans=0;
    for(int i=0;i<cnt&&s[i]!=0;i++)/*s[i]:第i個專題的題目數*/
    {
        if(s[i]>=mid)
        {
            ans+=mid;
            mid*=2;
        }
    }
    sum=max(sum, ans);
}

然後隊友想了一種優化方法:列舉最後一天的題目數,這種方法的可以形成一個可行性剪枝。如果第i天應該有的mid題目數,而實際的題目數是s[i]<mid,那麼就可以不往i-1搜尋因為s[k],k=[0, i)都小於s[i]。
而正向搜尋,s[i]<mid,但是s[k],k=[i, n)有可能有>=mid的,所以必須搜尋完。

優化後:跑了109ms

int er(int mid)/*最後一天的題目數*/
{
    long long ans=0;
    for(int i=cnt-1;i>=0;i--)
    {
        if(s[i]>=mid)
        {
            ans+=mid;
            if(mid%2==0)
                mid/=2;
            else/*mid為奇數則已經是第一天了*/
                break;

        }
        else/*可行性剪枝*/
            break;
    }
    sum=max(sum, ans);
}

當然看了別人的題解,二分也是可以的,也跑了109ms

int er(int mid)
{
    long long ans=0, w=0;
    while(1)
    {
        int p=lower_bound(s+w, s+cnt, mid)-s;/*二分找到第一個大等於mid的天數*/
        if(p==cnt)
            break;

        ans+=mid;
        mid*=2;
        w=p+1;/*移動二分的左端點*/
    }
    sum=max(sum, ans);
}

因為專題的編號<=10^9,為了方便統計數量,就離散化了一下。可以不離散化的,複雜度比較高但是能AC。

思考:雖然前面才做了兩道二分的題,但是這題知道是二分還是沒有想出來,因為之前都是先二分再搜尋O(lgn · n),這題是先搜尋再二分O(n · lgn)。
只有s與i有相關係,那麼就可以二分i找到最優的s。

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

//n原陣列大小 a原陣列中的元素 ls離散化的陣列 cnt離散化後的陣列大小 
int a[200005], ls[200005], n, cnt;
long long sum=0;
int s[200005];

void lsh()/*陣列離散化*/
{
    sort(a, a+n);
    cnt=unique(a, a+n)-a;

    for(int i=0;i<n;i++)
        ls[i]=lower_bound(a, a+cnt, ls[i])-a;

}

/*二分*/
int er(int mid)
{
    long long ans=0, w=0;
    while(1)
    {
        int p=lower_bound(s+w, s+cnt, mid)-s;
        if(p==cnt)
            break;

        ans+=mid;
        mid*=2;
        w=p+1;
    }
    sum=max(sum, ans);
}

/*優化搜尋*/
//int er(int mid)/*最後一天的題目數*/
//{
//    long long ans=0;
//    for(int i=cnt-1;i>=0;i--)
//    {
//        if(s[i]>=mid)
//        {
//            ans+=mid;
//            if(mid%2==0)
//                mid/=2;
//            else/*mid為奇數則已經是第一天了*/
//                break;
//
//        }
//        else/*可行性剪枝*/
//            break;
//    }
//    sum=max(sum, ans);
//}

int main()
{
    fill(s, s+200005, 0);
    scanf("%d",&n);

    for(int i=0;i<n;i++)
        scanf("%d",&a[i]), ls[i]=a[i];

    lsh();

    for(int i=0;i<n;i++)
    {
        s[ls[i]]++;
    }

    sort(s, s+cnt);

    int l=1; int r=*max_element(s, s+n);
    for(int i=l;i<=r;i++)
    {
        er(i);
    }

    cout<<sum<<endl;

    return 0;
}