競プロ典型 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 ,并使 ∠PiPjPk 最大。使用度数法输出最大值。
然而,设置 0∘≤∠PiPjPk≤180∘ 。
∠PiPjPk 是点 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;
}

浙公网安备 33010602011771号