题目大意:
如果一个数中不包含除4和7之外的数字则是幸运数。有n个岛屿,通过双向道路连接。这些岛屿被分为几个地区。每个岛属于恰好一个区域,同一区域中的任何两个岛之间存在道路,不同区域的任何两个岛之间没有路径。如果一个地区的岛屿数量是一个幸运数字,则这个地区是幸运的。问最少增加几条道路能创建一个幸运地区。
分析:
首先跑出每个地区的大小,因为边是双向的,所以不用连通分量,直接上并查集去维护即可。假设每个连通块的大小为
, 现在问题就变成问最少要用多少个
可以组成一个幸运数字了。因为题目要求的是连接的次数,所以把这个答案减
即可。
我们发现所有数字的和是等于
的,所以可以用dp,设
为前i个数,组成和为
的数,设第i个数为
,最小要用多少个数,有
显然这会超时,但是我们发现不同的 顶多只有 个,因为所有 的和为 ,而 到 的和就已经超过 了。所以可以变为一个多重背包,然后使用二进制优化多重背包可以做到 ,其中 为每种数字的个数。虽然 最大可以是 ,但是这样 的总数就很小,一般的复杂度应该是 的。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
const int maxn=1e5+7;
using namespace std;
int n,m,cnt,x,y;
int p[maxn],f[maxn],b[maxn],size[maxn],w[maxn],v[maxn];
int find(int x)
{
int y=x,root;
while (p[x]) x=p[x];
root=x;
x=y;
while (p[x])
{
y=p[x];
p[x]=root;
x=y;
}
return root;
}
void uni(int x,int y)
{
int u=find(x);
int v=find(y);
if (u==v) return;
if (size[x]>size[y]) p[v]=u,size[u]+=size[v];
else p[u]=v,size[v]+=size[u];
}
bool check(int x)
{
while (x>0)
{
if ((x%10!=4) && (x%10!=7)) return false;
x/=10;
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) size[i]=1;
for (int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
uni(x,y);
}
for (int i=1;i<=n;i++)
{
if (find(i)==i) b[size[i]]++;
}
memset(f,0x3f3f3f3f,sizeof(f));
f[0]=0;
for (int i=1;i<=n;i++)
{
if (!b[i]) continue;
int sum=1,power=1,k=1;
w[++cnt]=i;
v[cnt]=1;
while (sum<b[i])
{
power*=2;
sum+=power;
if (sum>=b[i])
{
sum-=power;
w[++cnt]=i*(b[i]-sum);
v[cnt]=b[i]-sum;
break;
}
w[++cnt]=i*power;
v[cnt]=power;
}
}
for (int i=1;i<=cnt;i++)
{
for (int j=n;j>=0;j--)
{
if (j-w[i]>=0)
f[j]=min(f[j],f[j-w[i]]+v[i]);
}
}
int ans=0x3f3f3f3f;
for (int i=4;i<=n;i++)
{
if (check(i))
{
ans=min(ans,f[i]-1);
}
}
if (ans>n) printf("-1");
else printf("%d",ans);
}