1. 程式人生 > >【dfs+dp+桶排序去重】洛谷P1441 砝碼稱重

【dfs+dp+桶排序去重】洛谷P1441 砝碼稱重

大致思路

首先看一下這道題: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,記住一句總結:用“單調性”解決“組合重複性”

使資料單調在這裡有兩種方法:

  1. 在讀入後加入一個Sort。(還是需要遍歷每一個砝碼並且做處理不能和上一個相等)
  2. 用桶儲存資料。(只需要遍歷資料的範圍(本題砝碼重量最大才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;
}