CF95E

做法

首先,不难将题目转化为:给定一些物品(连通块),物品有体积(连通块大小),对于每个 $x$,求最少几件物品能恰好装满一个容积为 $x$ 背包。

体积相同的物品我们视其为同一种。所以这是一个多重背包问题。利用二进制分解可以做到 $O(n\sqrt n)$。

时间复杂度证明

假设第 $i$ 种物品选了 $c_i$ 次。时间复杂度 $O(n(\sum_{i=1}^{n}\log_{c_i}))$。

有:一种方案中,最多只有 $\sqrt n$ 个 $i$ 满足 $c_i=0$(可以分 $i\leq\sqrt n$ 与 $i>\sqrt n$ 分类讨论)。

考虑最坏情况,此时的时间复杂度为 $O(n(\sum_{i=1}^{\sqrt n}\log_{c_i}))$;同时满足 $\sum_{i=1}^{\sqrt n} i\times c_i=n$,且它和 $\sum_{i=1}^{\sqrt n}2^{\lfloor \log i \rfloor} c_i$ 是同个级别的。

所以现在就是要贪心地构造 $c$ 数组,使得复杂度花销最大。假设是 $n$ 为 $1$,必定是优先让 $c_1=1$。接着是第二次,我们会发现当前的最优方案有两个:把 $c_1$ 加上 $2$ 以及把 $2^1=2$ 个满足 $\lfloor \log i \rfloor=1$ 的点各自加上 $1$。假若这两种都选了,那么有 $c_1=2,c_2=1,c_3=1$。之后第 $3$ 次的最优方案中 $c_1$ 需要加上 $4$,所有 $\lfloor \log i \rfloor=1$ 的点都要加上 $2$,所有 $\lfloor \log i \rfloor=2$ 的点要加上 $1$……总之,决策次数增加就意味着上一层中 $\lfloor \log i \rfloor=j$ 的所有点在当前需要加上的数翻了倍(这样才能使得 $\log c_i$ 增加)。这样的过程不超过 $\sqrt n$ 次。

归纳一下,$c$ 数组在最坏情况下:$c_i=\frac{\sqrt n}{2^{\lfloor \log i\rfloor}}$。时间复杂度是 $O(n(\sum_{i=1}^{\log\sqrt n} 2^{i - 1}\times((\log {\sqrt n}) - i+1)))$,$n$ 后面(括号里)的部分展开一下就是 $1\times(\log\sqrt n-0)+2\times(\log\sqrt n-1)+4\times (\log\sqrt n - 2)+\cdots+\sqrt n\times (\log\sqrt n-\log\sqrt n+1)$。再拆系数,$(\log\sqrt n)+(\log\sqrt n-1)+(\log\sqrt n - 1)+\cdots+(\log\sqrt n-\log\sqrt n+1)+(\log\sqrt n-\log\sqrt n+1)+\cdots+(\log\sqrt n-\sqrt n+1)$。不妨把它写成一个三角形的形式(每一列的数相同):$$ \begin{matrix} \sqrt n & \frac{\sqrt n}{2} & \cdots & 2 & 1\\ & \frac{\sqrt n}{2} & \cdots & 2 & 1 \\ & & \cdots & 2 & 1 \\ & & \cdots & \vdots & \vdots \\ & & & 2 & 1 \\ & & & & 1 \end{matrix} $$

其中,第一行的总和约为 $2\sqrt n$,第二行的约为 $\sqrt n$,第三行的 $\frac{\sqrt n}{2}$……最后一行为 $1$。等比数列求和即可,会发现总和约是 $4\sqrt n$。

综上所述,最大的花销也是 $n\sqrt n$ 级别的。故而本题的时间复杂度为 $O(n\sqrt n)$。

代码

#include <bits/stdc++.h>
#define FL(i, a, b) for(int i = (a); i <= (b); i++)
#define FR(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n, m, ans = INF, fa[N], cnt[N], c[N], dp[N];
int find(int x){return fa[x] == x? x : fa[x] = find(fa[x]);}
void Union(int x, int y){fa[find(x)] = find(y);}
int check(int x){
    while(x){
        if(x % 10 != 4 && x % 10 != 7) return 0;
        x /= 10;
    }
    return 1;
}
int main(){
    scanf("%d%d", &n, &m);
    FL(i, 1, n) fa[i] = i;
    FL(i, 1, m){
        int u, v; scanf("%d%d", &u, &v);
        Union(u, v);
    }
    FL(i, 1, n) cnt[find(i)]++;
    FL(i, 1, n) if(cnt[i]) c[cnt[i]]++;
    memset(dp, 0x3f, sizeof(dp)), dp[0] = 0;
    FL(i, 1, n){
        int s = c[i], p = 1, t;
        while(s > 0){
            t = min(s, p), s -= p, p *= 2;
            FR(j, n - t * i, 0)
                dp[j + t * i] = min(dp[j + t * i], dp[j] + t);
        }
    }
    FL(i, 1, n) if(check(i)) ans = min(ans, dp[i]);
    printf("%d\n", ans < INF? ans - 1 : -1);
    return 0;
}
posted @ 2023-07-26 19:45  徐子洋  阅读(22)  评论(0)    收藏  举报  来源