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的星球
这题是斯坦纳树的板子,上某谷学吧
原题数据很水,据说能随机化\(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;
}