abc426 题解

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]\),可以分为三类:

  1. \(j<mid\),这类我们放到 \([l,mid]\) 分治
  2. \(i>mid\),这类我们放到 \([mid+1,r]\) 分治
  3. 剩下的,很显然 \(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]);
}
posted @ 2025-10-05 11:48  Hirasawayuiii  阅读(27)  评论(0)    收藏  举报