2019HDU多校賽 第九場 HDU 6682 Rikka with Mista(折半搜索 + 組合計數 + 排序)

 

 

大致題意:給你最多40個數字,你可以任意的取數字,問所有的取法下,所有取的數字的和中4的個數的和是多少。

40個數字,其實就是折半搜索,但是好像有一個聽起來好像挺厲害的名字meet in middle。具體來說,數字分爲兩半,然後分別求出兩部分可以構成的所有的和。這樣兩部分分別最多有2^20約100W種數字,然後我們考慮這兩部分求和。

由於是計算和種4出現的次數,所以我們考慮按位來統計。對於某一位i,我們考慮對兩部分所有可能的子集和按照後i位排序,也即模10^(i+1)排序。然後,考慮如果第i位爲4,那麼滿足的條件就是第一部分的某個數字加上第二部分的某個數字的和模10^(i+1)一定介於4*10^i和5*10^i之間。由於我們已經排了序,所以對於第一部分的某個數字,他可行的的數字一定是兩個連續的區間(分別對應和會進位和不進位)。而且,隨着第一部分的數字越來越大,這兩個區間是單調的,所以這個過程我們可以用指針模擬,總的複雜度就是O(其中一部分子集個數)也即O(2^(n/2))。

我們考慮一下目前爲止的複雜度,由於是按位統計,所以是有最多10位,然後每位需要進行一個排序和統計,總的複雜度就是O(10*2^(n/2)log(2^(n/2))),直接這麼做是會超時的。可以看到瓶頸在於排序。這裏爲了優化排序,我們考慮另外有兩種排序方法可以優化。

首先是歸併排序,我們考慮從高位開始往下,首先按照模10^10的值,這個沒辦法只能直接排序。然後到下一位的時候,是模10^9排序。你會發現,對於最高位相同的數字來說,他們已經相當於是按照模10^9排了序,而最高爲最多隻有10種取法。那麼這就意味着,這百萬個數字,可以分爲最多10段,每一段內都是已經按照10^9排好序了的。那麼,這正好就可以利用歸併排序,把這最多10個有序段合併成一個,複雜度就是O(10*10*2^(n/2)))。

另一種是歸併排序,這次考慮從小的開始做。初始時相當於已經按照最低爲的下一位(0)排好序,現在多了一位,我們按照原本的排列順序,從前往後,如果下一位是1的,放到1的位置,2的放到2的位置,以此類推,最多有10個位置。然後從0開始到9,按照先入先出的順序一個個取出來,當前的順序就是按照模下一位排序的順序。爲什麼呢?按照0到9的順序出是因爲最高位的比較,而按照先入先出則是考慮了之前的排序。這個思想有點像後綴數組的思想了。如此複雜度會比歸併小一些,複雜度是O(10*2^(n/2))),但是由於需要知道某一位是多少,所以要用上取模和整除運算,加上常數實際複雜度二者相近。

具體見代碼:

歸併排序:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define eps 1e-4
#define pi 3.141592653589793
#define P 1000000007
#define LL long long
#define LDB long double
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<"      :   "<<x<<endl
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%lld%lld",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

const int N = 1<<20;

int a[N],A[N],B[N],C[N],t1,t2,n,m;
int pw[10],L[10],R[10];

inline void MergeSort(int *A,int t,int i)
{
    int tt=0; L[0]=0;
    for(int j=0;j<t;j++)
        if (j&&A[j]/pw[i]!=A[j-1]/pw[i]) R[tt++]=j-1,L[tt]=j;
    for(int j=0;j<t;j++) A[j]%=pw[i]; R[tt++]=t-1;
    if (tt==1) return;
    for(int j=0;j<t;j++)
    {
        int l=2e9,pos;
        for(int k=0;k<tt;k++)
            if (L[k]<=R[k]&&A[L[k]]<l) l=A[L[pos=k]];
        C[j]=l; L[pos]++;
    }
    for(int j=0;j<t;j++) A[j]=C[j];
}

int main()
{
    int T; sc(T);
    pw[0]=1;
    for(int i=1;i<10;i++)
        pw[i]=pw[i-1]*10;
    while(T--)
    {
        sc(n);
        for(int i=0;i<n;i++) sc(a[i]);
        m=n/2; n=n-n/2; t1=t2=0;
        int up1=1<<m,up2=1<<n;
        for(int i=0;i<up1;i++)
        {
            int sum1=0,sum2=0;
            for(int j=0;j<m;j++)
                if ((i>>j)&1) sum1+=a[j],sum2+=a[j+m];
            A[t1++]=sum1; B[t2++]=sum2;
        }
        for(int i=up1;i<up2;i++)
        {
            int sum=0;
            for(int j=0;j<n;j++)
                if ((i>>j)&1) sum+=a[j+m];
            B[t2++]=sum;
        }
        LL ans=0;
        sort(A,A+t1); sort(B,B+t2);
        // puts("sdf");
        for(int i=9;i;i--)
        {
            int up=5*pw[i-1],low=4*pw[i-1];
            for(int j=0,k=t2-1,l=t2-1,kk=t2-1,ll=t2-1;j<t1;j++)
            {
                while(~k&&A[j]+B[k]>=up) k--;
                while(~l&&A[j]+B[l]>=low) l--;
                while(~kk&&A[j]+B[kk]>=up+pw[i]) kk--;
                while(~ll&&A[j]+B[ll]>=low+pw[i]) ll--;
                ans+=k-l+kk-ll;
            }
            if (i==1) break;
            MergeSort(A,t1,i-1);
            MergeSort(B,t2,i-1);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

基數排序:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define eps 1e-4
#define pi 3.141592653589793
#define P 1000000007
#define LL long long
#define LDB long double
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define bug(x) cerr<<#x<<"      :   "<<x<<endl
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%lld%lld",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

const int N = 1<<20;

int a[N],A[N],B[N],t1,t2,n,m;
vector<int> pos[10];
int pw[10];

inline void Sort(int *A,int t,int i)
{
    memset(pos,0,sizeof(pos));
    for(int j=0;j<t;j++)
        pos[A[j]/pw[i]%10].pb(A[j]);
    t=0;
    for(int i=0;i<10;i++)
        for(int j=0;j<pos[i].size();j++)
            A[t++]=pos[i][j];
}

int main()
{
    int T; sc(T);
    pw[0]=1;
    for(int i=1;i<10;i++)
        pw[i]=pw[i-1]*10;
    while(T--)
    {
        sc(n);
        for(int i=0;i<n;i++) sc(a[i]);
        m=n/2; n=n-n/2; t1=t2=0;
        int up1=1<<m,up2=1<<n;
        for(int i=0;i<up1;i++)
        {
            int sum1=0,sum2=0;
            for(int j=0;j<m;j++)
                if ((i>>j)&1) sum1+=a[j],sum2+=a[j+m];
            A[t1++]=sum1; B[t2++]=sum2;
        }
        for(int i=up1;i<up2;i++)
        {
            int sum=0;
            for(int j=0;j<n;j++)
                if ((i>>j)&1) sum+=a[j+m];
            B[t2++]=sum;
        }
        LL ans=0;
        // puts("sdf");
        for(int i=1;i<=9;i++)
        {
            Sort(A,t1,i-1); Sort(B,t2,i-1);
            int up=5*pw[i-1],low=4*pw[i-1];
            int upp=up+pw[i],loww=low+pw[i];
            for(int j=0,k=t2-1,l=t2-1,kk=t2-1,ll=t2-1;j<t1;j++)
            {
                while(~k&&A[j]%pw[i]+B[k]%pw[i]>=up) k--;
                while(~l&&A[j]%pw[i]+B[l]%pw[i]>=low) l--;
                while(~kk&&A[j]%pw[i]+B[kk]%pw[i]>=upp) kk--;
                while(~ll&&A[j]%pw[i]+B[ll]%pw[i]>=loww) ll--;
                ans+=k-l+kk-ll;
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章