pro:给定N个点,M条边,现在你要给一些连通块加边,使得至少存在一个连通块的大小是由4和7组成的数字。问至少加边数量。

sol: 看似一个很难的题目。  首先不要想太难了,还是应该想能不能用背包做。 我们把块的大小相同的分到一组,就可以分组背包了。 然后注意到组别的大小其实不会太大,因为1*1+2*2+3*3+4*4+x*x<=1e5;   x是1e2级别的。 所以做100次多重背包。 总的复杂度也才O(100*N*K),K是个比较小的常数,真正的复杂度小于这个;

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int maxn=200010;
int fa[maxn],num[maxn],sum[maxn];
int dp[maxn],N;
int find(int x)
{
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}
void pack(int v,int now)
{
    for(int i=N;i>=v;i--){
        dp[i]=min(dp[i-v]+now,dp[i]);
    }
}
void solve(int v,int x)
{
    int now=1;
    while(now<x){
        pack(v*now,now);
        x-=now;
        now*=2;
    }
    if(x) pack(v*x,x);
}
bool check(int x){
    int tx=x;
    while(x){
        int t=x%10;
        if(t!=4&&t!=7) return false;
        x/=10;
    }
    return true;
}
int main()
{
    int M,v,u,fu,fv;
    scanf("%d%d",&N,&M);
    rep(i,1,N) num[i]=1,fa[i]=i;
    rep(i,1,M){
        scanf("%d%d",&u,&v);
        fu=find(u);
        fv=find(v);
        if(fu!=fv) num[fu]+=num[fv],fa[fv]=fu;
    }
    rep(i,1,N) {
        fu=find(i);
        if(fu==i) sum[num[fu]]++;
    }
    rep(i,1,N) dp[i]=N+M+1;
    rep(i,1,N)
      if(sum[i]) solve(i,sum[i]);
    int res=N+M+1;
    rep(i,1,N)
      if(check(i)) res=min(dp[i],res);
    if(res==N+M+1) puts("-1");
    else printf("%d\n",res-1);
    return 0;
}