【BZOJ3233】找硬幣(AHOI2013)-DP+數論

測試地址:找硬幣
做法: 本題需要用到DP+數論。
假設我們有了構造出了一個合法硬幣序列xx,怎麼計算最少需要使用的硬幣數量?顯然,因爲xkx_kxk1x_{k-1}的倍數,能用大的就應該用大的,那麼對於最大的幣值xkx_k,應該要使用aixk\lfloor\frac{a_i}{x_k}\rfloor個,於是還剩下ai%xka_i\%x_k需要支付,於是對於第二大的幣值xk1x_{k-1}需要使用ai%xkxk1\lfloor\frac{a_i\%x_k}{x_{k-1}}\rfloor個,於是還剩下ai%xk1a_i\% x_{k-1}需要支付…以此類推。於是我們得到答案:
ans=i=1n(aixk+j=1k1ai%xj+1xj)ans=\sum_{i=1}^n(\lfloor\frac{a_i}{x_k}\rfloor+\sum_{j=1}^{k-1}\lfloor\frac{a_i\%x_{j+1}}{x_j}\rfloor)
我們發現xjx_j之間互相產生貢獻,會且只會在相鄰的xjx_jxj+1x_{j+1}之間,以及最後再補上一個aixk\lfloor\frac{a_i}{x_k}\rfloor,也就是說xjx_j的選取是一個可以多階段決策的問題,也就可以用動態規劃解決了。
爲了方便,我們先把aixk\lfloor\frac{a_i}{x_k}\rfloor那個部分略掉(因爲可以O(nmaxai)O(n\cdot \max a_i)算出),令f(p)f(p)xk=px_k=pansans的最小值,那麼有狀態轉移方程:
f(x)=min{f(y)+i=1nai%xy}(yx)f(x)=\min\{f(y)+\sum_{i=1}^n\lfloor\frac{a_i\%x}{y}\rfloor\}(y|x)
邊界爲f(1)=0f(1)=0。這個東西每次轉移都暴力計算是O(n)O(n)的,而鑑於從f(p)f(p)可以轉移到pp的所有的倍數,因此轉移次數是一個調和級數,也就是O(maxailog(maxai))O(\max a_i\cdot \log(\max a_i))次轉移,那麼總的時間複雜度爲O(nmaxailog(maxai))O(n\cdot \max a_i\cdot \log(\max a_i)),爆炸的可能性很大(我沒試過,但應該會掛)。
於是我們需要找到O(1)O(1)轉移的方法,唯一的方式只有預處理出一部分答案,而上面那個i=1nai%xy\sum_{i=1}^n\lfloor\frac{a_i\%x}{y}\rfloor實在有點糾結,我們考慮怎麼把a%xy\lfloor\frac{a\%x}{y}\rfloor轉化成更好算的式子。
直覺上,我們感覺到a%xy=ay%xy\lfloor\frac{a\%x}{y}\rfloor=\lfloor\frac{a}{y}\rfloor\%\frac{x}{y},注意此處yxy|x,這是一個非常重要的性質。簡單證明如下:
根據等式的左邊,可以令a=k1x+r1(0r1<x)a=k_1x+r_1(0\le r_1<x),則r1=a%xr_1=a\% x,而令r1=k2y+r2(0r2<y)r_1=k_2y+r_2(0\le r_2<y),則k2=a%xyk_2=\lfloor\frac{a\%x}{y}\rfloor
a=k1x+k2y+r2a=k_1x+k_2y+r_2代入等式的右邊,那麼ay=k1xy+k2\lfloor\frac{a}{y}\rfloor=k_1\cdot \frac{x}{y}+k_2,而k2=r1y,r1<xk_2=\lfloor\frac{r_1}{y}\rfloor,r_1<x,於是k2<xyk_2<\frac{x}{y},因此k2k_2=ay%xy=\lfloor\frac{a}{y}\rfloor\%\frac{x}{y},等式兩邊都等於k2k_2,所以等式成立。
然後ay%xy=ayayxyxy\lfloor\frac{a}{y}\rfloor\%\frac{x}{y}=\lfloor\frac{a}{y}\rfloor-\Big\lfloor\frac{\lfloor\frac{a}{y}\rfloor}{\frac{x}{y}}\Big\rfloor\cdot \frac{x}{y},因爲xy\frac{x}{y}是正整數,根據一個用過很多次我不想再證的結論amn=amn\Big\lfloor\frac{\lfloor\frac{a}{m}\rfloor}{n}\Big\rfloor=\lfloor\frac{a}{mn}\rfloor,上式就可以寫成ayaxxy\lfloor\frac{a}{y}\rfloor-\lfloor\frac{a}{x}\rfloor\cdot \frac{x}{y}。有了這一結論,帶回一開始的式子中去,則有:
i=1nai%xy=i=1n(aiyaixxy)=(i=1naiy)xy(i=1naix)\sum_{i=1}^n\lfloor\frac{a_i\%x}{y}\rfloor=\sum_{i=1}^n(\lfloor\frac{a_i}{y}\rfloor-\lfloor\frac{a_i}{x}\rfloor\cdot \frac{x}{y})=(\sum_{i=1}^n\lfloor\frac{a_i}{y}\rfloor)-\frac{x}{y}\cdot(\sum_{i=1}^n\lfloor\frac{a_i}{x}\rfloor)
g(x)=i=1naixg(x)=\sum_{i=1}^n\lfloor\frac{a_i}{x}\rfloor,則狀態轉移方程就可以寫成:
f(x)=min{f(y)+g(y)xyg(x)}(yx)f(x)=\min\{f(y)+g(y)-\frac{x}{y}\cdot g(x)\}(y|x)
g(x)g(x)可以O(nmaxai)O(n\cdot \max a_i)預處理,於是轉移就是O(1)O(1)的了,總的複雜度就是O(nmaxai+maxailog(maxai))O(n\cdot \max a_i+\max a_i\cdot \log(\max a_i)),可以輕易地通過此題。
以下是本人代碼:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,a[55],maxa=0,tot[100010],f[100010],ans=inf;
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        maxa=max(maxa,a[i]);
    }
     
    for(int i=1;i<=maxa;i++)
    {
        f[i]=inf;
        tot[i]=0;
        for(int j=1;j<=n;j++)
            tot[i]+=a[j]/i;
    }
    f[1]=0;
     
    for(int i=1;i<=maxa;i++)
    {
        for(int k=2;i*k<=maxa;k++)
            f[i*k]=min(f[i*k],f[i]+tot[i]-tot[i*k]*k);
        ans=min(ans,f[i]+tot[i]);
    }
    printf("%d",ans);
     
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章