1001
思维
选一个子数组,取出值在一个区间内的元素,计算最大子段和b,问b最大的方案数?
注意到都是非负数,所以想要达到最大,一定要取到所有正数,所以子数组的范围一定包含第一个正数到最后一个正数之间的全部,前后缀0可以随便取。
而值域一定要包含最小正数到最大正数,其他可以随便取。
全0需要特判。
#include <bits/stdc++.h>
#define int long long
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using pii = pair<int, int>;
using graph = vector<vector<pii>>;
using ugraph = vector<vector<int>>;
constexpr int N = 2E5 + 5;
constexpr double eps = 1E-8;
constexpr int inf = 0x3f3f3f3f;
constexpr ll INF = 1E18;
constexpr int MOD = 998244353;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
int sum = accumulate(a.begin(), a.end(), 0LL);
if (sum == 0) {
int lr = n * (n + 1) / 2;
lr %= MOD;
int LR = (n + 1) * (n + 1) % MOD;
int ans = lr * LR % MOD;
cout << sum << " " << ans << "\n";
return;
}
int pre = 0, suf = 0;
for (int i = 1; i <= n; i++) {
if (a[i] == 0)
pre++;
else
break;
}
for (int i = n; i >= 1; i--) {
if (a[i] == 0)
suf++;
else
break;
}
int Max = -inf, Min = inf;
for (int i = 1; i <= n; i++) {
Max = max(a[i], Max);
if (a[i] > 0) Min = min(Min, a[i]);
}
if (pre < n) pre++;
if (suf < n) suf++;
int ans = pre * suf % MOD * (n - Max + 1) % MOD * (Min + n + 1) % MOD;
cout << sum << " " << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1;
cin >> T;
for (int ttt = 1; ttt <= T; ++ttt) {
solve();
}
return 0;
}
1008
贪心,模拟
用元素构造一个序列,序列每次增,减,不变都有一个奖励x,y,z,问奖励最大值?
注意到x>=y>=zx>=y>=zx>=y>=z,所以其实就两种策略,要么制造尽可能多的上升和下降,这样每次得到的是x+zx+zx+z,要么把所有相同的放一块,这样得到的是2y2y2y
手动分讨很难实现,不妨考虑模拟,每次都先取出最长的上升子序列,在上升子序列长度大于一个阈值之前这样肯定都是最优的,小于阈值后,我们需要比较上升子序列和相同的挨着放哪个更优
实现时可以记录每种元素的剩余个数,然后取出一个最长上升子序列就是每个元素的出现次数-1,看起来很暴力,但是所有元素出现次数等于nnn,这样暴力操作复杂度也是O(n)O(n)O(n)的,只要记得把出现次数变成0的元素删掉。
void solve() {
int n, x, y, z;
cin >> n >> x >> y >> z;
vector<int> tot(n + 1);
vector<int> tot_cnt(n + 1);
for (int i = 1; i <= n; i++) {
int num; cin >> num;
tot[num]++;
}
for (int i = 1; i <= n; i++) {
tot_cnt[tot[i]]++;
}
vector<int> arr;
for (int i = n; i >= 1; i--) {
while (tot_cnt[i]) {
arr.emplace_back(i);
tot_cnt[i]--;
}
}
if (x == y) {
cout << n * x << "\n";
return;
}
int ans = arr.size() * x;
for (auto &i : arr) i--;
while (not arr.empty() and arr.back() == 0) arr.pop_back();
while (((int)arr.size() - 1) * x + z >= (int)arr.size() * y) {
ans += ((int)arr.size() - 1) * x + z;
for (int &i : arr) {
i--;
}
while (not arr.empty() and arr.back() == 0) arr.pop_back();
}
int sum = accumulate(arr.begin(), arr.end(), 0LL);
ans += sum * y;
cout << ans << "\n";
}
1012
计算几何 枚举 二分 自适应辛普森
一个第一象限单增的函数表示山坡,海上第二象限有个太阳,山坡上盖楼,每个楼都有高度,需要满足相邻的两个楼不能互相遮挡,且楼底的欧氏距离不小于一个值。每个楼还有一个人数,总代价是∑每个楼的人数∗楼底沿山坡到达原点的距离\sum 每个楼的人数*楼底沿山坡到达原点的距离∑每个楼的人数∗楼底沿山坡到达原点的距离
最小化总代价?
注意到楼数不多,且不好贪心的确定一个盖楼顺序,不妨全排列枚举盖楼顺序。第一个楼肯定楼底在原点,后面的楼二分确定最小的合法位置,二分时直到前一个楼的位置了,之间check楼底的欧氏距离,以及楼底和太阳的连线是否会被前一栋楼挡住即可(因为在一个单增的山坡上,楼底是最可能被挡住采光的点)
确定了每一栋楼的楼底,接下来这种方案的答案就是楼底沿着山坡曲线走到原点的距离*楼里人数。
这里是本题最大的难点,沿着曲线的距离,这实际上是个曲线积分,表达式为∫0xi1+y′2dx\int_{0}^{x_i}\sqrt{1+y'^2}dx∫0xi1+y′2dx,xix_ixi为楼底的横坐标,yyy为山坡的表达式
山坡表达式是个有点复杂的多项式,手动算出原函数然后定积分不太可能的。
穷极脑力也做不出来的时候,往往就该上科技了,都打算法竞赛了,计算积分可以数值模拟啊,我们可以用自适应辛普森法,来计算一个已知被积函数,但是求不出原函数的积分。
被积函数就是1+y′2\sqrt{1+y'^2}1+y′2,他的函数值是可以单点计算的,这就可以了,可以由此计算出它的积分
这里注意精度太大会超时,本题不需要太高的精度,所以调用辛普森法时,精度和递归层数都开小点。
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N = 2E5 + 5;
constexpr long double eps = 1E-8;
constexpr long double inf = 1E8;
struct P {
long double x, y;
P(long double _x = 0, long double _y = 0){
x = _x;
y = _y;
}
P operator-(const P &o) {
return P(x - o.x, y - o.y);
}
long double operator^(const P &o) {
return x * o.y - y * o.x;
}
};
long double dist(P &u, P &v) {
return sqrt((u.x - v.x) * (u.x - v.x) + (u.y - v.y) * (u.y - v.y));
}
int dir(P &u, P &v, P &w) {
P vv = v - u;
P ww = w - u;
if ((vv ^ ww) > eps) return 1;
else if (abs(vv ^ ww) < eps) return 0;
else return -1;
}
int n, m, X, Y;
void output(long double x){
static char s[25];
sprintf(s,"%.4Le",x);
for(int i:{0,1,2,3,4,5,6,7}) printf("%c",s[i]);
printf("%c\n",s[strlen(s)-1]);
}
int a[10], h[10], w[10];
int height;
P last;
long double f(long double x) {
long double y = 0;
for (int i = 1; i <= m; i++) {
long double tmp = 1;
for (int j = 0; j < i; j++) {
tmp *= x;
}
y += tmp * a[i];
}
return y;
}
long double g(long double x) {
long double y = 0;
for (int i = 1; i <= m; i++) {
long double tmp = 1;
for (int j = 1; j < i; j++) {
tmp *= x;
}
y += a[i] * tmp * i;
}
return sqrt(1 + y*y);
}
bool check(long double x) {
P now(x, f(x));
P pre(last.x, f(last.x));
long double d = dist(now, pre);
if (d < height - eps) return false;
P sun(X, Y);
if (dir(now, sun, last) < -eps) return false;
return true;
}
long double simpson(long double l,long double r){
long double mid=(l+r)/2;
return (r-l)*(g(l)+4*g(mid)+g(r))/6;
}
long double asr(long double l,long double r,long double eps,long double ans,int step){
long double mid=(l+r)/2;
long double fl=simpson(l,mid),fr=simpson(mid,r);
if(abs(fl+fr-ans)<=15*eps&&step<0){
return fl+fr+(fl+fr-ans)/15;
}
return asr(l,mid,eps/2,fl,step-1)+
asr(mid,r,eps/2,fr,step-1);
}
long double cal(long double l,long double r,long double eps){
return asr(l,r,eps,simpson(l,r),6);
}
void solve() {
scanf("%lld %lld %lld %lld", &n, &m, &X, &Y);
for (int i = 1; i <= m; i++) scanf("%lld", a+i);
for (int i = 1; i <= n; i++) scanf("%lld %lld", h+i, w+i);
vector<int> arr(n + 1);
iota(arr.begin(), arr.end(), 0);
long double ans = inf;
do {
last.x = 0, last.y = h[arr[1]];
long double cur = 0;
for (int i = 2; i <= n; i++) {
height = max(h[arr[i]], h[arr[i-1]]);
long double lo = last.x, hi = inf;
while (fabs(hi - lo) > eps) {
long double mid = (hi + lo) / 2;
if (check(mid)) {
hi = mid;
} else {
lo = mid;
}
}
long double D = cal(0,lo,1E-5);
cur += D * w[arr[i]];
last.x = lo, last.y = f(lo) + h[arr[i]];
}
ans = min(ans, cur);
} while (next_permutation(1 + arr.begin(), arr.end()));
output(ans);
}
signed main() {
//ios::sync_with_stdio(false), cin.tie(nullptr);
int T = 1; scanf("%lld", &T);
for (int ttt = 1; ttt <= T; ++ttt) {
solve();
}
return 0;
}