2022NOIP A层联测23
A. zzy 的金牌
只关心最终状态,所以为了方便处理我们可以强制定序
先排个序,然后强制最终为不减的序列
设 \(f_{i, j, k}\) 表示考虑前 \(i\) 个,当前数增加了 \(j\), 还剩余 \(k\) 没有加
转移先跨层不选,然后在同层转移选择
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 305;
const int mod = 998244353;
int f[maxn][maxn][maxn];
int n, k, a[maxn];
void add(int &x, int y){x += y; x = x >= mod ? x - mod : x;}
int main(){
freopen("orzzy.in","r",stdin);
freopen("orzzy.out","w",stdout);
n = read(), k = read();
for(int i = 1; i <= n; ++i)a[i] = read();
sort(a + 1, a + n + 1);
f[0][0][k] = 1;
for(int i = 0; i < n; ++i){
for(int j = 0; j <= k; ++j)
int to = max(a[i] + j - a[i + 1], 0);
for(int r = to; r <= k; ++r)
if(f[i][j][r])
add(f[i + 1][to][r - to], f[i][j][r]);
for(int j = 0; j <= k; ++j)
for(int r = 1; r <= k; ++r)
add(f[i + 1][j + 1][r - 1], f[i + 1][j][r]);
}
int ans = 0;
for(int i = 0; i <= k; ++i)add(ans, f[n][i][0]);
printf("%d\n",ans);
return 0;
}
B. 口粮输送
考虑 $\sum a_i - b_i >= \sum w_i $的子图一定合法
对于 \(\sum a_i - b_i < \sum w_i\)的合法图一定存在某些边不走,可以删去边处理
划分为若干联通块,考虑每个合法联通块, 有$\sum a_i - b_i >= \sum w_i $
那么只看其最小生成树就可以完成判定
先对所有点集最小生成树判定,再枚举子集合并大集合
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 19;
int n, m, a[maxn], b[maxn];
struct edge{
int u, v, w;
friend bool operator < (const edge &x, const edge &y){
return x.w < y.w;
}
}e[maxn * maxn];
bool f[65539], in[maxn];
int fa[maxn];
int find(int x){return fa[x] == x ? x : fa[x] = find(fa[x]);}
bool merge(int x, int y){x = find(x), y = find(y); if(x == y)return false; fa[y] = x; return true;}
bool mst(int s){
int smt = 0, snt = 0, cnt = 0;
for(int i = 1; i <= n; ++i)if((1 << (i - 1)) & s)snt += a[i] - b[i], in[i] = true, ++cnt;else in[i] = false;
for(int i = 1; i <= n; ++i)fa[i] = i;
for(int i = 1; i <= m; ++i)
if(in[e[i].u] && in[e[i].v] && merge(e[i].u, e[i].v))
smt += e[i].w, --cnt;
return cnt == 1 && snt - smt >= 0;
}
void sol(){
n = read(), m = read();
for(int i = 1; i <= m; ++i){
int u = read(), v = read(), w = read();
e[i] = {u, v, w};
}
sort(e + 1, e + m + 1);
for(int i = 1; i <= n; ++i)a[i] = read(), b[i] = read();
for(int i = 1; i < (1 << n); ++i)f[i] = mst(i);
for(int i = 1; i < (1 << n); ++i)if(f[i] == false)
for(int j = (i - 1) & i; j && !f[i]; j = (j - 1) & i)
f[i] |= f[j] & f[i ^ j];
if(f[(1 << n) - 1])printf("Yes\n");
else printf("No\n");
}
int main(){
freopen("trans.in","r",stdin);
freopen("trans.out","w",stdout);
int t = read();
for(int i = 1; i <= t; ++i)sol();
return 0;
}
C. 作弊
反过来考虑每个人的贡献
找到 \(ll_i, lr_i, rl_i, rr_i\)
分别表示向左、右,第一个区间 \(max >= Li/R_i\) 的位置
那么一个人有贡献,当且仅当至少一端在 \((ll,lr]\) \([rl,rr)\)之间,并且没有跨过\(ll, rr\)的
于是用线段树维护这个东西,从左到右扫
扫到 \(i\), 将 \(ll,lr\)区间加
扫到 \(rl\) 对 \(lr,i\)区间jia
扫到 \(rr\)清除贡献
每次查询区间最大值即可完成转移
code
#include<bits/stdc++.h>
using namespace std;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 100005;
int f[maxn], a[maxn], n, l[maxn], r[maxn];
struct seg{
struct node{
int val, tag;
}t[maxn << 2 | 1];
void push_down(int x){
t[x << 1].val += t[x].tag;
t[x << 1 | 1].val += t[x].tag;
t[x << 1].tag += t[x].tag;
t[x << 1 | 1].tag += t[x].tag;
t[x].tag = 0;
}
void push_up(int x){t[x].val = max(t[x << 1].val, t[x << 1 | 1].val);}
void modify(int x, int l, int r, int L, int R, int val){
if(L <= l && r <= R){
t[x].val += val; t[x].tag += val;
return;
}
if(t[x].tag)push_down(x);
int mid = (l + r) >> 1;
if(L <= mid)modify(x << 1, l, mid, L, R, val);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
push_up(x);
}
int query(int x, int l, int r, int L, int R){
if(L <= l && r <= R)return t[x].val;
if(t[x].tag)push_down(x);
int mid = (l + r) >> 1, ans = 0;
if(L <= mid)ans = max(ans, query(x << 1, l, mid, L, R));
if(R > mid)ans = max(ans, query(x << 1 | 1, mid + 1, r, L, R));
return ans;
}
}t;
struct BIT{
int t[maxn];
int lowbit(int x){return x & -x;}
void modify(int x, int val){x = n - x + 1; while(x <= n){t[x] = max(t[x], val); x += lowbit(x);}}
int query(int x){x = n - x + 1; int ans = 0; while(x){ans = max(ans, t[x]); x -= lowbit(x);}return ans;}
void clear(){for(int i = 1; i <= n; ++i)t[i] = 0;}
}T;
int ll[maxn], lr[maxn], rl[maxn], rr[maxn];
vector<int>RL[maxn], RR[maxn];
int main(){
freopen("cheat.in","r",stdin);
freopen("cheat.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i <= n; ++i)l[i] = read(), r[i] = read();
for(int i = 1; i <= n; ++i){
T.modify(a[i], i), ll[i] = T.query(l[i]), lr[i] = T.query(r[i] + 1);
}
T.clear();
for(int i = n; i >= 1; --i){
T.modify(a[i], n - i + 1), rl[i] = n - T.query(l[i]) + 1, rr[i] = n - T.query(r[i] + 1) + 1;
}
for(int i = 1; i <= n; ++i){
if(rl[i])RL[rl[i]].push_back(i);
if(rr[i])RR[rr[i]].push_back(i);
}
for(int i = 1; i <= n; ++i){
t.modify(1, 1, n, i, i, t.query(1, 1, n, 1, n));
if(ll[i])t.modify(1, 1, n, 1, ll[i], 1);
if(lr[i])t.modify(1, 1, n, 1, lr[i], -1);
for(int x : RL[i])if(ll[x] < x)t.modify(1, 1, n, ll[x] + 1, x, 1);
for(int x : RR[i])if(lr[x] < x)t.modify(1, 1, n, lr[x] + 1, x, -1);
}
printf("%d\n",t.query(1, 1, n, 1, n));
return 0;
}
D. 合作的力量
先考虑二分答案 \(s\)
每次先选择 \(<= s / 2\)的,设其为 \(b_1 b_2 ... b_k\)
相邻两个 \(b\) 之间至多插入一个元素
实际上为
\(x > s / 2\)
\(x + max(a_l, a_r) <= s\)
其中 \(a_l , a_r\)表示左右第一个小于当前数的数
发现 \(x<= s /2\)也满足该条件
于是在 \(b1,bk\)之间只需要找 \(x + max(a_l, a_r) <= s\)的数的个数
需要特殊处理 \(l, b1\) \(bk, r\)找最小值看是否能插入即可
继续优化上面的东西
设 \(L_i = x + max(a_l, a_r)\)
统计的变成 \(s >= L_i\) 的数的个数
简单思考发现是区间第 \(k\) 小
吗?
直接这样显然不行,因为可能两侧会去到区间以外的
那么根据取到的两侧 \(b_1, b_k\)的大小,我们实际上有 \(k - 1, k ,k + 1, k + 2\)四种情况
分别对应 \(b_1, b_k\)能选并且在 \(l ,b1\) \(bk, r\)中插入一个 (k - 1)
都能选,但是无法插入(k)
有一个不能选 \(k + 1\)
两个都不能选 \(k + 2\)
于是,于是,褐了某大佬的代码
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 510005;
const int inf = 2147483647;
const int mx = 2000000000;
int n, m, a[maxn], b[maxn], sta[maxn], top, tot;
int lm[maxn], rm[maxn], down[maxn];
int st0[maxn][20], st1[maxn][20], lg[maxn];
int root[maxn];
struct seg{
struct node{
int l, r, sum;
}t[maxn * 40];
int cnt;
int modify(int x, int l, int r, int pos){
int now = ++cnt;
t[now] = t[x]; ++t[now].sum;
if(l == r)return now;
int mid = (0ll + l + r) >> 1;
if(pos <= mid)t[now].l = modify(t[x].l, l, mid, pos);
else t[now].r = modify(t[x].r, mid + 1, r, pos);
return now;
}
int query(int x, int y, int l, int r, int k){
if(t[y].sum - t[x].sum < k)return -1;
if(l == r)return l;
int mid = (0ll + l + r) >> 1, sl = t[t[y].l].sum - t[t[x].l].sum;
if(sl >= k)return query(t[x].l, t[y].l, l, mid, k);
else return query(t[x].r, t[y].r, mid + 1, r, k - sl);
}
}t;
int ask0(int l, int r){
int k = lg[(r - l + 1)];
return min(st0[l][k], st0[r - (1 << k) + 1][k]);
}
int ask1(int l, int r){
int k = lg[(r - l + 1)];
return min(st1[l][k], st1[r - (1 << k) + 1][k]);
}
int find1(int p, int k){
int l = p, r = n, ans = -1;
while(l <= r){
int mid = (l + r) >> 1;
if(ask1(p, mid) <= k)ans = mid, r = mid - 1;
else l = mid + 1;
}
return ans;
}
int find2(int p, int k){
int l = 1, r = p, ans = -1;
while(l <= r){
int mid = (l + r) >> 1;
if(ask1(mid, p) <= k)ans = mid, l = mid + 1;
else r = mid - 1;
}
return ans;
}
void sol(int L, int R, int k){
int ans = inf;
for(int i = -1; i <= 2; ++i)if(k + i >= 1 && k + i <= R - L + 1){
int s = t.query(root[L - 1], root[R], 0, mx, k + i);
int num = k + i;
int p1 = find1(L, s), p2 = find2(R, s);
if(num != 1){
int p3 = find1(p1 + 1, s), p4 = find2(p2 - 1, s);
if(i == -1)s = max({s, a[p2] + a[p4], a[p1] + a[p3], a[p1] + a[p2]});
if(a[p2] + a[p4] > s)--num, p2 = p4;
if(a[p1] + a[p3] > s)--num, p1 = p3;
if(a[p1] + a[p2] > s){
if(a[p1] > a[p2])--num, p1 = p3;
else --num, p2 = p4;
}
}
int mi = inf;
if(p1 != L)mi = min(mi, ask0(L, p1 - 1));
if(p2 != R)mi = min(mi, ask0(p2 + 1, R));
if(mi != inf && num == k - 1)++num, s = max(s, mi + max(a[p1], a[p2]));
if(num >= k)ans = min(ans, s);
}
printf("%d\n",ans);
}
int main(){
freopen("chikara.in","r",stdin);
freopen("chikara.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= n; ++i)a[i] = b[i] = read();
for(int i = 2; i <= n; ++i)lg[i] = lg[i >> 1] + 1;
for(int i = 1; i <= n; ++i){
while(top && a[sta[top]] > a[i]) --top;
lm[i] = sta[top]; sta[++top] = i;
}
top = 0;
for(int i = n; i >= 1; --i){
while(top && a[sta[top]] >= a[i]) --top;
rm[i] = sta[top]; sta[++top] = i;
}
top = 0;
for(int i = 1; i <= n; ++i)down[i] = max(a[lm[i] ? lm[i] : i], a[rm[i] ? rm[i] : i]) + a[i];
for(int i = 1; i <= n; ++i)st0[i][0] = a[i];
for(int j = 1; (1 << j) <= n; ++j)
for(int i = 1; i + (1 << j) - 1 <= n; ++i)
st0[i][j] = min(st0[i][j - 1], st0[i + (1 << (j - 1))][j - 1]);
for(int i = 1; i <= n; ++i)st1[i][0] = down[i];
for(int j = 1; (1 << j) <= n; ++j)
for(int i = 1; i + (1 << j) - 1 <= n; ++i)
st1[i][j] = min(st1[i][j - 1], st1[i + (1 << (j - 1))][j - 1]);
for(int i = 1; i <= n; ++i)root[i] = t.modify(root[i - 1], 0, mx, down[i]);
for(int i = 1; i <= m; ++i){
int L = read(), R = read(), k = read();
if(k == 1)printf("%d\n",ask0(L, R) * 2);
else sol(L, R, k);
}
return 0;
}