题目链接: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;
}