競プロ典型 90 問-难题

005倍增优化dp

题目大意(自己总结
只用数字 c1​,c2​,…,cK​ 可以构造出多少个 N 位正整数是 B 的倍数? 求除以 109+7 的余数。

  • $1 \leq K \leq 9$
  • $1 \leq c_1 \lt c_2 \lt \cdots \lt c_K \leq 9$
  • $1 \leq N \leq 10^{18}$
  • $2 \leq B \leq 1000$
    题目主要实现思路
    遇到这种凑倍数相关的题目,优先考虑dp[ i j ] 表示前i个数可以组成的数模上b的值,因此可得状态转移方程dp i (j * 10 + a[ k ])%b =dp i-1 j ,此时有三层循环N * B * K,因为N很大,所以考虑矩阵快速幂,可降到 O (B³×logN) ,引入一种新方法倍增法根据前面的递推公式dp[i + 1][(r × 10 + c[k]) % B] += dp[i][r],我们可以理解为从dp[0]开始,按dp[0]dp[1]dp[2]→…→dp[N]的顺序依次计算。但这种直接计算的方式需要 O (N) 步,效率过低。
    因此可以考虑先预处理出dp[ 1 ] dp 2 dp4 dp8 参考二进制的方式
    关键在于实现 “通过dp[i]数组和dp[j]数组,快速计算出dp[i+j]数组”。只要能实现这一点,我们就能像计算 3ⁿ那样,通过倍增法在 O (logN) 步内求出dp[N]数组。

这里的dp[i+j]表示 i+j 位整数对应的动态规划数组。我们可以将 i+j 位整数拆分为 “前 i 位” 和 “后 j 位” 两部分来分析:

  • 设前 i 位整数除以 B 的余数为 p(对应的数量为dp[i][p]种);
  • 设后 j 位整数除以 B 的余数为 q(对应的数量为dp[j][q]种)。

那么,由这两部分组成的 i+j 位整数除以 B 的余数为:(p × 10ʲ + q) % B

由此可推导出dp[i+j]的递推公式:dp[i + j][(p × tⱼ + q) % B] += dp[i][p] × dp[j][q]

其中,tⱼ表示 10ʲ除以 B 的余数。这正是 “通过dp[i]dp[j]计算dp[i+j]” 的递推公式,且该递推的时间复杂度为 O (B²)。

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e6 + 10;

const int mod = 1e9 + 7;

void solve()

{

    int n, b, k;

    cin >> n >> b >> k;

    vector<int> a(k, 0);

    for (int i = 0; i < k; i++)

    {

        cin >> a[i];

    }

    auto mul = [&](vector<int> &dpi, vector<int> &dpj, int powv) -> vector<int>

    {

        vector<int> res(b, 0);

        for (int p = 0; p < b; p++)

        {

            for (int q = 0; q < b; q++)

            {

                res[(p * powv + q) % b] += (dpi[p] * dpj[q]) % mod;

                res[(p * powv + q) % b] = res[(p * powv + q) % b] % mod;

            }

        }

        return res;

    };

    vector<int> tenpow(100, 0);

    tenpow[0] = 10;

    for (int i = 1; i < 100; i++)

    {

        tenpow[i] = (tenpow[i - 1] * tenpow[i - 1]) % b;

    }

    vector<vector<int>> fastdp(100, vector<int>(b, 0));

    for (int i = 0; i < k; i++)

    {

        fastdp[0][a[i] % b] += 1;

    }

    for (int i = 1; i < 100; i++)

    {

        fastdp[i] = mul(fastdp[i - 1], fastdp[i - 1], tenpow[i - 1]);

    }

    vector<int> res(b, 0);

    res[0] = 1;

    for (int i = 0; i < 63; i++)

    {

        if ((n >> i) & 1)

        {

            res = mul(res, fastdp[i], tenpow[i]);

        }

    }

    cout << res[0] << '\n';

}

signed main()

{

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int T;

    T = 1;

    // cin >> T;

    while (T--)

        solve();

    return 0;

}

006按位贪心类似的思路

Smallest Subsequence
问题陈述。
给定一个长度为 $N$ 的字符串 $S$ ,其中只有英文小写字母。

输出长度为 $K$ 的 $S$ 的最小子串,该子串符合词典顺序。
题目大意(自己总结

题目主要实现思路
定义$next[i][j]$ 数组,为从第i个位置之后包含第i个位置,包含j字母的第一个下标
逆序遍历预处理出$next[i][j]$ 然后题目要求字典顺序最小,因此从第一位开始贪心,枚举所有可以选的值,如果当前为pos,还可以选的字母个数为n-pos ,还要求选的个数为k-i 如果$n-pos>=k-i$ 那么就是满足情况可以加入的数组中,这样最后的结果一定满足字典序最小

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e6 + 10;

const int mod = 1e9 + 7;

void solve()

{

    int n, k;

    cin >> n >> k;

    string s;

    cin >> s;

    vector<vector<int>> next(n + 1, vector<int>(26, n));

    for (int i = n - 1; i >= 0; i--)

    {

  

        next[i] = next[i + 1];

        next[i][s[i] - 'a'] = i;

    }

    int j = -1;

    string res;

    for (int i = 0; i < k; i++)

    {

        for (char c = 'a'; c <= 'z'; c++)

        {

            int pos = next[j + 1][c - 'a'];

            if (n - pos >= k - i)

            {
               // cout << k << '\n';
                res += c;
                j = pos;
                break;

            }

        }

    }

    cout << res << '\n';

}

signed main()

{

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int T;

    T = 1;

    // cin >> T;

    while (T--)

        solve();

    return 0;

}

009计算集合极角排序加上二分

题目大意(自己总结
坐标平面上有 N 个不同的点 P1​,…,PN​ ,点 Pi​ 的坐标为 (Xi​,Yi​)。

我们希望从这些 N 点中选出三个互不相同的点 Pi​,Pj​,Pk​ ,并使 ∠Pi​Pj​Pk​ 最大。使用度数法输出最大值。

然而,设置 0∘≤∠Pi​Pj​Pk​≤180∘ 。

∠Pi​Pj​Pk​ 是点 Pi​→ 点 Pj​→ 点 Pk​ 处的折线所形成的角的大小。

  • $3 \leq N \leq 2000$
  • $0 \leq X_i, Y_i \leq 10^9$ $(1 \leq i \leq N)$
  • $(X_i, Y_i) \neq (X_j, Y_j)$ $(1 \leq i < j \leq N)$
  • 入力される値は全て整数である
    题目主要实现思路
    首先读取所有点,遍历一个点,求所有点与该点的向量和极角值极角值映射到$0-2 * PI$ ,按极角排序,此时为n-1个,遍历所有向量,此时可以取到最大的角的可能值就两种,要么是最靠近该向量反向延长线左边的点,和右边的点,注意有可能会有循环,然后二分找这两个点,分别带入算角度值
    取最大即可
    ![[Pasted image 20251108232727.png]]
#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e6 + 10;

const int mod = 1e9 + 7;

long double getthe(pair<int, int> a)

{

    return atan2(a.second, a.first) + 2 * M_PI;

}

struct d

{

    double dx, dy, theas;

    d(double _ = 0, double __ = 0)

    {

        dx = _;

        dy = __;

        theas = atan2(dy, dx) + 2 * M_PI;

    }

};

bool cmp(d a, d b)

{

    return a.theas < b.theas;

}

void solve()

{

    int n;

    cin >> n;

    double max_deg = INT_MIN;

    vector<pair<int, int>> point(n);

    for (int i = 0; i < n; i++)

    {

        cin >> point[i].first >> point[i].second;

    }

    for (int i = 0; i < n; i++)

    {

        vector<double> thes;

        vector<d> q;

        for (int j = 0; j < n; j++)

        {

            if (i == j)

            {

                continue;

            }

  

            int dx = point[i].first - point[j].first;

            int dy = point[i].second - point[j].second;

            q.push_back({dx, dy});

        }

        sort(q.begin(), q.end(), cmp);

        for (int tp = 0; tp < q.size(); tp++)

        {

            thes.push_back(q[tp].theas);

        }

        for (int k = 0; k < q.size(); k++)

        {

            double nw = q[k].theas;

            double tar = nw + M_PI;

            int pos = upper_bound(thes.begin(), thes.end(), tar) - thes.begin();

            int m = thes.size();

            int idx1 = pos % m;

            int idx2 = (pos - 1 + m) % m;

            for (auto it : {idx1, idx2})

            {

                if (it == k)

                {

                    continue;

                }

                auto q1 = q[k], q2 = q[it];

                double dot = q1.dx * q2.dx + q1.dy * q2.dy;

                double mag_m = sqrt(q1.dx * q1.dx + q1.dy * q1.dy);

                double mag_s = sqrt(q2.dx * q2.dx + q2.dy * q2.dy);

                if (mag_m < 1e-10 || mag_s < 1e-10)

                    continue;

                double cos_theta = dot / (mag_m * mag_s);

                cos_theta = max(min(cos_theta, 1.0), -1.0); // 防止精度溢出

                double angle = acos(cos_theta);

                double deg = angle * 180.0 / M_PI;

                if (deg > max_deg)

                {

                    max_deg = deg;

                    if (fabs(max_deg - 180.0) < 1e-10)

                    { // 提前终止(180°是最大值)

                        cout << fixed << setprecision(12) << 180.0 << endl;

                        return;

                    }

                }

            }

        }

    }

    cout << fixed << setprecision(12) << max_deg << '\n';

}

signed main()

{

    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int T;

    T = 1;

    // cin >> T;

    while (T--)

        solve();

    return 0;

}
posted @ 2025-11-05 23:13  Jwe1  阅读(16)  评论(0)    收藏  举报