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;
}