Solutions - NOISG 2023 重现赛

目前唯一一次教练来查成绩的重现赛,同时也是打最烂的一场。

Markdown. 加粗符号位置随机没有特殊意义。


T1

主观难度:【1】

数据范围坑害 Hootime 想了半个小时的根号分治。

不算难的贪心。我们维护 \(k+1\) 个小根堆,第一关键字为当前该项目的能力值,如果当前能力达标就扔进下一个堆里,将堆 \(k+1\) 的贡献加进当前能力,重复直到没有可更新的值。

显然一个元素只能进 \(k\) 次,复杂度 \(\mathrm O(nk \log n)\)

#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, k, cnt;
vector<llong> a[N], b[N];
llong now[N];

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

int main(){
	freopen("in.txt", "r", stdin);
	read(n, k);
	for(int i = 1; i <= n; ++i)
		a[i].push_back(0), b[i].push_back(0);
	for(int i = 1, x; i <= n; ++i)
		for(int j = 1; j <= k; ++j)
			read(x), a[i].push_back(x);
	for(int i = 1, x; i <= n; ++i)
		for(int j = 1; j <= k; ++j)
			read(x), b[i].push_back(x);
	for(int i = 1; i <= n; ++i)
		a[i].push_back(0), b[i].push_back(0);
	for(int i = 1; i <= n; ++i) pq[1].emplace(a[i][1], i);
	do{
		while(pq[k+1].size()){
			++cnt;
			int u = pq[k+1].top().second; pq[k+1].pop();
			for(int i = 1; i <= k; ++i) now[i] += b[u][i];
		}
		for(int i = 1; i <= k; ++i){
			while(pq[i].size() && now[i] >= pq[i].top().first){
				pq[i+1].emplace(a[pq[i].top().second][i+1], pq[i].top().second);
				pq[i].pop();
			}
		}
	} while(pq[k+1].size());
	printf("%d", cnt);
	return 0;
}

T2

主观难度:【0e】

ODT 题。Hootime 用了太多 STL 被反噬了。

#include <bits/stdc++.h>
#define llong long long
#define N 200005
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, q;
llong tot, ans[N];

typedef pair<llong, int> Query;
typedef tuple<llong, int> Res;
typedef tuple<int, int, llong> Node;
set<Node> s;
multiset<Res, less<Res> > res;
Query qry[N];

int main(){
	freopen("in.txt", "r", stdin);
	read(n, m, q);
	s.emplace(1, n, (llong)1e18+3);
	llong cnt = 1;
	for(int i = 1; i <= m; ++i){
		int l, r; read(l, r);
		auto it1 = s.upper_bound(make_tuple(l+1, 0, 0)); --it1;
		auto it2 = s.upper_bound(make_tuple(r+1, 0, 0));
		auto tmp1 = it1, tmp2 = it2; --tmp2;
		int L = get<0>(*tmp1); llong t1 = get<2>(*tmp1);
		int R = get<1>(*tmp2); llong t2 = get<2>(*tmp2);
		llong t = cnt-l;
		while(tmp1 != it2){
			int l2 = max(get<0>(*tmp1), l);
			int r2 = min(get<1>(*tmp1), r);
			llong ti = get<2>(*tmp1);
			if(ti <= (llong)1e18) res.emplace(t-ti-1, r2-l2+1);
			++tmp1;
		}
		s.erase(it1, it2);
		s.emplace(l, r, t);
		if(L != l) s.emplace(L, l-1, t1);
		if(R != r) s.emplace(r+1, R, t2);
		cnt += r-l+1;
	}
	for(int i = 1; i <= q; ++i) read(qry[i].first), qry[i].second = i;
	sort(qry+1, qry+q+1, greater<Query>());
	auto it = res.end();
	for(int i = 1; i <= q; ++i){
		while(it != res.begin() && get<0>(*--it) >= qry[i].first) tot += get<1>(*it);
//		printf("%d %d\n", get<0>(*it), get<1>(*it));
		if(it != res.end() && get<0>(*it) < qry[i].first) ++it;
		ans[qry[i].second] = tot;
	}
	for(int i = 1; i <= q; ++i) printf("%lld ", ans[i]);
	return 0;
}

T3

主观难度:【1+】

冷笑话:Hootime 是重现赛场上唯一一个没做出 T3 的人。大概是因为 T2 卡太久了。

显然是最短路。

显然飞越高越好,因为题给限制只是一个下界。那么我们把过程拆分为从 \(1\) 到一个地方和从 \(n\) 到一个地方,能往上飞就尽量往上飞,这样高度就等于了用时。于是答案为 \(\min_{1 \le i \le n} \{ 2 \times \max \{ dis_{1, i} + dis_{n, i} \} \}\)。然而样例二都过不了。

我们发现往上飞和往下飞中间可能有 \(1\) 单位时间停顿,原因在于两点点权相等导致这里平飞比向上以单位再向下一单位更优。于是我们枚举边,判一下。具体见代码。

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

#include <bits/stdc++.h>
#define llong long long
#define N 200005
#define M 400005
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;

int a[N];
int to[M<<1], nxt[M<<1], head[N], gsiz;
#define mkarc(u,v) (++gsiz, to[gsiz]=v, nxt[gsiz]=head[u], head[u]=gsiz)
int dis1[N], dis2[N], vis1[N], vis2[N], ans = 1e9+7;

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

int main(){
	freopen("in.txt", "r", stdin);
	read(n, m);
	for(int i = 1; i <= n; ++i) read(a[i]);
	for(int i = 1; i <= m; ++i){
		int u, v; read(u, v);
		mkarc(u, v), mkarc(v, u);
	}
	for(int i = 1; i <= n; ++i) dis1[i] = dis2[i] = 1e9+7;
	dis1[1] = 0, pq.emplace(0, 1);
	while(pq.size()){
		int u = pq.top().second; pq.pop();
		if(vis1[u]) continue;
		vis1[u] = true;
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			if(dis1[u]+max(a[v]-dis1[u],1) < dis1[v]){
				dis1[v] = dis1[u]+max(a[v]-dis1[u],1);
				pq.emplace(dis1[v], v);
			}
		}
	}
	dis2[n] = 0, pq.emplace(0, n);
	while(pq.size()){
		int u = pq.top().second; pq.pop();
		if(vis2[u]) continue;
		vis2[u] = true;
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			if(dis2[u]+max(a[v]-dis2[u],1) < dis2[v]){
				dis2[v] = dis2[u]+max(a[v]-dis2[u],1);
				pq.emplace(dis2[v], v);
			}
		}
	}
	for(int i = 1; i <= n; ++i)
		ans = min(ans, max(dis1[i], dis2[i])*2);
	for(int u = 1; u <= n; ++u){
		for(int i = head[u]; i; i = nxt[i]){
			int v = to[i];
			ans = min(ans, max(dis1[u], dis2[v])*2+(dis1[u] == dis2[v]));
		}
	}
	printf("%d", ans);
	return 0;
}

T4

主观难度:【2】

本来想评 1+ 的,想了想还是评 2。

我们离线下来对 \(r_i\) 扫描线,用一个线段树维护从 \(l\) 最远能连续地访问到哪里。每次加入线段的时候将 \([1, l_i]\) 中最远距离不小于 \(l_i-1\) 的值改为 \(r_i\)。正常线段树打打标记做就行了。

我们发现标记是可以合并的。设原有标记为 \(i\) 新的标记为 \(j\),则由扫描线有 \(r_j > r_i\)。发现如果 \(l_j > r_i+1\) 那么两个标记是不能拼起来的(否则一定有空隙),否则把两个标记拼起来即可。查询是简单的。

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

#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, q;

struct Node{
	int l, r, id;
} a[N<<1];
bool ans[N];

int val[N<<2], tagl[N<<2], tagr[N<<2];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define mid ((l+r)>>1)

inline void pushup(int x){
	val[x] = max(val[ls(x)], val[rs(x)]);
	return;
}
inline void build(int x = 1, int l = 1, int r = n){
	if(l == r){
		tagl[x] = tagr[x] = l-1;
		val[x] = l-1;
		return;
	}
	tagl[x] = tagr[x] = 1e9+7;
	build(ls(x), l, mid), build(rs(x), mid+1, r);
	pushup(x);
	return;
}	
inline void addtag(int x, int kl, int kr){
	if(kl > val[x]+1) return;
	tagl[x] = min(tagl[x], kl);
	tagr[x] = val[x] = kr;
	return;
}
inline void pushdown(int x){
	if(tagr[x] > 1e9) return;
	addtag(ls(x), tagl[x], tagr[x]);
	addtag(rs(x), tagl[x], tagr[x]);
	tagl[x] = tagr[x] = 1e9+7;
	return;
}
inline void modify(int L, int R, int kl, int kr, int x = 1, int l = 1, int r = n){
	if(L <= l && R >= r) return addtag(x, kl, kr);
	pushdown(x);
	if(L <= mid) modify(L, R, kl, kr, ls(x), l, mid  );
	if(R >  mid) modify(L, R, kl, kr, rs(x), mid+1, r);
	pushup(x);
	return;
}
inline int query(int pos, int x = 1, int l = 1, int r = n){
	if(l == r) return val[x];
	pushdown(x);
	if(pos <= mid) return query(pos, ls(x), l, mid  );
	else           return query(pos, rs(x), mid+1, r);
}

int main(){
	// freopen("in.txt", "r", stdin);
	read(n, m, q);
	build();
	for(int i = 1; i <= m; ++i) read(a[i].l, a[i].r);
	for(int i = 1; i <= q; ++i) read(a[i+m].l, a[i+m].r), a[i+m].id = i;
	m += q;
	sort(a+1, a+m+1, [&](Node o1, Node o2){return o1.r<o2.r || (o1.r==o2.r&&o1.id<o2.id);});
	for(int i = 1; i <= m; ++i){
		if(!a[i].id) modify(1, a[i].l, a[i].l, a[i].r);
		else ans[a[i].id] = (query(a[i].l) == a[i].r);
	}
	for(int i = 1; i <= q; ++i)
		puts(ans[i] ? "YES" : "NO");
	return 0;
}

T5

主观难度:【3】

我草。


首先 \(2n\)\(7t+n\) 是显然的。分数也是显然的。

我们发现每个样本最多可以放 \(300\) 个细菌,每次只放较少的细菌有点浪费。如果你还没想出来建议别往下看,再想想。提示:没说不能放入重复的细菌。

我们可以先二分出 T,然后我们发现,每种细菌我们可以放 \(2^n\) 个,这样如果样本里有 T 那么我们就可以推断出 S。做到了 \(8t+\frac{n}{8}\)

\(8t\) 是无法接受的。我们可以每 \(8\) 个分一块,如果有 T 再二分。这里可以先把 S 筛出来统一回答,然后把剩下的东西放进 \(s1\),每次从 \(s1\) 取出 \(8\) 个二分,以免浪费二分次数。块里没有 T 的先存在 \(s2\) 里,等到最后在往里面放一个已知的 T 来确定 S。于是 \(3t+\frac{n}{8}+\frac{n}{8} = 3t + \frac{n}{4}\)。二分的时候发现样本里还有一些空位,于是我们可以顺便检测一些 \(s2\) 的细菌。交一发,\(\mathrm {maxcnt} = 170\)

然后继续做常数优化。我们发现分块二分 T 时需要先判断块里有没有 T,而这件事我们在第一次筛掉 S 时已经干过了,于是我们可以在那里就二分出第一个 T,确定了第一个 T 和前面的所有 R,然后把剩下的再加入 \(s1\)

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

int query_sample(std::vector<int> species);
void answer_type(int x, char c);

int id[N], que1[N], que2[N], he1, ta1, he2, ta2;
int ans[N]; // 1: Regular, 2: Strong, 3: Toxic

inline int check(vector<int> tmp){
	int t = 0, siz = tmp.size();
	while(siz) ++t, siz >>= 1;
	int cnt = 8-t, tmphe = he2;
	while(cnt && tmphe <= ta2){
		int x = que2[tmphe++];
		for(int i = 1; i <= 1<<(8-cnt); ++i)
			tmp.push_back(x);
		--cnt;
	}
	int res = query_sample(tmp);
	if(res == tmp.size())
		return res & ((1<<t)-1);
	cnt = 8-t;
	while(cnt && he2 <= ta2){
		int x = que2[he2++];
		if(res & (1<<8-cnt)) ans[x] = 2;
		else ans[x] = 1;
		--cnt;
	}
	return res & ((1<<t)-1);
}

void determine_type(int n){
	for(int i = 1; i <= n; ++i) id[i] = i, ans[i] = 0;
	shuffle(id+1, id+n+1, random_device{});
	he1 = he2 = 1, ta1 = ta2 = 0;
	for(int l = 1; l <= n; l += 8){
		int r = min(l+7, n);
		vector<int> tmp;
		for(int i = l; i <= r; ++i)
			for(int j = 1; j <= (1<<i-l); ++j)
				tmp.push_back(id[i]);
		int res = check(tmp);
		if(res == tmp.size()){
			for(int i = l; i <= r; ++i)
				que2[++ta2] = id[i];
			continue;
		}
		int now[10], top = 0;
		for(int i = l; i <= r; ++i){
			if(res & (1<<i-l)) ans[id[i]] = 2;
			else now[++top] = id[i];
		}
		int ll = 1, rr = top;
		while(ll < rr){
			tmp.clear();
			int mid = (ll+rr)>>1;
			for(int i = ll; i <= mid; ++i)
				tmp.push_back(now[i]);
			if(check(tmp)) ll = mid+1;
			else           rr = mid;
		}
		for(int i = 1; i < ll; ++i) ans[now[i]] = 1;
		ans[now[ll]] = 3;
		for(int i = ll+1; i <= top; ++i) que1[++ta1] = now[i];
	}
	while(he1 <= ta1){
		int now[10], top = 0;
		while(top < 8 && he1 <= ta1) now[++top] = que1[he1++];
		vector<int> tmp;
		for(int i = 1; i <= top; ++i) tmp.push_back(now[i]);
		int res = check(tmp);
		if(res){
			for(int i = 1; i <= top; ++i)
				ans[now[i]] = 1;
			continue;
		}
		int l = 1, r = top;
		while(l < r){
			tmp.clear();
			int mid = (l+r)>>1;
			for(int i = l; i <= mid; ++i)
				tmp.push_back(now[i]);
			if(check(tmp)) l = mid+1;
			else           r = mid;
		}
		for(int i = 1; i < l; ++i) ans[now[i]] = 1;
		ans[now[l]] = 3;
		for(int i = l+1; i <= top; ++i) que1[++ta1] = now[i];
	}
	int t;
	for(int i = 1; i <= n; ++i)
		if(ans[i] == 3){t = i; break;}
	while(he2 <= ta2){
		int now[10], top = 0;
		while(top < 8 && he1 <= ta1) now[++top] = que1[he1++];
		vector<int> tmp;
		for(int i = 1; i <= top; ++i)
			for(int j = 1; j <= (1<<i-1); ++j)
				tmp.push_back(now[i]);
		tmp.push_back(t);
		int res = check(tmp);
		for(int i = 1; i <= top; ++i){
			if(res & (1<<i-1)) ans[now[i]] = 2;
			else ans[now[i]] = 1;
		}
	}
	for(int i = 1; i <= n; ++i)
		answer_type(i, (ans[i]==1 ? 'R' : (ans[i]==2 ? 'S' : 'T')));
	return;
}
#undef N

这时交一发。\(\mathrm {maxcnt} = 148\)。过了。

posted @ 2026-03-05 15:02  Hootime  阅读(1)  评论(0)    收藏  举报