Codeforces Contest 1079 problem E The Unbearable Lightness of Weights —— DP

202 篇文章 6 订阅

You have a set of n weights. You know that their masses are a1, a2, …, an grams, but you don’t know which of them has which mass. You can’t distinguish the weights.

However, your friend does know the mass of each weight. You can ask your friend to give you exactly k weights with the total mass m (both parameters k and m are chosen by you), and your friend will point to any valid subset of weights, if it is possible.

You are allowed to make this query only once. Find the maximum possible number of weights you can reveal after this query.

Input
The first line contains a single integer n (1≤n≤100) — the number of weights.

The second line contains n integers a1,a2,…,an (1≤ai≤100) — the masses of the weights.

Output
Print the maximum number of weights you can learn the masses for after making a single query.

Examples
inputCopy
4
1 4 2 2
outputCopy
2
inputCopy
6
1 2 4 4 4 9
outputCopy
2
Note
In the first example we can ask for a subset of two weights with total mass being equal to 4, and the only option is to get {2,2}.

Another way to obtain the same result is to ask for a subset of two weights with the total mass of 5 and get {1,4}. It is easy to see that the two remaining weights have mass of 2 grams each.

In the second example we can ask for a subset of two weights with total mass being 8, and the only answer is {4,4}. We can prove it is not possible to learn masses for three weights in one query, but we won’t put the proof here.

题意:

给你一个数组,你知道这里面是什么数,但是给你的位置不一定正确,你朋友知道,你可以问他一次k个数和为m这k个数的所有位置在哪,问你你最多能知道多少个数的确切位置。

题解:

你只能知道问的数的所有位置,所以问的数只有相同的,否则还是不知道确切位置,因为相同的数无论在哪个位置都一样。那么容易得到当数的种类只有1和2的时候所有数的位置都确定。其他情况,我们可以看出来,我们要找的是,在知道k种和为m的数如果只有一种数组成的情况,那么就可以确定这种数中k个数的位置,比如说例1:1 4 2 2,我们问2个数和为4的所有位置,那么这只有一种可能,和为4的还有一种情况,不过那只有一个数。
例2:1 2 4 4 4 9,2个数和为8有3种情况,不过这都是4组成的,所以并没有什么关系,和为12有两种可能,4 4 4和1 2 9,那么这种就不可以了。
要知道k种和为m的所有情况,一下就想到二维的背包。

for(int i=1;i<=n;i++)
    {
        for(int j=10000;j>=a[i];j--)
        {
            for(int k=0;k<=100;k++)
            {
                if(dp[j-a[i]][k]!=-1)
                {
                    if(dp[j][k+1]==-1)
                        dp[j][k+1]=dp[j-a[i]][k];
                    else
                        dp[j][k+1]+=dp[j-a[i]][k];
                }
            }

        }
    }

这个就是状态转移方程了,这道题不能把j放在外面否则相同的数会重复利用。。吧,所以外面枚举所有数,然后j是背包,k是枚举这个背包里有几个数,由于很多状态是-1,所以不用担心会t。
之后就是遍历每一个数,问i个的情况,如果dp[num][i]== C 这 个 数 的 所 有 可 能 i C^{i}_{这个数的所有可能} Ci 就是这种数的所有排列组合等于dp[num][i]时,就是只有这种数组成的情况。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
map<int,int>mp;
int a[105];
ll jc[105],dp[10005][105];
ll qpow(ll a,ll b)
{
    ll ans=1,ret=a;
    while(b)
    {
        if(b&1)
            ans=(ans*ret)%mod;
        ret=ret*ret%mod;
        b>>=1;
    }
    return ans;
}
void init()
{
    jc[0]=jc[1]=1;
    for(ll i=2;i<=100;i++)
        jc[i]=(jc[i-1]*i)%mod;
}
ll C(int n,int m)
{
    if(n==m)
        return 1;
    return (qpow(jc[m],mod-2)*qpow(jc[n-m],mod-2)%mod)*jc[n]%mod;
}
int main()
{

    init();
    //printf("%lld\n",C(5,5));
    memset(dp,-1,sizeof(dp));
    int n;
    scanf("%d",&n);
    int x;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),mp[a[i]]++;
    if(mp.size()<=2)
        return 0*printf("%d\n",n);
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=10000;j>=a[i];j--)
        {
            for(int k=0;k<=100;k++)
            {
                if(dp[j-a[i]][k]!=-1)
                {
                    if(dp[j][k+1]==-1)
                        dp[j][k+1]=dp[j-a[i]][k];
                    else
                        dp[j][k+1]+=dp[j-a[i]][k];
                }
            }

        }
    }
    int maxn=0;
    for(map<int,int>::iterator it=mp.begin();it!=mp.end();it++)
    {
        int up=it->second;
        for(int i=1;i<=up;i++)
        {
            int num=i*it->first;
            //cout<<it->second<<" "<<i<<" "<<C(it->second,i)<<endl;
            //cout<<num<<" "<<i<<" "<<dp[num][i]<<" "<<it->second<<" "<<i<<" "<<C(it->second,i)<<endl;
            if(dp[num][i]==C(it->second,i))
                maxn=max(maxn,i);
        }
    }
    printf("%d\n",maxn);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值