CF EDU 11
- [A. Co-prime Array](http://codeforces.com/contest/660/problem/A)
- [B. Seating On Bus](http://codeforces.com/contest/660/problem/B)
- [C. Hard Process](http://codeforces.com/contest/660/problem/C)
- 思路
- [D. Number of Parallelograms](http://codeforces.com/contest/660/problem/D)
- [E. Different Subsets For All Tuples](https://www.luogu.com.cn/problem/CF660E)
- [F Bear and Bowling 4](https://www.luogu.com.cn/problem/CF660E)
A. Co-prime Array
思路
两两挨着判断, 如果两者的最大公约数不为1的话, 那么就需要在他两个中间插入一个数, 使得这个数对于那两个数都是互质的。
插入什么数呢?
1, 可以恨清晰的想到, 1与任何数都互质。
因为这题还需要输出变化后的数组。所以我用的链表。O(1)修改。
代码
//
// main.cpp
// CF660(EDU 11)
//
// Created by 陈博 on 2020/9/24.
//
#include<bits/stdc++.h>
using namespace std;
int n;
int a[100010];
int p[200010];
int gcd(int x, int y)
{
return !y ? x : gcd(y, x % y);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
p[i] = i+1;
}
int cnt = 0;
for(int i = 1; i <= n-1; i++)
{
if(gcd(a[i], a[i+1]) != 1)
{
cnt++;
p[i] = n + cnt;
p[n+cnt] = i+1;
a[n + cnt] = 1;
}
}
p[n] = 0;
cout << cnt << endl;
int q = 1;
while(q != 0)
{
cout << a[q] << " ";
q = p[q];
}
return 0;
}
大佬做法
大佬根本没有用链表。 大佬在计算需要插入多少个的时候遍历了一边。
并在输出改变后的数组时又遍历了一遍。
#include <bits/stdc++.h>
using namespace std;
int n, i, ans;
int a[5000];
int main() {
//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i < n; i++) {
if (__gcd(a[i], a[i + 1]) != 1) {
ans++;
}
}
printf("%d\n", ans);
for (int i = 1; i <= n; i++) {
printf("%d ", a[i]);
if (i < n && __gcd(a[i], a[i + 1]) != 1) {
printf("1 ");
}
}
return 0;
}
B. Seating On Bus
思路
wtcl~~
我纯的暴力, 分了两类:1.有人坐靠近过道的座位。2.只有坐靠窗的座位的人。
对于第2种,可以先判断最后一排有1个人或2个人。然后剩下的就是只坐靠窗位置且人数为偶数了。—对于每一排, 先输出奇数, 在输出偶数。
对于第一种, 我也是把他分成了两种情况, 对于每个并着的两人座位, 是否坐满。
显而易见, 根据我们的上车顺序, 先下车的肯定都是两人座都坐满的人。
那么对于这些人, 和上一种情况处理方式相同即可
剩下的人就是坐在车尾且两人座只靠窗的人了。那么我们也是同样继续遍历输出即可
冗长的代码
//
// main.cpp
// B
//
// Created by 陈博 on 2020/9/24.
//
#include<bits/stdc++.h>
using namespace std;
int n, m;
int main()
{
cin >> n >> m;
if(m <= 2 * n)
{
bool flag = 0;
if(m % 2 == 1)
{
flag = 1;
m--;
}
for(int i = 1; i <= m - 1; i += 2)
{
cout << i << " " << i + 1 << " ";
}
if(flag)
{
cout << m + 1 << " ";
}
}
else
{
bool flag = 0;
if(m % 2 == 1)
{
flag = 1;
m--;
}
for(int i = 2 * n + 1; i <= m - 1; i += 2)
{
cout << i << " " << i - (2 * n) << " ";
cout << i + 1 << " " << i - (2 * n - 1) << " ";
}
if(flag)
{
cout << m + 1 << " " << m - (2 * n - 1) << " ";
m++;
}
m -= 2 * n;
m++;
for(int i = m; i <= 2 * n; i++)
{
cout << i << " ";
}
}
cout << endl;
return 0;
}
简短的dalao做法
大佬的做法有多简洁, 就证明我有多菜。。。
二话不说, 大佬直接从1开始枚举, 只不过他枚举的是每个座位(靠窗的)。并判断他旁边的座位有没有人。如果有, 就输出, 没有就跳过呗。。。
(智商受到了严重的打击。。。)
#include <bits/stdc++.h>
using namespace std;
int n, m, i;
int main() {
//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
ios_base::sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= 2 * n; i++) {
if (2 * n + i <= m) {
cout << 2 * n + i << " ";
}
if (i <= m) {
cout << i << " ";
}
}
return 0;
}
C. Hard Process
思路
首先想到, 对于这一段(有0有1)的序列, 如果要真想使其中的答案最优的话。那么连续的一段0, 把他变成1的解肯定是要分着变成1的解要优的。
比如100110, k = 2时, 那么变成111110肯定比变成101111或110110要好。(其实因为如果你把隔开的0变成1的话, 那么中间那些没有变成0的1就可能中断你这个修改后最大的1的连续子序列)
那么其实我们就可以想到, 把这个序列里的0都提出来, 然后把连续的k个0变成1, 并放回原序列中求最大长度。
可以用尺取法。
对于每一个新的0, 如果当前还没有变到k个, 那么就把他变成0。如果已经变了k个了,那么就把最前面变得那个还原并及时更新答案。更新答案时顺便记录一下最长的1子序列的左右端点, 就可以很轻松的输出改变后的序列了。
代码
//
// main.cpp
// C
//
// Created by 陈博 on 2020/9/24.
//
#include<bits/stdc++.h>
using namespace std;
int n, k;
int a[300010];
int main()
{
cin >> n >> k;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
}
int l = 1;
int r = 1;
int num = 0;
int maxl = 0;
int maxr = 0;
int ans = 0;
int cnt = 0;
while(r <= n)
{
cnt++;
if(a[r] == 0)
{
num++;
}
if(num > k)
{
cnt--;
if(a[l] == 0)
{
num--;
}
l++;
}
if(cnt > ans)
{
ans = cnt;
maxl = l;
maxr = r;
}
r++;
}
cout << ans << endl;
for(int i = maxl; i <= maxr; i++)
{
a[i] = 1;
}
for(int i = 1; i <= n; i++)
{
cout << a[i] << " ";
}
cout << endl;
return 0;
}
巧妙的dalao做法
大佬把原数列变成了如下的意思:
对于原序列的每一个数x, 将他变成1的最小变换次数是多少
很清晰的就可以猜出, 最小变换次数 = 原数
⊕
\oplus
⊕ 1;
那么在这个新序列上进行尺取就会方便许多。你只需要判断当前的序列和是否大于k即可。
别忘了最后输出原序列的时候要异或回去, 并且把最长连续1子段进行处理。
#include <bits/stdc++.h>
using namespace std;
int n, k, ans, w, s, l, r, i;
int a[400400];
int main() {
//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
ans = -1;
l = 1; r = 0;
scanf("%d%d", &n, &k);
w = 1;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
a[i] ^= 1;
s += a[i];
while (s > k) {
s -= a[w++];
}
if (i - w + 1 > ans) {
ans = i - w + 1;
l = w;
r = i;
}
}
for (int i = 1; i <= n; i++) {
a[i] ^= 1;
}
for (int i = l; i <= r; i++) {
a[i] |= 1;
}
printf("%d\n", ans);
for (int i = 1; i <= n; i++) {
printf("%d ", a[i]);
}
return 0;
}
D. Number of Parallelograms
思路
首先来思考一下判断平行四边形的方法。
一个比较显然的方法就是:平行四边形的两对对角顶点的中点是重合的。(因为平行四边形的对角线互相平分);
根据这个, 我们可以处理出平面上的点两两组合成的线段的中点。并对每个中点计数。求和即可。
考虑当前这个点, 假设他是n条线段的中点, 那么以这个点为平行四边形的对角线交点可以组成多少个平行四边形呢?
这n条线段都可以看成是平行四边形的对角线 两条对角线可组成一个平行四边形,那么显然有
C
n
2
=
n
×
(
n
−
1
)
2
C_{n}^2 = \frac{n\times(n-1)}{2}
Cn2=2n×(n−1)个平行四边形。
可以用map把中点都存起来。(载体为pair)
代码
//
// main.cpp
// D
//
// Created by 陈博 on 2020/9/24.
//
#include<bits/stdc++.h>
using namespace std;
int n;
struct midpoint
{
double x;
double y;
}mid[2001010];
double x[2010];
double y[2010];
bool judge(int a, int b)
{
double x1 = mid[a].x;
double y1 = mid[a].y;
double x2 = mid[b].x;
double y2 = mid[b].y;
if(fabs(x1 - x2) < 1e-9 && fabs(y1 - y2) < 1e-9)
{
return 1;
}
return 0;
}
map<pair<double, double>, int> mp;
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i];
}
int p = 0;
for(int i = 1; i <= n; i++)
{
for(int j = i + 1; j <= n; j++)
{
//cout << i << " " << j << " " << p << endl;
mid[++p].x = (x[i] + x[j]) / 2.0;
mid[p].y = (y[i] + y[j]) / 2.0;
mp[make_pair(mid[p].x, mid[p].y)]++;
}
}
/* cout << p << endl;
for(int i= 1; i <= p; i++)
{
cout << mid[i].x << " " << mid[i].y << endl;
}*/
int ans = 0;
map<pair<double, double>, int>::iterator it;
for(it = mp.begin(); it != mp.end(); it++)
{
int num = it->second;
ans = ans + num * (num - 1) / 2;
}
cout << ans << endl;
return 0;
}
dalao的类似做法
一样的存储方式,只不过大佬是这样想的:
假如现在找到了一条新的过中点p的边, 那么他与之前的所有中点为p的线段都能组成平行四边形, 直接让答案加上即可。
#include <bits/stdc++.h>
using namespace std;
int n, i, j;
int x[2222], y[2222];
map<pair<int, int>, int> e;
long long ans;
long long c2(int n) {
return (1ll * n * (n - 1)) / 2;
}
int main() {
//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
ios_base::sync_with_stdio(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
ans += e[make_pair(x[i] + x[j], y[i] + y[j])];
e[make_pair(x[i] + x[j], y[i] + y[j])]++;
}
}
cout << ans;
return 0;
}
E. Different Subsets For All Tuples
思路
灵性推式子题, 请自品。
下面是式子风波:
首先, 这些可能的数列里面, 每个都有一个空串, 所以共有
m
n
m^n
mn个空串
只需要考虑非空的串了。
我们可以反过来填, 先选出我们要计数的子序列, 在还原整个子序列。
假设我们已经填好了一个子序列
x
1
,
x
2
,
x
3
.
.
.
x
k
x_1, x_2, x_3 ... x_k
x1,x2,x3...xk
由于是要考虑本质不同的子序列, 所以我们希望当前这种子序列在他还原完整个序列时是独一无二的。
假设
x
1
x_1
x1所在下标为j
首先考虑原序列的1 ~j-1中可以填哪些数, 发现除了
x
1
x_1
x1, 其他数都能填,因为如果当前位置可以填
x
1
x_1
x1的话, 那么当前位置就可以和选出的子序列(抛掉开头的
x
1
x_1
x1)组成一个一模一样的子序列就不能保证只记一次了。所以在
x
1
x_1
x1前面的每个数有(m-1)种值可供选择。
依此类推, 在
x
1
到
x
2
,
x
2
到
x
3
,
.
.
.
.
.
.
.
x
k
−
1
到
x
k
x_1 到 x_2, x_2到x_3,.......x_{k-1}到x_k
x1到x2,x2到x3,.......xk−1到xk间都有
m
−
1
m-1
m−1种选择。
顺次再来考虑从
x
k
到
末
尾
n
x_k到末尾n
xk到末尾n, 那么他也有m种选择了。
再来考虑子序列本身, 首先他的每个数的值都是有m种选择的, 然后, 他的位置可以从j+1到n中选择k-1个位置(第一个位置在j已经确定了)
所以我们经过如上的一波玄学推理,, 最终的方案数有分步乘法计数原理和 加法原理(枚举子序列长度与末尾位置)得 :
∑
k
=
1
n
∑
j
=
0
n
−
k
m
k
m
j
(
m
−
1
)
n
−
j
−
k
C
n
−
j
−
1
k
−
1
\sum_{k=1}^{n}\sum_{j=0}^{n-k}m^km^j(m-1)^{n-j-k}C_{n-j-1}^{k-1}
k=1∑nj=0∑n−kmkmj(m−1)n−j−kCn−j−1k−1整理一下
∑
k
=
1
n
∑
j
=
0
n
−
k
m
k
+
j
(
m
−
1
)
n
−
(
j
+
k
)
C
n
−
(
j
+
k
)
−
1
+
k
k
−
1
\sum_{k=1}^{n}\sum_{j=0}^{n-k}m^{k+j}(m-1)^{n-(j+k)}C_{n-(j+k)-1+k}^{k-1}
k=1∑nj=0∑n−kmk+j(m−1)n−(j+k)Cn−(j+k)−1+kk−1可以发现, 中间的那两项m和(m-1)可以以他为主体进行枚举,统计在两次cigema中那两项出现的次数。
那么, 设s = j + k; 进行打表暴力找规律可得:
∑
s
=
1
n
m
s
(
m
−
1
)
n
−
s
∑
k
=
0
s
−
1
C
n
−
s
+
k
k
\sum_{s=1}^nm^s(m-1)^{n-s}\sum_{k=0}^{s-1}C_{n-s+k}^{k}
s=1∑nms(m−1)n−sk=0∑s−1Cn−s+kk
对于后面的那一个cigema, 我们可以根据
∑
k
≤
n
C
r
+
k
k
=
C
r
+
n
+
1
n
\sum_{k \le n}C_{r+k}^{k} = C_{r+n+1}^{n}
∑k≤nCr+kk=Cr+n+1n化简:
∑
s
=
1
n
m
s
(
m
−
1
)
n
−
s
C
n
s
−
1
\sum_{s=1}^nm^s(m-1)^{n-s}C_n^{s-1}
s=1∑nms(m−1)n−sCns−1
这个式子再加上
m
n
m^n
mn就是答案。
对于中间那个式子的证明, 给一个提示
C
n
m
=
n
+
1
m
+
1
C
n
+
1
m
+
1
C_n^m = \frac{n+1}{m+1}C_{n+1}^{m+1}
Cnm=m+1n+1Cn+1m+1
在用反证法即可, 最后化简成
C
r
0
=
1
C_r^0 = 1
Cr0=1,显然成立。
对了,最后答案的那个式子还要取模(Lucas, ksm, 逆元等等)。。。
//
// main.cpp
// E
//
// Created by 陈博 on 2020/9/24.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, m;
ll ans = 0;
const ll mod = 1e9 + 7;
ll ksm(ll a, ll b)
{
ll res = 1;
while(b)
{
if(b & 1)
{
res = res * a % mod;
}
b >>= 1;
a = a * a % mod;
}
return res;
}
ll frac[1000010];
ll C(ll a, ll b)
{
return ((frac[a] * ksm(frac[b], mod - 2)) % mod * ksm(frac[a-b], mod-2) % mod);
}
ll lucas(ll a, ll b)
{
if(!b)
{
return 1;
}
return C(a % mod, b % mod) * lucas(a / mod, b / mod) % mod;
}
int main()
{
cin >> n >> m;
frac[0] = 1;
for(int i = 1; i <= n+2; i++)
{
frac[i] = (frac[i-1] * i) % mod;
// cout << i << " " << frac[i] << endl;
}
ans = ans + (ksm(m, n)) % mod;
for(int s = 1; s <= n; s++)
{
ll now = 1;
now = now * ksm(m, s) % mod * ksm(m-1, n-s) % mod * lucas(n, s-1) % mod;
//cout << frac[2] << " " << ksm(frac[1], mod - 2) % mod <<endl;
/*cout << C(2, 1) << endl;
cout << ksm(m, s) << " " << ksm(m-1, n-s) << " " << lucas(n+1,s) - 1 << endl;
cout << now << endl;*/
ans = (ans + now) % mod;
}
cout << ans << endl;
return 0;
}
dalao骚气做法
如果我们设j为
x
k
x_k
xk所在下标会有什么不同呢?
可得式子:
∑
k
=
1
n
∑
j
=
k
n
m
n
−
j
+
k
(
m
−
1
)
j
−
k
C
j
−
1
k
−
1
\sum_{k=1}^n\sum_{j=k}^nm^{n - j + k}(m-1)^{j-k}C_{j-1}^{k-1}
k=1∑nj=k∑nmn−j+k(m−1)j−kCj−1k−1改变枚举顺序:
∑
j
=
1
n
∑
k
=
1
j
m
n
−
j
+
k
(
m
−
1
)
j
−
k
C
j
−
1
k
−
1
\sum_{j=1}^n\sum_{k=1}^jm^{n - j + k}(m-1)^{j-k}C_{j-1}^{k-1}
j=1∑nk=1∑jmn−j+k(m−1)j−kCj−1k−1直接将枚举的k, j减1得:
∑
j
=
0
n
−
1
∑
k
=
0
j
−
1
m
n
−
(
j
+
1
)
+
(
k
+
1
)
(
m
−
1
)
(
j
+
1
)
−
(
k
+
1
)
C
j
k
\sum_{j=0}^{n-1}\sum_{k=0}^{j-1}m^{n - (j+1) + (k+1)}(m-1)^{(j+1)-(k+1)}C_{j}^{k}
j=0∑n−1k=0∑j−1mn−(j+1)+(k+1)(m−1)(j+1)−(k+1)Cjk化简得:
∑
j
=
0
n
−
1
∑
k
=
0
j
−
1
m
n
−
j
+
k
(
m
−
1
)
j
−
k
C
j
k
\sum_{j=0}^{n-1}\sum_{k=0}^{j-1}m^{n-j+k}(m-1)^{j-k}C_{j}^{k}
j=0∑n−1k=0∑j−1mn−j+k(m−1)j−kCjk提出
m
n
−
j
m^{n-j}
mn−j来:
∑
j
=
0
n
−
1
m
n
−
k
∑
k
=
0
j
−
1
m
k
(
m
−
1
)
j
−
k
C
j
k
\sum_{j=0}^{n-1}m^{n-k}\sum_{k=0}^{j-1}m^{k}(m-1)^{j-k}C_{j}^{k}
j=0∑n−1mn−kk=0∑j−1mk(m−1)j−kCjk后面那一项根据二项式定理得:
∑
j
=
0
n
−
1
m
n
−
j
(
(
m
−
1
)
+
m
)
j
=
∑
j
=
0
n
−
1
m
n
−
j
(
2
m
−
1
)
j
\sum_{j=0}^{n-1}m^{n-j}((m-1) + m)^j = \sum_{j = 0}^{n-1}m^{n-j}(2m-1)^j
j=0∑n−1mn−j((m−1)+m)j=j=0∑n−1mn−j(2m−1)j
哈哈哈哈啊哈哈哈哈哈终于推完了我疯了。
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define X 1000000007
#define Qinv(x) Qpow(x,X-2)
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m;
I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}//快速幂
int main()
{
RI i,ans,p1,p2,b1,b2;
scanf("%d%d",&n,&m),ans=p1=Qpow(m,n),p2=1,b1=Qinv(m),b2=(1LL*2*m-1)%X;//初始化
for(i=0;i^n;++i) Inc(ans,1LL*p1*p2%X),p1=1LL*p1*b1%X,p2=1LL*p2*b2%X;//O(n)计算答案
return printf("%d",ans),0;//输出答案
}
F Bear and Bowling 4
思路
设两个前缀和
s
u
m
1
,
s
u
m
2
sum1, sum2
sum1,sum2.
sum1表示
∑
a
[
i
]
\sum a[i]
∑a[i];
sum2表示
∑
i
∗
a
[
i
]
\sum i*a[i]
∑i∗a[i]
那么对于一段区间[l,r]的答案, 为
1
∗
a
[
l
]
+
2
∗
a
[
l
+
1
]
+
.
.
.
+
(
r
−
l
+
1
)
∗
a
[
r
]
1*a[l] + 2 * a[l+1] + ... + (r-l+1)*a[r]
1∗a[l]+2∗a[l+1]+...+(r−l+1)∗a[r]
可以看到,我们可以首先让sum2[r] - sum[l-1]得到l*a[l] + (l+1)a[l+1]+…+r * a[r];然后,我们就可以再减去(l-1)(sum1[r] - sum1[l-1])就得到答案了。
设dp[j]表示以j为右端点的最大答案。
那么, 我们可得到式子:
d
p
[
j
]
=
max
(
s
u
m
2
[
i
]
−
s
u
m
2
[
j
−
1
]
−
(
i
−
1
)
×
s
u
m
1
[
j
]
+
(
i
−
1
)
×
s
u
m
1
[
i
−
1
]
)
(
0
<
i
≤
j
)
dp[j] = \max(sum2[i] - sum2[j-1] - (i-1) \times sum1[j] + (i-1) \times sum1[i-1])(0 \lt i \le j)
dp[j]=max(sum2[i]−sum2[j−1]−(i−1)×sum1[j]+(i−1)×sum1[i−1])(0<i≤j)去掉max得
d
p
[
j
]
=
s
u
m
2
[
i
]
−
s
u
m
2
[
j
−
1
]
−
(
i
−
1
)
×
s
u
m
1
[
j
]
+
(
i
−
1
)
×
s
u
m
1
[
i
−
1
]
dp[j]=sum2[i] - sum2[j-1] - (i-1) \times sum1[j] + (i-1) \times sum1[i-1]
dp[j]=sum2[i]−sum2[j−1]−(i−1)×sum1[j]+(i−1)×sum1[i−1]因为是由i转移到j, 所以我们把i的项作为变量, j项与答案作为常数, ij项作为斜率与自变量可得:
(
i
−
1
)
×
s
u
m
1
[
i
−
1
]
−
s
u
m
2
[
i
−
1
]
=
s
u
m
1
[
j
]
×
(
i
−
1
)
+
d
p
[
j
]
−
s
u
m
2
[
j
]
(i-1)\times sum1[i-1]-sum2[i-1] = sum1[j] \times (i-1) + dp[j] - sum2[j]
(i−1)×sum1[i−1]−sum2[i−1]=sum1[j]×(i−1)+dp[j]−sum2[j]则在根据
y
=
k
x
+
b
y = kx + b
y=kx+b得到:
y
=
(
i
−
1
)
×
s
u
m
1
[
i
−
1
]
−
s
u
m
2
[
i
−
1
]
y = (i-1)\times sum1[i-1]-sum2[i-1]
y=(i−1)×sum1[i−1]−sum2[i−1]
k
=
s
u
m
1
[
j
]
k = sum1[j]
k=sum1[j]
x
=
(
i
−
1
)
x = (i-1)
x=(i−1)
b
=
d
p
[
j
]
−
s
u
m
2
[
j
]
b = dp[j] - sum2[j]
b=dp[j]−sum2[j]以(i-1)为横坐标, (i-1)*sum1[i-1] - sum2[i-1]为纵坐标, 单调队列维护上凸壳即可。
对了, 这里的斜率k = sum1[j]可能是负的, 所以不能简单的只取单调队列的队首, 所以我们应该维护整个凸壳, 并二分。
#include<bits/stdc++.h>
using namespace std;
int n;
int a[200010];
long long sum1[200010];
long long sum2[200010];
long long ans = 0;
int q[200010];
double y(int z)
{
return 1.0 * (z) * sum1[z] - sum2[z];
}
double x(int z)
{
return (z) * 1.0;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
sum1[i] = sum1[i-1] + a[i];
sum2[i] = sum2[i-1] + (long long)i * a[i];
}
int tail = 0;
for(int j = 1; j <= n; j++)
{
long long k = sum1[j];
int l = 0;
int r = tail;
while(l < r)
{
int mid = (l + r) >> 1;
if((y(q[mid]) - y(q[mid+1])) / (x(q[mid]) - x(q[mid+1])) > k)
{
l = mid + 1;
}
else
{
r = mid;
}
}
int i = q[r];
ans = max(ans, sum2[j] - sum2[i] - (i) * sum1[j] + (i) * sum1[i]);
while(tail >= 1 && (y(q[tail]) - y(q[tail - 1])) / (x(q[tail]) - x(q[tail-1])) < (y(q[tail]) - y(j)) / (x(q[tail]) - x(j)))
{
tail--;
}
q[++tail] = j;
}
cout << ans << endl;
return 0;
}