51nod 2022赛前模测 提高组验题 / CSP-S模拟3

由于本人太菜,各位大佬太强,所以本人没有造数据以及预定讲题的机会,所以只能写篇题解了

另外记录了各题的造数据人,感谢良/凉心造数据人们

还有一点本人口胡的预测
upd : 这, 卡下界是吧..
预计前\(15\)左右的人的分数在 \(120 - 180\)之间,最高分在 \(220 - 350\)

小于 \(100\) 分的不少于 \(10\)

\(T1\) 预计 \(0 - 3\)\(AC\)

\(T2\) 挺水的一题, 预计 \(2 - 6\)\(AC\)

\(T3\) 预计 \(0 - 2\)\(AC\)

\(T4\) 预计会板子的都能\(AC\), 不会板子的都不能 \(AC\) (废话) , \(4 - 10\) 人吧?

\(CDsidi\)的要求,加上他的预测:

基本分排名第4-9 在\(160\)上下浮动,平均分\(\leq 100\)
少于\(80\)分的 约一半
存在AC一题的人数 \(\leq\) 10
T1, 3, AC总人数 \(\leq 2\)

A score and rank

考虑找最大子段和的过程, 我们不停累加上当前数, 当 \(sum < 0\) 时令 \(sum = 0\) 即不选择前面的区间

我们可以用类似的方法进行贪心

首先我们在一个已知范围内,要使区间合法,显然贪心的删最大的一定最优

那么我们需要维护一个堆,堆里是当前选择的元素,当一个区间\(sum >= s\)时不停去掉 \(top()\)并记录答案

\(sum < 0\) 时, 直接清掉堆, 令\(sum = 0\)

但是这样是假的,我们考虑当扫到一个负数时, 此时 \(sum > 0\) 我们选择了前面的区间, 在后面删除时,如果我们删除了前面区间的元素,而使得前面区间的值$ < 0$ 我们不会选择前面的区间,也就是说,如果直接加入负数,会使得后面选择了一段小于 \(0\) 的区间,这显然不对

例如 \(\text{6 -5 2 2 2 2 2 2}\) , \(s = 7\), 在后面删除我会先去掉前面的 \(6\) , 这时我认为他有 \(6\) 的贡献, 但是此时如果我不再选择前两个元素, 那么贡献其实是 \(1\)

所以我们考虑此时区间对后面最大的贡献是 \(sum\) , 所以我们需要对堆进行操作,消除新加入的负数带来的影响,而我们贪心策略是取最大元素,所以这里我们需要尽可能用小元素消除,通过不停取最小值加上当前负数,直到其大于等于 \(0\) ,再把新元素入堆

如上面的例子,我会把 \(6\) 取出来, \(-5 + 6 = 1 > 0\)\(1\) 插回去, 这样等效于前面区间最多有 \(1\) 的贡献

此时堆已经不能满足所有操作,我们把堆换成平衡树 \(set\) 即可

新数据 \(by\; Eafoo\) ,非常凉心,把所有已知假贪心卡到 \(30pts\) 以下,貌似没啥梯度和区分度

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<set>
#include<map>

using namespace std;

const int maxn = 1000005;
typedef long long ll;
ll a[maxn], n, s;
inline ll read(){
	ll x = 0; char c = getchar(); bool f = 0;
	while(c < '0' || c > '9'){if(c == '-')f = 1;c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return f ? -x : x;
}
multiset<ll> q;
int main(){
	n = read(), s = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	ll sum = 0;
	int ans = 0;
	if(s <= 0){
		int ans = 0;
		for(int i = 1; i <= n; ++i)if(a[i] > s)++ans;
		printf("%d\n",ans);
		return 0;
	}
	for(int i = 1; i <= n; ++i){
		if(a[i] >= 0){sum += a[i]; q.insert(a[i]);}
		else{
			while(sum >= s && !q.empty()){
				auto it = --q.end();
				sum -= *it; q.erase(it); ++ans;
			}
			if(sum + a[i] > 0){
				sum += a[i]; 
				while(a[i] < 0 && !q.empty()){
					a[i] += *q.begin();
					q.erase(q.begin());
				}
				q.insert(a[i]);
			}else{
				q.clear();
				sum = 0;
			}
		}
	}
	while(sum >= s && !q.empty()){
		auto it = --q.end();
		sum -= *it;
		q.erase(it);
		++ans;
	}
	printf("%d\n",ans);
	return 0;
}

B 松鼠大作战 / HZOI大作战

考场读错题。。。。

发现找到一个比手里数大的,后面再取谁就是固定的,可以通过倍增求出每个点向上换 \(2^i\) 个 武器/板子 的节点

简单倍增即可解决问题

新数据 \(by\; joke3579\),有梯度和区分度

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<set>
#include<map>

using namespace std;

typedef long long ll;
const int maxn = 500005;
inline int read(){
	int x = 0; char c = getchar(); bool f = 0;
	while(c < '0' || c > '9'){if(c == '-')f = 1;c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return f ? -x : x;
}
int n, q, a[maxn];
struct edge{int to, net;}e[maxn << 1 | 1];
int head[maxn], tot;
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
int mx[maxn][20], dep[maxn], fa[maxn];
void dfs(int x){
	if(a[fa[x]] > a[x])mx[x][0] = fa[x];
	else{
		int f = fa[x]; for(int i = 19; i >= 0; --i)if(mx[f][i] != 0 && a[mx[f][i]] <= a[x]) f = mx[f][i];
		mx[x][0] = mx[f][0];
	}
	for(int i = 1; i <= 19; ++i)mx[x][i] = mx[mx[x][i - 1]][i - 1];
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa[x])continue;
		dep[v] = dep[x] + 1;
		fa[v] = x;
		dfs(v);
	}
}
int main(){
	n = read(), q = read();
	for(int i = 1; i <= n; ++i)a[i] = read();
	for(int i = 1; i < n; ++i){
		int x = read(), y = read();
		add(x, y); add(y, x);
	}
	dep[1] = 1; dfs(1);
	for(int i = 1; i <= q; ++i){
		int u = read(), v = read(), w = read();
		int ans = 0;
		if(a[u] <= w){
			for(int j = 19; j >= 0; --j)if(mx[u][j] != 0 && a[mx[u][j]] <= w)u = mx[u][j];
			u = mx[u][0];
		}
		if(dep[u] >= dep[v]){
			++ans;
			for(int j = 19; j >= 0; --j)if(dep[mx[u][j]] >= dep[v])u = mx[u][j], ans += (1 << j);
		}
		printf("%d\n",ans);
	}
	return 0;
}

C 小 S 的旅行 / Delov的旅行

二分答案进行\(check\)

考虑一个节点对其子树外的贡献只有一条入边,一条出边,我们记录他们的集合 \((a, b) \in S\)

发现 \(a_1 >= a_2 \&\& b_1 >= b_2\) 的话 \((a_1,b_2)\) 没有意义,直接扔掉即可

而因为我们二分了答案,所以对于确定的 \((a,b)\) ,我们找到 \(a`\) 最大的能使得\(a` + b <= mid\) 的与之配对 \((a, b`)\) 这样 \(b`\) 是最小的,显然最优

可以证明满足条件的二元组个数非常有限,但是我不会证

新数据 \(by\; Delov\) ,我的考场假贪心 \(50 - > 20\)

数据有梯度,但是不知道有什么其他解法

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<set>
#include<map>

using namespace std;

typedef long long ll;
const int maxn = 231075;
inline int read(){
	int x = 0; char c = getchar(); bool f = 0;
	while(c < '0' || c > '9'){if(c == '-')f = 1;c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return f ? -x : x;
}
struct edge{int to, net, val;}e[maxn << 1 | 1];
int n, head[maxn], tot;
void add(int u, int v, int w){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
	e[tot].val = w;
}
struct node{
	ll a, b;
	friend bool operator < (const node &x, const node &y){
		return x.a == y.a ? x.b < y.b : x.a < y.a;
	}
};
set<node>s[maxn];
node ls[maxn];
bool dfs(int x, int fa, ll mid){
	s[x].clear();
	int lson = 0, rson = 0, vl, vr;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to; if(v == fa)continue;
		if(dfs(v, x, mid) == 0)return 0; 
		if(lson)rson = v, vr = e[i].val;
		else lson = v, vl = e[i].val;
	}
	if(!lson && !rson){s[x].insert({0, 0});return true;}
	if(!rson){for(node now : s[lson])s[x].insert({now.a + vl, now.b + vl});return true;}
	int cnt = 0;
	auto it = s[rson].begin(), en = --s[rson].end();
	for(node now : s[lson]){
		while(it != en && (*next(it)).a + now.b + vl + vr <= mid)++it;
		if((*it).a + now.b + vl + vr <= mid)ls[++cnt] = {now.a + vl, (*it).b + vr};
	}
	it = s[lson].begin(), en = --s[lson].end();
	for(node now : s[rson]){
		while(it != en && (*next(it)).a + now.b + vl + vr <= mid)++it;
		if((*it).a + now.b + vl + vr <= mid)ls[++cnt] = {now.a + vr, (*it).b + vl};
	}
	if(cnt == 0)return false;
	sort(ls + 1, ls + cnt + 1);
	s[x].insert(ls[1]);
	int las = 1;
	for(int i = 2; i <= cnt; ++i)
		if(ls[i].b < ls[las].b && ls[i].a > ls[las].a){
			s[x].insert(ls[i]); las = i;
		}
	if(s[x].empty())return false;
	return true;
}
int main(){
	n = read();
	for(int i = 2; i <= n; ++i){
		int u = read(), w = read();
		add(i, u, w); add(u, i, w);
	}
	ll l = 0, r = 17179870000, ans = r;
	while(l <= r){
		ll mid = (l + r) >> 1;
		if(dfs(1, 0, mid))r = mid - 1, ans = mid;
		else l = mid + 1;
	}

	printf("%lld\n",ans);
	return 0;
}

D 小可爱的星球 / gtm和joke的星球

这题是斯坦纳树的板子,上某谷学吧

image

原题数据很水,据说能随机化\(AC\)

新数据\(by\;\) \(gtm1514\)

数据比原来略强,但是新增特殊性质送分比较多

code
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<set>
#include<map>

using namespace std;

typedef long long ll;
const int maxn = 1005;
inline int read(){
	int x = 0; char c = getchar(); bool f = 0;
	while(c < '0' || c > '9'){if(c == '-')f = 1;c = getchar();}
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return f ? -x : x;
}
struct edge{
	int to, net, val;
}e[maxn];
int n, m, k, ts[maxn];
int head[maxn], tot;
void add(int u, int v, int w){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
	e[tot].val = w;
}
typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii>>q;
int dp[maxn][1025];
bool vis[maxn];
void dij(int s){
	memset(vis, 0, sizeof(vis));
	while(!q.empty()){
		int x = q.top().second;
		q.pop();
		if(vis[x])continue;
		vis[x] = 1;
		for(int i = head[x]; i; i = e[i].net){
			int v = e[i].to;
			if(dp[v][s] > dp[x][s] + e[i].val){
				dp[v][s] = dp[x][s] + e[i].val;
				q.push(pii(dp[v][s], v));
			}
		}
	}
}
int main(){
	n = read(), m = read(), k = read();
	for(int  i = 1; i <= m; ++i){
		int u = read(), v = read(), w = read();
		add(u, v, w); add(v, u ,w);
	}
	for(int i = 1; i <= k; ++i)ts[i] = read();
	memset(dp, 0x3f, sizeof(dp));
	for(int i = 1; i <= k; ++i)dp[ts[i]][1 << (i - 1)] = 0;
	for(int s = 1; s < (1 << k); ++s){
		for(int i = 1; i <= n; ++i){
			for(int x = s & (s - 1); x; x = s & (x - 1))
				dp[i][s] = min(dp[i][s], dp[i][x] + dp[i][s xor x]);
			if(dp[i][s] != dp[0][0]){q.push(pii(dp[i][s], i));}
		}
		dij(s);
	}
	printf("%d\n",dp[ts[1]][(1 << k) - 1]);
	return 0;
}
posted @ 2022-09-11 12:07  Chen_jr  阅读(413)  评论(0编辑  收藏  举报