(惠看懂的)牛客-锁(状压dp)

题目链接

106号房间共有n名居民, 他们每人有一个重要度。房间的门上可以装若干把锁。假设共有k把锁,命名为1到k。每把锁有一种对应的钥匙,也用1到k表示。钥匙可以复制并发给任意多个居民。每个106房间的居民持有若干钥匙,也就是1到k的一个子集。如果几名居民的钥匙的并集是1到k,即他们拥有全部锁的对应钥匙,他们都在场时就能打开房门。新的陆战协定规定,一组居民都在场时能打开房门当且仅当他们的重要度加起来至少为m。问至少需要给106号房间装多少把锁。即,求最小的k,使得可以适当地给居民们每人若干钥匙(即一个1到k的子集),使得任意重要度之和小于m的居民集合持有的钥匙的并集不是1到k,而任意重要度之和大于等于m的居民集合持有的钥匙的并集是1到k。

输入描述:
第一行两个整数n和m,0<n<21,0<m<1000000001。
第二行n个整数表示居民们的重要度。
重要度在[1,1000000000]之间。
输出描述:
一个整数表示最少需要多少把锁。

题解
这题确实不好想,小编也看了很多博客,想了很久才想出来,对于才学状态压缩dp的我们这题不是很适合刷,虽然是代码是模板题但要想出来却不容易,性价比不高。那么我先说说做法:我们找出一个集合,他的重要度小于m,然后加上任意一个人后,他的总要度大于等于m,最终答案就是这种集合的个数。为什么了?我们假设这样的集合缺少一把钥匙,再加上另一个人后重要度大于m,故另一个人必须有这把钥匙,所以就把这个钥匙分给这些人(也就是除这个集合外的所有人,因为加他们后重要值都大于m),为什么要这样了,假设一个集合(x)加上某个集合(y)里的一个人后重要值大于m(还是假设只缺少一把钥匙),这时可能有人觉得可以把钥匙分给y集合里的人,但此时你看我们在x集合里加上一个除y集合以外的人重要值还是小于m(代表钥匙不全),此时我们再加一个y集合里的人重要值肯定是大于m的,如果我们考虑这种情况相当于又给y集合里的人分了把钥匙,那么很显然这把钥匙是多余的,这种情况我们就没必要考虑,故可以得出我们上面的结论(也就是找 在人尽可能多的情况下重要值小于m的集合)

看懂的同学给个赞哦
java代码

import java.util.*;
public class Main {
    
    
    public static void main(String[] args) {
    
    
    	Scanner sc = new Scanner(System.in);
    	long n,m;
    	final int N=30;
    	long[]a=new long[N];
    	long []dp=new long[1<<21];
    	n=sc.nextLong();
    	m=sc.nextLong();
    	for(int i=0;i<n;i++)
    		a[i]=sc.nextLong();
    	long ans=0;
    	for(int i=0;i<(1<<n);i++) {
    
    
    		boolean flag=true;
    		for(int j=0;j<n;j++) {
    
    
    			if((i&(1<<j))==0) {
    
    
    				dp[i|(1<<j)]=dp[i]+a[j];
                    if(dp[i|(1<<j)]<m)flag=false;
    				
    			}
    		}
    		if(dp[i]<m&&flag)ans++;
    	}
    		
    		System.out.println(ans);	
	
	}
}

c++代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m,i,j,mx,flag,s,nt,a[101],f[1200010],ans;
int main(){
    
    
    scanf("%lld%lld",&n,&m);
    for(i=0;i<n;i++){
    
    
        scanf("%lld",&a[i]);
    }
    mx=1ll<<n;mx--;
    for(s=0;s<=mx;s++){
    
    
        flag=0;
        for(i=0;i<n;i++)if(!(s&(1ll<<i))){
    
    
            nt=s|(1ll<<i);
            f[nt]=f[s]+a[i];
            if(f[nt]<m)flag=1;
        }
        if(!flag&&f[s]<m)ans++;
    }
    printf("%lld",ans);
}

猜你喜欢

转载自blog.csdn.net/jahup/article/details/105879141