题解 DTOJ #1071. 国王小C kingdom

欢迎访问 My Luogu Space。


【题目大意】

有一些正整数,保证这些正整数的和不超过 50000 50000 50000,现在你需要任意取出一些数,使得这些数的和只由 4 4 4 7 7 7 组成。如果无法组成则输出“-1”。


【题解】

二进制拆位优化dp

一开始有一个很朴素的dp:
d p [ j ] = m i n ( d p [ j ] , d p [ j − s [ i ] + 1 ) dp[j] = min(dp[j],dp[j-s[i]+1) dp[j]=min(dp[j],dp[js[i]+1) i i i 枚举的是这些正整数, j j j 枚举的是从 n n n n − s [ i ] n-s[i] ns[i] 内的数。
dp里面的值存的是要取到这个数最少需要取几次。

不过这样的效率会有些不够,我们考虑怎么优化。

我们发现本题给出的数据如果比较大,则会有较多的数字重复,而重复的数字我们没必要一个一个拿进去dp。

假设现在某一个数字重复的次数为 k k k,我们将 k k k 分成 1 , 2 , 4 , 8...... 1,2,4,8...... 1,2,4,8......,最后一组为剩下的数。我们将每一组看成一个新的数字,而这些数字能够组成原本 k k k 个数字能组成的所有的数。我们只需要在更新答案的时候让次数加这些数字的值就可以了。

这样数字的个数就被降了下来,效率得到提高。


【代码】

// output format !!!
// long long !!!
#include 
typedef long long LL;
using namespace std;
const int MAXN = 50000+10;
const int INF = 0x3f3f3f3f;

int n, m, ans=INF;
int fa[MAXN], num[MAXN], siz[MAXN], dp[MAXN];
vector<int> S;

int G(int x){
	return fa[x]==x ? x : fa[x]=G(fa[x]);
}
int can(int x){
	while(x){
		int y = x%10;
		if(y!=4 && y!=7) return 0;
		x /= 10;
	}
	return 1;
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; ++i) fa[i]=i, siz[i]=1;
	while(m--){
		int a, b; scanf("%d%d", &a, &b);
		int x=G(a), y=G(b);
		if(x == y) continue;
		fa[y] = x, siz[x] += siz[y];
	}
	for(int i=1; i<=n; ++i)
		if(G(i) == i){
			if(!num[siz[i]]) S.push_back(siz[i]);
			++num[siz[i]];
		}
	memset(dp, INF, sizeof dp);
	dp[0] = 0;
	for(auto i=S.begin(); i!=S.end(); ++i){
		for(int k=1; k<=num[*i]; num[*i]-=k, k<<=1)
			for(int j=n; j>=k*(*i); --j)
				dp[j] = min(dp[j], dp[j-k*(*i)]+k);
		for(int j=n; j>=num[*i]*(*i); --j)
			dp[j] = min(dp[j], dp[j-num[*i]*(*i)]+num[*i]);
	}
	for(int i=1; i<=n; ++i){
		if(dp[i] >= ans) continue;
		if(can(i)) ans = dp[i];
	}
	printf("%d", ans==INF?-1:ans-1);
	return 0;
}

你可能感兴趣的:(题解)