【dfs+dp+桶排序去重】洛谷P1441 砝碼稱重
阿新 • • 發佈:2019-02-16
大致思路:
首先看一下這道題:https://blog.csdn.net/m0_38033475/article/details/80380467
你對比一下會發現,都是求“方案數”的,其實都是用“01揹包”來做的:對本題來說,f[j]的值表示重量為j時的方案數(每個方案的重量和要不一樣)。但是你會發現其實兩題是不一樣的,是在於題意不同,本題求的不是單純的“方案數”,而是不同的重量和有多少種,然而,顯然,有可能多個方案擁有一樣的重量和,所以不能單純以求得的方案數拿來當重量和。
所以就要思考,怎樣的方案是算重複的?它所得的重量和要相等,而重量和相等則意味著——砝碼的重量組合其實是一樣的!而我已經保證用dfs去依次加砝碼了,為什麼會出現重量組合相同的情況?其實根本原因就是因為重量其實是無序排列的,比如“1 2 3”和“1 3 2”,或者出現“1 1 1”和“1 1 1”(所以你即使如果先把資料給sort了,也需要在迴圈中去比較不能和上一個放入的相同),就算有相同元素都不行(如“1 1 3”和“1 2 2”的可能)。
anyway,記住一句總結:用“單調性”解決“組合重複性”。
使資料單調在這裡有兩種方法:
- 在讀入後加入一個Sort。(還是需要遍歷每一個砝碼並且做處理不能和上一個相等)
- 用桶儲存資料。(只需要遍歷資料的範圍(本題砝碼重量最大才100,因此完全可以大大優化))
其實都是排序啦。
考慮用桶儲存資料
優點:在讀入之後沒有額外的複雜度
缺點:可能需要更多的時間來遍歷到所有資料
再看看桶的大小, a_{i}ai <=100,缺點完全可以忽略,用桶儲存可行!
這是我第一次接觸桶排序,小小地用自己的話總結一下:
首先是a陣列,a[v]表示v這個資料值有多少個。
其實就是輸入資料的時候,比如資料為v,則使a[v]++。把資料中的最小值和最大值記錄一下,那麼我在dfs遍歷的時候就可以“單調”選取,程式碼如下:
for(int i=last;i<=max_nums;i++)//這裡很重要。保證了pack陣列的單調性,進而使得其不重複 //last是當前dfs遍歷到的資料大小,max_nums是最大資料
{
if (a[i]>0)
{
a[i]--;
pack[x]=i;
dfs(x+1,i);
a[i]++;
}
AC程式碼(via Feather_sea)#include<bits/stdc++.h> using namespace std; int a[107],pack[21],ans=0,n,m,max_nums=0,min_nums=999,num; bool f[3000]; void dp() { memset(f,0,sizeof(f));f[0]=1; int sum=0,tot=0; for(int i=1;i<=num;i++) sum+=pack[i]; for(int i=1;i<=num;i++) { for(int j=sum;j>=pack[i];j--) f[j]=f[j]+f[j-pack[i]]; } for(int i=1;i<=sum;i++) { if (f[i]) tot++; } ans=max(ans,tot); } void dfs(int x,int last) { if (x>num) { dp(); return; } for(int i=last;i<=max_nums;i++)//這裡很重要。保證了pack陣列的單調性,進而使得其不重複 { if (a[i]>0) { a[i]--; pack[x]=i; dfs(x+1,i); a[i]++; } } } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { int v; scanf("%d",&v); max_nums=max(max_nums,v); min_nums=min(min_nums,v); a[v]++; } num=n-m;//留下n-m個砝碼 dfs(1,min_nums);//從最小值進行搜尋 printf("%d",ans); return 0; }