Solutions - NOISG 2020 重现赛

T1

主观难度:【0】

不会做的出门左转,一机房有初一小朋友正在学前缀和。

T2

主观难度:【1】

容易看出方程式 \(dp_i = dp_j + (n-j) \max_{k-j+1}^{i} a_k\)。这个式子是很容易想到斜率优化的。但是合并凸壳不太好做。

观察 Subtask 4,我们发现这个 Subtask 的答案显然等于 \(na_1\)。仔细思考后我们发现对于一个连续的不增子序列,设子序列开头为 \(i\),中间任一元素为 \(j\),如果你把 \(j\) 选为一段的开头那么在付出 \(a_i(n-i)\) 的代价之外你还需要付出额外的 \(a_j(n-j)\) 的代价,但是如果 \(i\) 的段包含了 \(j\) 那么只需要付出 \(a_i(n-i)\) 的代价。故对于一个连续的不增子序列,只选开头最优。

于是我们把这个序列变成了不降的,发现方程式变为了 \(dp_i = dp_j + (n-j)a_i\),斜率优化搞搞即可。

#include <bits/stdc++.h>
#define llong long long
#define N 1000006
using namespace std;

#define bs (1<<20)
char buf[bs], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,bs,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void read(T& x){
	x = 0; int w = 1;
	char ch = gc();
	while(ch < '0' || ch > '9'){
		if(ch == '-') w = -w;
		ch = gc();
	}
	while(ch >= '0' && ch <= '9')
		x = (x<<3)+(x<<1)+(ch^48), ch = gc();
	x *= w;
}
template<typename T, typename ...Args>
inline void read(T& x, Args& ...y){
	return read(x), read(y...);
}

int n;
llong a[N], dp[N];

struct Seg{
	llong k, b;
};
#define gety(s,x) (s.k*x+s.b)

Seg val[N<<6]; int ls[N<<6], rs[N<<6], tsiz, root;
#define mid ((l+r)>>1)

inline int newnode(){
	return ++tsiz;
}
inline void modify(Seg k, int& x = root, int l = 1, int r = 1e9){
	if(!x) return val[x = newnode()] = k, void();
	Seg k1 = val[x], k2 = k;
	if(gety(k1, mid) > gety(k2, mid)) swap(k1, k2);
	val[x] = k1;
	if(l == r) return;
	if(gety(k1, l) > gety(k2, l)) modify(k2, ls[x], l, mid  );
	if(gety(k1, r) > gety(k2, r)) modify(k2, rs[x], mid+1, r);
	return;
}
inline llong query(int pos, int& x = root, int l = 1, int r = 1e9){
	if(!x) return 1e18+3;
	llong res = gety(val[x], pos);
	if(pos <= mid) res = min(res, query(pos, ls[x], l, mid  ));
	else           res = min(res, query(pos, rs[x], mid+1, r));
	return res;
}

int main(){
//	freopen("in.txt", "r", stdin);
	read(n);
	for(int i = 1; i <= n; ++i) read(a[i]);
	for(int i = 1; i <= n; ++i) a[i] = max(a[i], a[i-1]);
	modify({n, 0});
	for(int i = 1; i <= n; ++i){
		dp[i] = query(a[i]);
		modify({n-i, dp[i]});
	}
	printf("%lld", dp[n]);
	return 0;
}

T3

主观难度:【0】

花了 2.5h 吃了一坨大的,非常美味。

容易想到维护斜率。但是碍于区间赋值操作,区间的两端不好维护。于是我们开两个线段树,一个维护实际值,一个维护斜率。就做完了。

时间复杂度 \(\mathrm O(n \log n)\)

代码长度 6.5k。好大一坨。

#include <bits/stdc++.h>
#define llong long long
#define N 300005
using namespace std;

#define bs (1<<20)
char buf[bs], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,bs,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void read(T& x){
    x = 0; int w = 1;
    char ch = gc();
    while(ch < '0' || ch > '9'){
        if(ch == '-') w = -w;
        ch = gc();
    }
    while(ch >= '0' && ch <= '9')
        x = (x<<3)+(x<<1)+(ch^48), ch = gc();
    x *= w;
}
template<typename T, typename ...Args>
inline void read(T& x, Args& ...y){
    return read(x), read(y...);
}

int n, q;
int a[N];

#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define mid ((l+r)>>1)

class SegT1{
    private:
    struct Seg{llong k, b; bool vis;};
    Seg val[N<<2], tag1[N<<2], tag2[N<<2];
    inline void addtag1(int x, Seg k){
        tag2[x] = {0, 0, false};
        val[x] = tag1[x] = k;
        return;
    }
    inline void addtag2(int x, Seg k){
        val[x].k += k.k, tag2[x].k += k.k;
        val[x].b += k.b, tag2[x].b += k.b;
        tag2[x].vis = true;
        return;
    }
    inline void pushdown(int x){
        if(tag1[x].vis){
            addtag1(ls(x), tag1[x]);
            addtag1(rs(x), tag1[x]);
            tag1[x] = {0, 0, false};
        }
        if(tag2[x].vis){
            addtag2(ls(x), tag2[x]);
            addtag2(rs(x), tag2[x]);
            tag2[x] = {0, 0, false};
        }
        return;
    }
    
    public:
    inline void build(int x = 1, int l = 1, int r = n){
    	tag1[x] = tag2[x] = {0, 0, false};
        if(l == r) return addtag1(x, {0, a[l], true});
        build(ls(x), l, mid), build(rs(x), mid+1, r);
        return;
    }
    inline void modify1(int L, int R, Seg k, int x = 1, int l = 1, int r = n){
        if(L <= l && R >= r) return addtag1(x, k);
        pushdown(x);
        if(L <= mid) modify1(L, R, k, ls(x), l, mid  );
        if(R >  mid) modify1(L, R, k, rs(x), mid+1, r);
        return;
    }
    inline void modify2(int L, int R, Seg k, int x = 1, int l = 1, int r = n){
        if(L <= l && R >= r) return addtag2(x, k);
        pushdown(x);
        if(L <= mid) modify2(L, R, k, ls(x), l, mid  );
        if(R >  mid) modify2(L, R, k, rs(x), mid+1, r);
        return;
    }
    inline llong query(int pos, int x = 1, int l = 1, int r = n){
        if(l == r) return val[x].k*pos+val[x].b;
        pushdown(x);
        if(pos <= mid) return query(pos, ls(x), l, mid  );
        else           return query(pos, rs(x), mid+1, r);
    }
    inline llong operator[](int pos){
        return query(pos);
    }
} val;

class SegT2{
    private:
    struct Node{
        int l, r; int val, len1, len2;
        llong val1, val2;
        bool vis;
    };
    Node val[N<<2]; llong tag1[N<<2], tag2[N<<2];
    const llong NOTAG = (llong)1e18+3;
    
    Node merge(Node a, Node b){
        if(!a.vis) return b;
        if(!b.vis) return a;
        Node res; res.vis = true;
        res.l = a.l, res.r = b.r;
        res.val = max(a.val, b.val);
        if(a.val2 == b.val1) res.val = max(res.val, a.len2+b.len1);
        res.val1 = a.val1, res.val2 = b.val2;
        if(a.len1 == a.r-a.l+1 && a.val1 == b.val1) res.len1 = a.len1+b.len1;
        else res.len1 = a.len1;
        if(b.len2 == b.r-b.l+1 && a.val2 == b.val2) res.len2 = a.len2+b.len2;
        else res.len2 = b.len2;
        return res;
    }
    
    inline void pushup(int x){
        val[x] = merge(val[ls(x)], val[rs(x)]);
        return;
    }
    inline void addtag1(int x, llong k){
    	tag1[x] = k, tag2[x] = NOTAG;
        Node res;
        res.l = val[x].l, res.r = val[x].r;
        res.val1 = res.val2 = k;
        res.val = res.len1 = res.len2 = val[x].r-val[x].l+1;
        res.vis = true;
        val[x] = res;
        return;
    }
    inline void addtag2(int x, llong k){
    	if(tag2[x] == NOTAG) tag2[x] = 0;
        val[x].val1 += k, val[x].val2 += k;
        tag2[x] += k;
        return;
    }
    inline void pushdown(int x){
        if(tag1[x] != NOTAG){
            addtag1(ls(x), tag1[x]);
            addtag1(rs(x), tag1[x]);
            tag1[x] = NOTAG;
        }
        if(tag2[x] != NOTAG){
            addtag2(ls(x), tag2[x]);
            addtag2(rs(x), tag2[x]);
            tag2[x] = NOTAG;
        }
    }
    
    public:
    inline void build(int x = 1, int l = 1, int r = n-1){
        tag1[x] = tag2[x] = NOTAG;
        if(l == r) return val[x] = {l, r, 1, 1, 1, a[l+1]-a[l], a[l+1]-a[l], true}, void();
        build(ls(x), l, mid), build(rs(x), mid+1, r);
        pushup(x);
    }
    inline void modify1(int L, int R, llong k, int x = 1, int l = 1, int r = n-1){
        if(L <= l && R >= r) return addtag1(x, k);
        pushdown(x);
        if(L <= mid) modify1(L, R, k, ls(x), l, mid  );
        if(R >  mid) modify1(L, R, k, rs(x), mid+1, r);
        pushup(x);
        return;
    }
    inline void modify2(int L, int R, llong k, int x = 1, int l = 1, int r = n-1){
        if(L <= l && R >= r) return addtag2(x, k);
        pushdown(x);
        if(L <= mid) modify2(L, R, k, ls(x), l, mid  );
        if(R >  mid) modify2(L, R, k, rs(x), mid+1, r);
        pushup(x);
        return;
    }
    inline Node query(int L, int R, int x = 1, int l = 1, int r = n-1){
        if(L <= l && R >= r) return val[x];
        pushdown(x);
        Node res; res.vis = false;
        if(L <= mid) res = merge(res, query(L, R, ls(x), l, mid  ));
        if(R >  mid) res = merge(res, query(L, R, rs(x), mid+1, r));
        return res;
    }
    inline int evaluate(int L, int R){
        return query(L, R-1).val+1;
    }
} seg;

// In all SegT options, 1 = cover, 2 = add

inline int solve1(){
    while(q--){
        int op, l, r, k, b;
        read(op, l, r);
        if(op == 1 || op == 2) read(k, b);
        if(op == 3) puts("1");
    }
    return 0;
}

int main(){
    // freopen("in.txt", "r", stdin);
    read(n, q);
    for(int i = 1; i <= n; ++i) read(a[i]);
    if(n == 1) return solve1();
    val.build(), seg.build();
    while(q--){
        int op, l, r; read(op, l, r);
        if(op == 1){ // Add
            llong k, b; read(b, k);
            b -= l*k;
            val.modify2(l, r, {k, b, true});
            if(l != r) seg.modify2(l, r-1, k);
            if(l != 1) seg.modify1(l-1, l-1, val[l]-val[l-1]);
            if(r != n) seg.modify1(r,   r,   val[r+1]-val[r]);
        }
        if(op == 2){ // Cover
            llong k, b; read(b, k);
            b -= l*k;
            val.modify1(l, r, {k, b, true});
            if(l != r) seg.modify1(l, r-1, k);
            if(l != 1) seg.modify1(l-1, l-1, val[l]-val[l-1]);
            if(r != n) seg.modify1(r,   r,   val[r+1]-val[r]);
        }
        if(op == 3){ // Evaluate
            if(l == r) puts("1");
            else printf("%d\n", seg.evaluate(l, r));
        }
    }
    return 0;
}

T4

主观难度:【1+】

模拟赛场上糖了。

我们把每个点位能到达的区域画在图上,发现是从原点开始向左下和右下无限延伸的两条射线组成的图形。这不太好看,于是我们令 \(x_i = t_i+a_i, y_i = t_i-a_i\),坐标系被我们旋转了 \(45 \degree\)(顺便还放大到了 \(\sqrt{2}\),但这不重要)。于是发现一个点可达另一个点当且仅当 \(x_i \le x_j \land y_i \le y_j\)。这下方便多了。

然而 Hootime 场上做到这里不会做了。

其实接下来要求的就是最小链覆盖。我们通过 Dilworth 定理将其转化为最长反链,于是这题就变成了导弹拦截,用你喜欢的方式求出 LIS 即可。

就做完了。

#include <bits/stdc++.h>
#define llong long long
#define N 500005
using namespace std;

#define bs (1<<20)
char buf[bs], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,bs,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void read(T& x){
	x = 0; int w = 1;
	char ch = gc();
	while(ch < '0' || ch > '9'){
		if(ch == '-') w = -w;
		ch = gc();
	}
	while(ch >= '0' && ch <= '9')
		x = (x<<3)+(x<<1)+(ch^48), ch = gc();
	x *= w;
}
template<typename T, typename ...Args>
inline void read(T& x, Args& ...y){
	return read(x), read(y...);
}

int n, m;
struct Node{int x, y;};
Node a[N];
int tmp[N], cnt;

int main(){
	freopen("in.txt", "r", stdin);
	read(m, n);
	for(int i = 1; i <= n; ++i) read(a[i].x);
	for(int i = 1; i <= n; ++i) read(a[i].y);
	for(int i = 1; i <= n; ++i) a[i] = {a[i].x+a[i].y, a[i].x-a[i].y};
	sort(a+1, a+n+1, [&](Node o1, Node o2){return o1.x>o2.x || (o1.x==o2.x&&o1.y>o2.y);});
	for(int i = 1; i <= n; ++i){
		int pos = lower_bound(tmp+1, tmp+cnt+1, a[i].y)-tmp;
		tmp[pos] = a[i].y;
		if(pos > cnt) ++cnt;
	}
	printf("%d", cnt);
	return 0;
}

T5

主观难度:【2】

脑子抽了导致没看懂题解。

我们建出最短路树。然后答案肯定只有走最短路加权和不走最短路。

我们枚举每条非树边 \((u, v)\),容易发现 \((u, v)\) 可以替换最短路上的一段。我们记其在最短路上的祖先与最短路第一次相交的节点为 \(b_u, b_v\),容易发现路径 \(1 \to u \to v \to n\) 与最短路不同的段只有 \(b_u \to u \to v \to b_v\)。于是我们在 \(b_u\) 标记一下有一条距离为距离为 \(dis_{1,u}+len_{(u, v)}+dis_{v, n}\) 的路径可以替换 \(b_u\) 到以后某点的路径,在 \(b_v\) 删除标记。

然后我们枚举最短路上的边,该边被伸长后的最短路即为 \(\min\{ dis_{1, n}+w_i, \min_{k \in s_i} k \}\),其中 \(k\) 为可替换该边的路径长度,\(w_i\) 为可对该边加权的边的最大权。

然后对所有东西取 \(\max\) 即可。

复杂度 \(\mathrm O(n \log n)\)

Tip:其实 multiset 可以当可删堆用。

#include <bits/stdc++.h>
#define llong long long
#define N 300005
#define M 300005
using namespace std;

#define bs (1<<20)
char buf[bs], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,bs,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void read(T& x){
	x = 0; int w = 1;
	char ch = gc();
	while(ch < '0' || ch > '9'){
		if(ch == '-') w = -w;
		ch = gc();
	}
	while(ch >= '0' && ch <= '9')
		x = (x<<3)+(x<<1)+(ch^48), ch = gc();
	x *= w;
}
template<typename T, typename ...Args>
inline void read(T& x, Args& ...y){
	return read(x), read(y...);
}

int n, m, l;
int to[M<<1], nxt[M<<1], id[M<<1], tag[M<<1], head[N], gsiz; llong val[M<<1];
#define mkarc(u,v,w1,w2) (++gsiz, to[gsiz]=v, val[gsiz]=w1, id[gsiz]=w2, nxt[gsiz]=head[u], head[u]=gsiz)
int fa[N], bel[N], vis[N], sta[N], nid[N], eid[N], top;
llong eval[N], dis1[N], dis2[N], ans;

typedef pair<llong, int> Node;
priority_queue<Node, vector<Node>, greater<Node> > pq;

vector<llong> tmp1[N], tmp2[N];
multiset<llong> tmp;

int main(){
	freopen("in.txt", "r", stdin);
	read(n, m);
	for(int i = 1; i <= m; ++i){
		int u, v; llong w; read(u, v, w);
		mkarc(u, v, w, i), mkarc(v, u, w, i);
		eval[i] = w;
	}
	for(int i = m; i >= 1; --i) eval[i] = max(eval[i], eval[i+1]);
	for(int i = 1; i <= n; ++i) dis1[i] = dis2[i] = (llong)1e18+3;
	dis1[1] = 0, pq.emplace(0, 1);
	while(pq.size()){
		int u = pq.top().second; pq.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			if(dis1[u]+val[i] < dis1[v]){
				dis1[v] = dis1[u]+val[i], fa[v] = u;
				pq.emplace(dis1[v], v);
			}
		}
	}
	for(int i = 1; i <= n; ++i) vis[i] = false;
	dis2[n] = 0, pq.emplace(0, n);
	while(pq.size()){
		int u = pq.top().second; pq.pop();
		if(vis[u]) continue;
		vis[u] = true;
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			if(dis2[u]+val[i] < dis2[v]){
				dis2[v] = dis2[u]+val[i];
				pq.emplace(dis2[v], v);
			}
		}
	}
	for(int i = 1; i <= n; ++i) vis[i] = false;
	int now = n;
	while(now != 0) bel[now] = now, vis[now] = true, now = fa[now];
	for(int i = 2; i <= n; ++i){
		int j = i;
		while(!bel[j]) sta[++top] = j, j = fa[j];
		while(top) bel[sta[top]] = bel[j], --top;
	}
	now = n;
	while(now != 0) sta[++l] = now, now = fa[now];
	reverse(sta+1, sta+l+1);
	nid[n] = l;
	for(int u = n; u != 1; u = fa[u]){
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			if(v != fa[u]) continue;
			eid[v] = id[i], nid[v] = nid[u]-1;
			break;
		}
	}
	for(int u = 1; u <= n; ++u){
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			if(fa[u] == v || fa[v] == u) continue;
			if(bel[u] == bel[v]) continue;
			if(nid[bel[u]] > nid[bel[v]]) continue;
			tmp1[nid[bel[u]]].push_back(dis1[u]+val[i]+dis2[v]);
			tmp2[nid[bel[v]]].push_back(dis1[u]+val[i]+dis2[v]);
		}
	}
	for(int i = 1; i < l; ++i){
		for(llong val : tmp1[i]) tmp.insert(val);
		for(llong val : tmp2[i]) tmp.erase(tmp.lower_bound(val));
		ans = max(ans, min(dis1[n]+eval[eid[sta[i]]+1], tmp.size() ? *tmp.begin() : (llong)1e18+3));
	}
	printf("%lld", ans);
	return 0;
}

posted @ 2026-03-02 20:49  Hootime  阅读(5)  评论(0)    收藏  举报