A.R
思路
假设有一串包含
k
k
k个R
的字符串[l,r]
,其左右第一个P
为L
,R
,那么这一段的贡献则为
(
l
−
L
)
∗
(
R
−
r
)
(l-L)*(R-r)
(l−L)∗(R−r),假设
L
1
L_1
L1,在
[
L
1
,
R
]
(
L
1
>
L
)
[L_1,R](L_1>L)
[L1,R](L1>L)也存在满足条件的串,如果还是按照之前那个计算方法,则于前面会产生重复的串,那么我们可以只计算一个字符X
对以它为结尾的串的影响 !
转移方程:
- X = ′ P ′ , d p [ i ] = 0 X='P',dp[i]=0 X=′P′,dp[i]=0
- X = ′ R ′ & & N u m R = = k , d p [ i ] = P [ i ] − R [ i ] X='R'\&\& Num_R==k ,dp[i]=P[i] - R[i] X=′R′&&NumR==k,dp[i]=P[i]−R[i]
- X = o t h e r , d p [ i ] = d p [ i − 1 ] X=other,dp[i]=dp[i-1] X=other,dp[i]=dp[i−1]
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e6 + 10;
char ch[maxn];
ll dp[maxn];
vector<int> R;
int main(){
int n, k; cin >> n >> k;
int lp = 0, lr = 0, sk = 0;
for(int i = 1; i <= n; i++){
cin >> ch[i];
if(ch[i] == 'R')
R.push_back(i);
}
ll res = 0;
for(int i = 1; i <= n; i++){
if(ch[i] == 'P') lp = i, lr += sk, sk = 0;
else if(ch[i] == 'R'){
sk++;
if(sk == k){
dp[i] = R[lr++] - lp;
sk--;
}
else dp[i] = dp[i - 1];
}
else dp[i] = dp[i - 1];
res += dp[i];
}
cout << res;
}
D.雪色光晕
思路
题意简化就是求点到线段的最短距离。
总的来说就是分为两种情况
- 垂点在线段上:答案即三角形的高
- 垂点不在线段上:答案即两个斜边中短的那一条
知识点:海伦公式
p = a + b + c 2 , s = p ∗ ( p − a ) ∗ ( p − b ) ∗ ( p − c ) p=\frac{a+b+c}{2},s=\sqrt{p*(p-a)*(p-b)*(p-c)} p=2a+b+c,s=p∗(p−a)∗(p−b)∗(p−c)
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn = 2e5 + 10;
const double eps = 1e-8;
double min(double x, double y) { if (x - y > eps) x = y; return x; }
struct Dir {
double x, y;
}dir[maxn], s, t;
int main() {
int n; cin >> n;
cin >> s.x >> s.y >> t.x >> t.y;
for (int i = 1; i <= n; i++) cin >> dir[i].x >> dir[i].y;
double res = 9999999999;
for (int i = 0; i <= n; i++) {
double x = s.x + dir[i].x, y = s.y + dir[i].y;
double a = (s.x - x) * (s.x - x) + (s.y - y) * (s.y - y);
double b = (s.x - t.x) * (s.x - t.x) + (s.y - t.y) * (s.y - t.y);
double c = (t.x - x) * (t.x - x) + (t.y - y) * (t.y - y);
if (b >= a + c || c >= a + b) {
res = min(res, min(sqrt(b), sqrt(c)));
}
else {
a = sqrt(a); b = sqrt(b); c = sqrt(c);
double p = (a + b + c) / 2;
double s = sqrt(p * (p - a) * (p - b) * (p - c));
double h = s / a * 2;
res = min(res, h);
}
s.x = x, s.y = y;
}
cout << fixed << setprecision(8) << res;
}
G.子序列权值乘积
思路
累积最大值、最小值对答案的贡献
如果我们把
x
x
x作为最小值,那么
x
x
x的贡献则为
x
2
n
u
m
x^{2^{num}}
x2num,
n
u
m
num
num为比
x
x
x大的数的数量,因为最小值为
x
x
x的子序列为
2
n
u
m
2^{num}
2num种(随便选一个大于等于
x
x
x作为子序列最大值)
比如序列[1,2,3,4],如果把1作为最小值,那么1参与的子序列有:[1,1]、[1,2]、[1,3]、[1,2,3]、[1,4]、[1,2,4]、[1,3,4]、[1,2,3,4]
但是答案是让求
m
i
n
∗
m
a
x
min*max
min∗max,只需要求1的贡献有什么用啊?
因为res是对每一个子序列的
m
i
n
∗
m
a
x
min*max
min∗max的积!(自己就读成和了
所以根据乘法的分配律,我们可以把所有的相同的min放到一起,相同的max放到一起,因此我们只需要统计
x
x
x作为最小值出现的次数即可!
最大值同理!
优化
上面思路的朴素做法是
∏
x
=
1
n
q
p
o
w
(
a
[
x
]
,
q
p
o
w
(
2
,
n
−
x
)
)
\prod_{x=1}^nqpow(a[x],qpow(2,n-x))
∏x=1nqpow(a[x],qpow(2,n−x)),我们可以优化一下。
对于一个数x,如果每多一个y(
y
≥
x
y \ge x
y≥x)那么x出现的次数翻倍,我们可以通过这个性质,优化答案的累计过程
复杂度:O(n)
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 10;
const ll mod = 1e9 + 7;
int arr[maxn];
int main(){
int n; cin >> n;
for(int i = 1; i <= n; i++) cin >> arr[i];
sort(arr + 1, arr + 1 + n);
ll res_min = 1, res_max = 1;
for(int i = 1; i <= n; i++){
res_min = res_min * res_min % mod * arr[i] % mod;
res_max = res_max * res_max % mod * arr[n - i + 1] % mod;
}
cout << res_min * res_max % mod;
}
J.区间合数的最小公倍数
思路
首先要知道最大公因数和最小公倍数在素因数角度的本质
lcm(a,b) | a和b的素因数幂取max,比如9跟30, 9 = 3 3 , 30 = 2 ∗ 3 ∗ 5 9=3^3,30=2*3*5 9=33,30=2∗3∗5,所以 l c m ( 9 , 30 ) = 2 ∗ 3 3 ∗ 5 lcm(9,30)=2*3^3*5 lcm(9,30)=2∗33∗5 |
---|---|
gcd(a,b) | a和b的素因数幂取min,比如9和30, 9 = 3 3 , 30 = 2 ∗ 3 ∗ 5 9=3^3,30=2*3*5 9=33,30=2∗3∗5,所以 g c d ( 9 , 30 ) = 3 gcd(9,30)=3 gcd(9,30)=3 |
知道这两条性质,那么这个题就显而易见了,求出所有和数的素因数,然后对各素数的幂取max。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 3e4 + 10;
const ll mod = 1e9 + 7;
bool vis[maxn + 10];
vector<int> prime;
int num[maxn];
void E() {
for (int i = 2; i <= maxn; i++) {
if (!vis[i]) prime.push_back(i);
for (int j = 0; j < (int)prime.size() && i * prime[j] <= maxn; j++) {
vis[i * prime[j]] = true;
if (i % prime[j] == 0) break;
}
}
}
void solve(int x) {
for (auto& te : prime) {
int temp = 0;
while (x % te == 0 && x) {
x /= te;
temp++;
}
num[te] = max(num[te], temp);
if (!te) return;
}
}
ll qpow(ll a, ll x) {
ll res = 1;
while (x) {
if (x & 1) res = res * a % mod;
a = a * a % mod;
x >>= 1;
}
return res;
}
int main() {
E();
int l, r; cin >> l >> r;
bool is = false;
for (int i = l; i <= r; i++) {
if (!vis[i]) continue;
else is = true;
solve(i);
}
if (!is) {
cout << -1;
return 0;
}
ll ans = 1;
for (auto &te:prime) {
(ans *= qpow(te, num[te])) %= mod;
}
cout << ans;
}
I.爆炸的符卡洋洋洒洒
思路
01背包变种:容量为K的背包变为容量的K的倍数的背包
容量为K通过取模来把他缩小到K的范围,如: a + b = n k a+b=nk a+b=nk,那么 a % k + b % k = k a\%k+b\%k=k a%k+b%k=k
因此转移方程为:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
(
j
−
a
[
i
]
%
k
+
k
)
%
k
]
+
b
[
i
]
)
dp[i][j]=max(dp[i-1][j],dp[i-1][(j-a[i]\%k+k)\%k]+b[i])
dp[i][j]=max(dp[i−1][j],dp[i−1][(j−a[i]%k+k)%k]+b[i])
按照习惯,我们接下来就是把二维dp压为一维,但是本题是压不了的。
因为取模操作,我们可能会对上一轮结果产生影响,比如我们 d p [ 10 ] = d p [ 7 ] + b [ 10 ] dp[10]=dp[7]+b[10] dp[10]=dp[7]+b[10],此时 d p [ 7 ] dp[7] dp[7]为选择第10个物品时的 d p [ 7 ] dp[7] dp[7],而不是选择第9个物品时的 d p [ 7 ] dp[7] dp[7],因此我们必须用二维来区分 i i i和 i − 1 i-1 i−1。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll dp[1010][1010], a[1010], b[1010];
int main() {
int n, k; cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
a[i] %= k;
}
memset(dp, 128, sizeof dp);
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < k; j++) {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][(j - a[i] + k) % k] + b[i]);
}
}
if (dp[n][0] <= 0) dp[n][0] = -1;
cout << dp[n][0];
}