CF95E Lucky Country 并查集+dp

题目大意:
如果一个数中不包含除4和7之外的数字则是幸运数。有n个岛屿,通过双向道路连接。这些岛屿被分为几个地区。每个岛属于恰好一个区域,同一区域中的任何两个岛之间存在道路,不同区域的任何两个岛之间没有路径。如果一个地区的岛屿数量是一个幸运数字,则这个地区是幸运的。问最少增加几条道路能创建一个幸运地区。

分析:
首先跑出每个地区的大小,因为边是双向的,所以不用连通分量,直接上并查集去维护即可。假设每个连通块的大小为 x i , 现在问题就变成问最少要用多少个 x i 可以组成一个幸运数字了。因为题目要求的是连接的次数,所以把这个答案减 1 即可。

我们发现所有数字的和是等于 n 的,所以可以用dp,设 f [ i ] [ j ] 为前i个数,组成和为 j 的数,设第i个数为 a i ,最小要用多少个数,有

f [ i ] [ j ] = m i n ( f [ i 1 ] [ j a i ] + 1 )

显然这会超时,但是我们发现不同的 a i 顶多只有 n 个,因为所有 a i 的和为 n ,而 1 n 的和就已经超过 n 了。所以可以变为一个多重背包,然后使用二进制优化多重背包可以做到 O ( n n l o g k ) ,其中 k 为每种数字的个数。虽然 k 最大可以是 1 e 5 ,但是这样 a i 的总数就很小,一般的复杂度应该是 O ( n n ) 的。

代码:

#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);
}

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/81128808
今日推荐