abc426 题解
abc426 题解
赛时 ABCD,E 被卡精度卡了30min,21:41 改为 long double 过了 /ll
A
水题,懒得写
B
同上
C
拿树状数组硬跑,每次记录当前的 \(x\) 的最大值(即当前序列最大值)
for(int i = 1; i <= n; i++) add(i, 1);
int lim = 0;
while(q--) {
int x = rd(), y = rd();
if(x <= lim) {
wr(0);
continue;
}
int ans = ask(x)-ask(lim);
add(y, ans);
lim = x;
wr(ans);
}
D
不难发现最优情况一定是固定序列中最长的一段 \(0/1\),然后将剩下的 \(0/1\) 计算答案
具体实现即对于固定 \(0/1\) 的最长段分别跑一遍,记录答案最小值
以当前处理 \(0\) 的最长段为例,答案即为 除最长段以外的所有 \(0\) 的个数 \(\times2\)(变为 \(1\) 再变回为 \(0\))+ 所有 \(1\) 的个数
for(int i = 1; i <= n; i++) {
if(s[i] == '0') {
cnt0++;
f[i][0] = f[i-1][0]+1;
f[i][1] = 0;
max0 = max(max0, f[i][0]);
}
else {
cnt1++;
f[i][0] = 0;
f[i][1] = f[i-1][1]+1;
max1 = max(max1, f[i][1]);
}
}
ans = min(cnt1+(cnt0-max0)*2, cnt0+(cnt1-max1)*2);
wr(ans);
E
题意即为两动点找最小值,不难发现整个过程可分为两段,即两点都在动和只有一点在动。并且,根据数学知识,距离与时间的关系为二次函数的形式,所以可以通过三分或直接求解来解决
不过我忘了三分咋写了,于是就用二次函数硬解了
根据向量相关知识,我们设:
\(td=tg-ts,ad=ag-as\)
\(tv=\frac{td}{|td|},av=\frac{ad}{|ad|}\)(即方向向量)
设二次函数为 \(y=ax^2+bx+c\)
第一段(两点都动)中,对应的参数即为
\(a=(tv-ta)^2\)
\(b=(ts-as)\cdot(tv)\)
\(c=(ts-as)^2\)
\(x\in[0,\min(|ta|,|ad|)]\)
根据数学知识直接求最小值
第二段中,我们设 \(|ta|<|ad|\)
\(a=1\)
\(b=2\times(ts-ag)\cdot(tv)\)
\(c=(ts-ag)^2\)
\(x\in[\min(|ta|,|ad|),\max(|ta|,|ad|)]\)
\(|ta|>|td|\) 的情况同理
于是得出答案
不过要注意各种精度问题,包括但不限于 eps,long double 等的应用
cin >> ts.x >> ts.y >> tg.x >> tg.y >> as.x >> as.y >> ag.x >> ag.y;
long double dist, disa, ans;
pos td = (tg-ts), ad = (ag-as), tv, av;
dist = dis(td);
disa = dis(ad);
if(dist > eps) tv = (1.0/dist)*td;
else tv = {0, 0};
if(disa > eps) av = (1.0/disa)*ad;
else av = {0, 0};
long double a = dot(tv-av, tv-av);
long double b = dot(ts-as, tv-av)*2;
long double c = dot(ts-as, ts-as);
long double lim = min(dist, disa);
ans = cal(a, b, c, 0, lim);
if(dist > disa+eps) {
a = 1;
b = 2*dot(ts-ag, tv);
c = dot(ts-ag, ts-ag);
ans = min(ans, cal(a, b, c, lim, dist));
}
if(disa > dist+eps) {
a = 1;
b = -2*dot(tg-as, av);
c = dot(tg-as, tg-as);
ans = min(ans, cal(a, b, c, lim, disa));
}
printf("%.6Lf\n", ans);
F
不难发现,答案即为大于 \(k\) 的个数乘 \(k\) 加上所有不大于 \(k\) 的值
考虑线段树解决
线段树维护区间最小值和区间正数的数量
每次操作处理所有不大于 \(k\) 的值,然后将其赋值为 \(\inf\)
然后查询区间减去 \(k\) 后正数的数量
最后区间减掉 \(k\)
由于每个 \(a_i\) 只会变为负数一次,所以复杂度是对的
void pushup(ll p) {
t[p].minn = min(t[ls(p)].minn, t[rs(p)].minn);
t[p].cnt = t[ls(p)].cnt+t[rs(p)].cnt;
}
void pushdown(ll p) {
if(t[p].lzy == 0) return;
t[ls(p)].minn += t[p].lzy;
t[rs(p)].minn += t[p].lzy;
t[ls(p)].lzy += t[p].lzy;
t[rs(p)].lzy += t[p].lzy;
t[p].lzy = 0;
}
void build(ll p, ll l, ll r) {
t[p].l = l, t[p].r = r;
if(l == r) {
t[p].minn = a[l], t[p].cnt = 1;
return;
}
ll mid = (l+r) >> 1;
build(ls(p), l, mid);
build(rs(p), mid+1, r);
pushup(p);
}
void modify(ll p, ll l, ll r, ll v) {
if(l <= t[p].l && t[p].r <= r) {
t[p].minn += v;
t[p].lzy += v;
if(t[p].minn <= 0) t[p].cnt = 0;
return;
}
ll mid = (t[p].l+t[p].r) >> 1;
pushdown(p);
if(l <= mid) modify(ls(p), l, r, v);
if(mid < r) modify(rs(p), l, r, v);
pushup(p);
}
ll update(ll p, ll l, ll r, ll v) {
if(t[p].minn > v) return 0;
if(t[p].l == t[p].r) {
ll ret = t[p].minn;
t[p].cnt = 0;
t[p].minn = inf;
return ret;
}
pushdown(p);
ll mid = (t[p].l+t[p].r) >> 1, res = 0;
if(l <= mid) res += update(ls(p), l, r, v);
if(mid < r) res += update(rs(p), l, r, v);
pushup(p);
return res;
}
ll query(ll p, ll l, ll r) {
if(l <= t[p].l && t[p].r <= r) return t[p].cnt;
pushdown(p);
ll mid = (t[p].l+t[p].r) >> 1, res = 0;
if(l <= mid) res += query(ls(p), l, r);
if(mid < r) res += query(rs(p), l, r);
return res;
}
void solve() {
n = rd();
for(ll i = 1; i <= n; i++) a[i] = rd();
build(1, 1, n);
q = rd();
while(q--) {
ll l = rd(), r = rd(), k = rd(), ans = 0;
ans = update(1, l, r, k)+k*query(1, l, r);
wr(ans);
modify(1, l, r, -k);
}
}
G
题意即为多次询问区间 01 背包问题
考虑从分治入手
首先考虑两个 01 背包(可选物品不同)如何合并
很显然,记录前缀最大值,然后合并,即:
\(f_{i,j}=\max_{k=1}^jf_{i,k},g_{i,j}=\max_{k=1}^jg_{i,k}\)
\(h_{i,j}=max_{k=1}^j(f_{i,k}+g_{i,j-k})\)
回到原题,将所有问题先离线处理
设目前分治区间为 \([l,r]\),对于所有问题 \([i,j]\in[l,r]\),可以分为三类:
- \(j<mid\),这类我们放到 \([l,mid]\) 分治
- \(i>mid\),这类我们放到 \([mid+1,r]\) 分治
- 剩下的,很显然 \(mid\in[i,j]\),我们现在处理
设 \(f_{i,j}\) 为 \([i,mid]\) 内的物品容量为 \(j\) 的最大值
设 \(g_{i,j}\) 为 \([mid+1,i]\) 内的物品容量为 \(j\) 的最大值
则对于询问 \(l,r,c\),其答案即为我们上面提到的 \(f_{l,c}\) 与 \(g_{r,c}\) 的合并
时间复杂度为 \(O(\sum len\times c)=O(cn\log n)\)
这种方法也被称为“猫树分治”
具体细节见代码
void solv(ll L, ll R, vector <ques> q) {
if(L == R) {
for(auto [l, r, c, id] : q) ans[id] = (c>=h[l])?w[l]:0;
return;
}
// 处理问题
ll mid = (L+R) >> 1;
vector <ques> q1, q2, qq;
for(auto Q : q) {
if(Q.r <= mid) q1.eb(Q);
else if(Q.l > mid) q2.eb(Q);
else qq.eb(Q);
}
solv(L, mid, q1);
solv(mid+1, R, q2);
// 计算前后缀
for(ll i = mid+1; i >= L; i--) for(ll j = 0; j <= 500; j++) f[i][j] = 0;
for(ll i = mid; i >= L; i--) {
for(ll j = 500; j >= 0; j--) {
if(j >= h[i]) f[i][j] = max(f[i+1][j], f[i+1][j-h[i]]+w[i]);
else f[i][j] = f[i+1][j];
}
}
for(ll i = mid; i <= R; i++) for(ll j = 0; j <= 500; j++) g[i][j] = 0;
for(ll i = mid+1; i <= R; i++) {
for(ll j = 500; j >= 0; j--) {
if(j >= h[i]) g[i][j] = max(g[i-1][j], g[i-1][j-h[i]]+w[i]);
else g[i][j] = g[i-1][j];
}
}
// 处理当前问题
for(auto [l, r, c, id] : qq) {
for(ll i = 0; i <= c; i++) ans[id] = max(ans[id], f[l][i]+g[r][c-i]);
}
}
void solve() {
n = rd();
for(ll i = 1; i <= n; i++) h[i] = rd(), w[i] = rd();
m = rd();
vector <ques> q;
for(ll i = 1; i <= m; i++) {
ll l = rd(), r = rd(), c = rd();
q.eb((ques){l, r, c, i});
}
solv(1, n, q);
for(ll i = 1; i <= m; i++) wr(ans[i]);
}