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;
}

posted @ 2022-11-09 21:33  Chen_jr  阅读(15)  评论(0编辑  收藏  举报