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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值