CF95E Lucky Country 题解
Desciption
Solution
非常巧妙的 \(dp\) 题,sto xy学长 orz
同一个区域的岛屿明显只能在一起,所以用并查集合并一下同一区域内的岛屿。
然后我们要找出用最少的边连出一个幸运地区。
那这不就是一个背包吗!
朴素的状态:\(dp_{i, j}\) 表示考虑到前 \(i\) 个区域,一共有 \(j\) 个岛屿的最小连边数。
朴素的转移方程:
\[dp_{i, j} = \min\{dp_{i - 1, j - a_i} + 1\}
\]
\(a_i\):表示区域 \(i\) 中的岛屿个数。
显然会 T,考虑优化。
经典的二进制拆分就登场了,所以我们二进制拆一下跑个 01背包 即可。
Code
#include <bits/stdc++.h>
using namespace std;
namespace IO{
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
template <typename T> inline void write(T x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace IO;
const int N = 1e5 + 10;
int n, m, tot, cnt;
int f[N], siz[N];
int a[N], dp[N], w[N], v[N];
inline int find(int x){
return f[x] == x ? x : f[x] = find(f[x]);
}
inline void prework(){
for(int i = 1; i <= n; ++i){
if(!a[i]) continue;
int tmp = 1;
while(a[i]){
w[++cnt] = i * tmp, v[cnt] = tmp;
a[i] -= tmp, tmp <<= 1;
if(a[i] < tmp && a[i]){
w[++cnt] = i * a[i], v[cnt] = a[i];
break;
}
}
}
}
inline bool check(int x){
while(x){
if(x % 10 != 4 && x % 10 != 7) return 0;
x /= 10;
}
return 1;
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i) f[i] = i, siz[i] = 1;
for(int i = 1; i <= m; ++i){
int fu = find(read()), fv = find(read());
if(fu != fv){
if(siz[fu] < siz[fv]) swap(fu, fv);
f[fu] = fv, siz[fv] += siz[fu];
}
}
for(int i = 1; i <= n; ++i)
if(find(i) == i) a[siz[i]]++;
memset(f, 0x3f, sizeof(f));
prework();
f[0] = 0;
for(int i = 1; i <= cnt; ++i)
for(int j = n; j >= w[i]; --j)
f[j] = min(f[j], f[j - w[i]] + v[i]);
int ans = 1e9;
for(int i = 1; i <= n; ++i)
if(check(i)) ans = min(ans, f[i]);
write(ans > n ? -1 : ans - 1);
puts("");
return 0;
}
\[\_EOF\_
\]