Codeforces Round #343 (Div. 2)
题目链接:https://codeforces.com/contest/629
说起来经过一个月分数不升反降,道理很简单,前面起飞的是数据结构和数论场,假如连续整点图论就一直降落了。没事多积累一下知道在进步。
A - Far Relative’s Birthday Cake
水题,随便组合一下。
B - Far Relative’s Problem
水题,不过题意有点混乱,搞点差分就行。
*C - Famil Door and Brackets
题意:给一个长度为 \(m(1\leq m \leq n)\) 的括号串 \(s\) ,在它前面和后面加上可以为空的前缀 \(p\) 和后缀 \(q\) ,使其结果为恰好为长度为 \(n(1\leq n \leq 2\cdot 10^5)\) 的合法括号串。限制 \(n-m\leq2000\) 。
题解:由于这个限制,所以可以用个 \(O(n^2)\) 的算法。先统计出整个 \(s\) 串的左右差和最小左右差。然后就可以设计一些转移。
不过越界真的好烦耶!
设 \(dp1[i][j]\) 为前缀放了i个,左右差为j的方案数,由必须合法可知j>=0。
设 \(dp2[i][j]\) 为前缀和后缀共放了i个,左右差为j的方案数,由必须合法可知j>=0。
由必须合法可知,从前缀加上 \(s\) 后要求结果仍>=0,故必须从 \(j\geq minsum\) 的前缀,转移到 \(j+sum\) 的后缀,且这里不能够越界!
int n, m;
char s[100005];
ll dp1[2005][2005];
ll dp2[2005][2005];
void test_case() {
int n, m;
scanf("%d%d%s", &n, &m, s + 1);
int sum = 0, minsum = 0;
for(int i = 1; i <= m; ++i) {
if(s[i] == '(')
++sum;
else {
--sum;
minsum = min(minsum, sum);
}
}
dp1[0][0] = 1;
int c = 2000;
for(int i = 1; i <= c; ++i) {
for(int j = 0; j <= c; ++j) {
dp1[i][j] += (j > 0 ? dp1[i - 1][j - 1] : 0) + dp1[i - 1][j + 1];
if(dp1[i][j] >= MOD)
dp1[i][j] %= MOD;
}
}
// for(int i = 0; i <= c; ++i) {
// for(int j = 0; j <= c; ++j)
// printf("dp1[%d][%d]=%lld\n", i, j, dp1[i][j]);
// puts("");
// }
// puts("---");
for(int j = -minsum; j + sum <= c && j <= c; ++j) {
for(int k = 0; k <= c; ++k)
dp2[k][j + sum] += dp1[k][j];
}
// for(int i = 0; i <= c; ++i) {
// for(int j = 0; j <= c; ++j)
// printf("dp2[%d][%d]=%lld\n", i, j, dp2[i][j]);
// puts("");
// }
// puts("---");
for(int i = 1; i <= c; ++i) {
for(int j = 0; j <= c; ++j) {
dp2[i][j] += (j > 0 ? dp2[i - 1][j - 1] : 0) + dp2[i - 1][j + 1];
if(dp2[i][j] >= MOD)
dp2[i][j] %= MOD;
}
}
// for(int i = 0; i <= c; ++i) {
// for(int j = 0; j <= c; ++j)
// printf("dp2[%d][%d]=%lld\n", i, j, dp2[i][j]);
// puts("");
// }
// puts("---");
printf("%lld\n", dp2[n - m][0] % MOD);
}
*D - Babaei and Birthday Cake
粗看像单调队列优化dp什么的,仔细分析。
题意:有 \(n(1\leq n\leq 10^5)\) 个圆柱体蛋糕,其中第 \(i\) 个蛋糕要么放在桌子上,要么放在一个 \(j<i\) 且 \(V_j<V_i\) 的蛋糕上。求最大体积的蛋糕。
题解:意思是什么面积和高都是没用的,全部算体积。看起来也只和最后一个蛋糕有关。
设 \(dp[i]\) 表示以第 \(i\) 个蛋糕结尾的最大体积,则: \(dp[i]=V_i+\max\limits_{j=0\; and \; V_j<V_i}^{i-1}dp[j]\) ,要求小于某个key的value的最小值。这不是平衡树吗?仔细想想权值线段树也可做。
注意到其实可以把圆周率提取出来,所以是准确运算。而且相同的V值,后面的结果肯定不会比前面更差。(所以修改的时候直接赋值而不是取max)
const double eps = 1e-8;
const double PI = acos(-1.0);
struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
static const int MAXN = 100000;
ll mx[(MAXN << 2) + 5];
void PushUp(int o) {
mx[o] = max(mx[ls], mx[rs]);
}
void Build(int o, int l, int r) {
if(l == r)
mx[o] = 0;
else {
int m = l + r >> 1;
Build(ls, l, m);
Build(rs, m + 1, r);
PushUp(o);
}
}
void Update(int o, int l, int r, int p, ll v) {
if(l == r) {
mx[o] = v;
return;
} else {
int m = l + r >> 1;
if(p <= m)
Update(ls, l, m, p, v);
if(p >= m + 1)
Update(rs, m + 1, r, p, v);
PushUp(o);
}
}
ll Query(int o, int l, int r, int ql, int qr) {
if(ql <= l && r <= qr) {
return mx[o];
} else {
int m = l + r >> 1;
ll res = 0;
if(ql <= m)
res = Query(ls, l, m, ql, qr);
if(qr >= m + 1)
res = max(res, Query(rs, m + 1, r, ql, qr));
return res;
}
}
#undef ls
#undef rs
} st;
int n;
ll V[100005];
ll v[100005];
int id[100005];
void test_case() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
ll r, h;
scanf("%lld%lld", &r, &h);
V[i] = v[i] = r * r * h;
}
sort(v + 1, v + 1 + n);
int c = unique(v + 1, v + 1 + n) - (v + 1);
for(int i = 1; i <= n; ++i)
id[i] = lower_bound(v + 1, v + 1 + c, V[i]) - v;
st.Build(1, 1, c);
for(int i = 1; i <= n; ++i) {
if(id[i] == 1) {
st.Update(1, 1, c, 1, V[i]);
continue;
} else {
ll res = st.Query(1, 1, c, 1, id[i] - 1);
st.Update(1, 1, c, id[i], V[i] + res);
continue;
}
}
printf("%.12f\n", PI * st.Query(1, 1, c, 1, c));
}
收获:检查线段树的下标,不要写错线段树的下标!