CSP-S模拟8

不愧为 \(IOI\) 赛制

A. 选举

以为是个贪心题,结果怎么贪都不对,到九点多意识到这是 \(DP\) ,然后就不想打了。。。。

写了个假 \(DP\) ,就不在这里说了。。

首先如果我们知道选择了多少协作者,那么我们一定按照时间升序搞到他们,并且在剩下的州选择时间最少的若干个得到选票

那么我们就有了一个 \(DP\), 外层枚举选择几个协作者, \(dp_{i, j, k}\) 表示考虑了前 \(i\) 个州,选择了 \(j\) 个协作者, \(k\) 张选票的最小花费

继续考虑优化这个东西,发现如果按照 \(b\) 升序排列,那么如果选择了一个协作者,在他前面存在未选择的州,那么答案一定不是最优

所以排序后的前若干个州要么选择了选票,要么选择了协作者,这样我们就可以去掉上面 \(k\) 的枚举了

code
#include<bits/stdc++.h>

using namespace std;
const int maxn = 505;
typedef long long ll;
typedef unsigned long long ull;
const double inf = 0x3f3f3f3f3f3f3f;
const int INF = 0x3f3f3f3f;
int n, k;
struct node{
	int a, b;
	friend bool operator < (const node &x, const node &y){
		return x.b < y.b;
	}
}a[maxn];
double f[2][maxn];
double ans;
multiset<int>s;
int nxt[maxn];
int main(){
	scanf("%d%d",&n,&k);
	for(int i = 1; i <= n; ++i)scanf("%d%d",&a[i].a, &a[i].b);
	int cb = 0; for(int i = 1; i <= n; ++i)cb += (a[i].b != -1);
	for(int i = 1; i <= n; ++i)a[i].b = a[i].b == -1 ? INF : a[i].b;
	for(int i = 1; i <= k; ++i)ans += a[i].a;
	sort(a + 1, a + n + 1);
	for(int i = n; i >= 0; --i){
		if(i < k){
			nxt[i] = nxt[i + 1] + *s.begin();
			s.erase(s.begin());
		}
		s.insert(a[i].a);
	}
	cb = min(cb, k);
	for(int b = 0; b <= cb; ++b){
		for(int i = 0; i <= 1; ++i)for(int j = 0; j <= b; ++j)f[i][j] = inf + 5;
		f[0][0] = 0; 
		for(int i = 0; i <= k; ++i){
			int nt = i & 1, up = min(b, i);
			for(int j = 0; j <= up; ++j){
				if(f[nt][j] < inf){
					if(j == b)ans = min(ans, f[nt][j] + (double)nxt[i] / (double)(b + 1));
					f[1 - nt][j] = min(f[1 - nt][j], f[nt][j] + (double)a[i + 1].a / (b + 1));
					if(a[i + 1].b != INF)f[1 - nt][j + 1] = min(f[1 - nt][j + 1], f[nt][j] + ((double)a[i + 1].b / (j + 1)));
					f[nt][j] = inf + 5;
				}
			}
			if(a[i + 1].b == INF)break;
		}
	}
	printf("%.5lf\n",ans);
	return 0;
}

B. 港口设施

把入栈和出栈看做在时间轴上的一个线段,当两条线段不相交或者有包含关系的话,那么他们是否在一个集合是无所谓的

唯一有问题的在于两个线段相交,这个时候他们不能放在一个集合

使用扩展域并查集可以方便的维护这个东西,或者建边黑白染色也行

具体做法是根据时间维护一个栈,每个元素出栈时将从栈顶一直到他在栈中的位置的元素的扩展与他合并/连边

但是这样是能卡成 \(n^2\) 的,考虑优化这个过程

发现每次操作时,实际上我们把一段区间染成了特定颜色,而对后面的操作来说,对于一个相同的颜色连 \(1\) 条边或者多条边本质相同

所以我们可以记录一个转移指针 \(trans\) ,跳到下一个与当前位置颜色不同的点

记录下一个是为了方便删除,记录 \(nxt\) 表示下一个包括他自己没有被删除的位置,类似并查集维护

这样我们就能快速染色了

code
#include<bits/stdc++.h>

using namespace std;

typedef unsigned long long ull;
typedef long long ll;
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
	return x;
}
const int mod = 1e9 + 7;
const int maxn = 3000005;
int n, timeline[maxn + maxn];
struct node{int a, b;}d[maxn];
int f[maxn + maxn], sta[maxn], top;
int trans[maxn], in[maxn], nxt[maxn];
int fa(int x){return x == f[x] ? x : f[x] = fa(f[x]);}
bool merge(int x, int y){
	x = fa(x), y = fa(y);
	int dx = fa(x + n), dy = fa(y + n);
	if(x == y || dx == dy)return false;
	f[dx] = y; f[dy] = x;
	return true;
}
int findnxt(int x){return x == nxt[x] ? x : nxt[x] = findnxt(nxt[x]);}
bool vis[maxn];
int main(){
	n = read();
	for(int i = 1; i <= n; ++i)d[i].a = read(), d[i].b = read();
	for(int i = 1; i <= n; ++i)timeline[d[i].a] = i, timeline[d[i].b] = -i;
	for(int i = 1; i <= n + n; ++i)f[i] = i;
	for(int i = 1; i <= n + n; ++i){
		nxt[i] = i; trans[i] = i + 1;
		if(timeline[i] > 0){
			sta[++top] = timeline[i];
			in[timeline[i]] = top;
		}else{
			int out = -timeline[i];
			for(int tr = findnxt(in[out] + 1); tr <= top; tr = findnxt(tr)){
				if(merge(sta[tr], out) == false){printf("0\n");return 0;}
				int tp = trans[tr];
				trans[tr] = top + 1;
				tr = tp;
			}
			nxt[in[out]] = nxt[in[out]] + 1;
		}
	}
	int cnt = 0, ans = 1;
	for(int i = 1; i <= n + n; ++i)if(fa(i) == i)++cnt;
	cnt >>= 1;
	for(int i = 1; i <= cnt; ++i)ans += ans, ans = ans >= mod ? ans - mod : ans;
	printf("%d\n",ans);
	return 0;
}```cpp

C. 幽深府邸

记录 \(pre_i\)\(nxt_i\) 表示打开第 \(i\) 个门从左侧/右侧至少需要到哪个房间,这样我们就能根据当前区间进行扩展

如果我们到了一个之前走过的房间,那么当前能扩展的区间就可以与原来该房间的区间取并集

这样复杂度仍然不对,

所以可以随机化选择扩展的顺序,期望下是正确的

或者扩展一个未扩展的位置时,先去递归扩展该位置,再回来取并,类似记忆化搜索

貌似还有一种倍增优化?我太菜了,不会

code
#include<bits/stdc++.h>

using namespace std;

typedef unsigned long long ull;
typedef long long ll;
const int maxn = 500005;
mt19937 rd((ull)&maxn);
inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
	return x;
}
int n, c[maxn], b[maxn];
vector<int>key[maxn];
int pre[maxn], nxt[maxn], rpos[maxn];
int p[maxn], l[maxn], r[maxn];
void expand(int x){
	while(1){
		bool fl = 0;
		if(r[x] < n){if(pre[r[x]] >= l[x])l[x] = min(l[x], l[r[x] + 1]), r[x] = max(r[x], r[r[x] + 1]), fl = 1;}
		if(l[x] > 1){if(nxt[l[x] - 1] <= r[x])r[x] = max(r[x], r[l[x] - 1]), l[x] = min(l[x], l[l[x] - 1]), fl = 1;}
		if(!fl)break;
	}
}
int main(){
	n = read();
	for(int i = 1; i < n; ++i)c[i] = read();
	for(int i = 1; i <= n; ++i){
		b[i] = read(); for(int j = 1; j <= b[i]; ++j)key[i].push_back(read());
	}
	for(int i = 1; i <= n; ++i){
		for(int x : key[i])rpos[x] = i;
		pre[i] = rpos[c[i]];
	}
	for(int i = 1; i <= n; ++i)rpos[i] = n + 1;
	for(int i = n; i > 0; --i){
		nxt[i] = rpos[c[i]];
		for(int x : key[i])rpos[x] = i;
	}
	for(int i = 1; i <= n; ++i)l[i] = r[i] = p[i] = i;
	shuffle(p + 1, p + n + 1, rd);
	for(int i = 1; i <= n; ++i)expand(p[i]);
	int Q = read();
	for(int i = 1; i <= Q; ++i){
		int x = read(), y = read();
		if(l[x] <= y && y <= r[x])printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

D. 长途巴士

这波暴力都不会打,我太菜了

因为司机不能缺水,所以我们能在到站之前让某个客人的后缀下车

这样我们就能按照 \(D\) 排序进行转移了

那么设 \(f_i\) 表示前 \(i\) 个人的最小花费

转移有两个

  1. 该乘客到站

\(\large f_i = f_{i - 1} + w \times (\lfloor \frac{x}{t} \rfloor + [x \mod t \geq D_i])\)

  1. 该乘客作为某个饮水站前最后一个喝水的人,他以及他前面的若干个人下车

\(\displaystyle \large f_i = min_{j = 0}^{i - 1} (\sum_{k = j}^{i}C_k + w \times (i - j) \times Mn_i)\)

其中

\(\displaystyle \large Mn_i = min_{D_i \leq (s_j \mod t) \leq D_{i + 1}} (\lfloor \frac{s_j}{t} \rfloor)\)

表示最小满足能让该后缀下车的车站

这个可以双指针扫一遍处理出来

上面的 2. 可以斜率优化

因为询问的斜率没有单调性,所以查询可以在凸包上二分

由于我太菜了,为了复习斜率优化,在这里推一下式子

先去掉只与 \(i\) 有关的, 设 \(g_i = f_i - sum_j - w\times Mn_i \times j\)

\(w\) 常量忽略, 设\(p = Mn_i\) 就是我们查询的斜率

\(g_i = f_i - sum_i - p \times i\)

\(j < k\) \(g_j \geq g_k\)

\(f_j - sum_j - pj \geq f_k - sum_k - pk\)

\(f_j - sum_j - f_k + sum_k \geq p(j - k)\)

\((f_j - sum_j - f_k + sum_k) / (j - k) \leq p\)

也就是说,当该斜率小于查询值时 后面的更优

那么我们应该维护斜率 单调递增下凸包

code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
inline ll read(){
	ll x = 0; char c = getchar();
	while(c < '0' || c > '9')c = getchar();
	do{x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}while(c <= '9' && c >='0');
	return x;
}
const int maxn = 2e5 + 5;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll x, n, m, w, t;
typedef pair<ll, ll> pll;
pll s[maxn], a[maxn];
ll sum[maxn], mn[maxn], f[maxn];
int sta[maxn], top;
double solpe(int j, int k){
	return (double)(f[k] - sum[k] + sum[j] - f[j]) / (k - j); 
}
void push(int i){
	while(top > 1 && solpe(sta[top - 1], sta[top]) >= solpe(sta[top], i))--top;
	sta[++top] = i;
}
void bound(int x){
	int l = 1, r = top;
	while(l <= r){
		if(r - l <= 4){
			for(int i = l; i <= r; ++i)f[x] = min(f[x], f[sta[i]] + sum[x] - sum[sta[i]] + w * (x - sta[i]) * mn[x]);
			return;
		}
		int mid =(l + r) >> 1;
		if(solpe(sta[mid], sta[mid + 1]) > mn[x] * w)r = mid;
		else l = mid;
	}
}
int main(){
	x = read(), n = read(), m = read(), w = read(), t = read();
	for(int i = 1; i <= n; ++i)s[i].second = read(), s[i].first = s[i].second % t;
	s[++n] = pll(x % t, x);
	for(int i = 1; i <= m; ++i)a[i].first = read(), a[i].second = read();
	sort(a + 1, a + m + 1); sort(s + 1, s + n + 1);
	for(int i = 1; i <= m; ++i)sum[i] = sum[i - 1] + a[i].second;
	a[m + 1].first = inf;
	int p = 1;
	for(int i = 1; i <= m; ++i)mn[i] = inf;
	for(int i = 1; i <= m; ++i){
		while(p <= n && s[p].first < a[i].first) ++p;
		while(p <= n && s[p].first < a[i + 1].first)mn[i] = min(mn[i], s[p++].second / t);
	}
	push(0);
	for(int i = 1; i <= m; ++i){
		f[i] = f[i - 1] + w * (x / t  + (x % t >= a[i].first));
		if(mn[i] != inf)bound(i);
		push(i);
	}
	f[m] = f[m] + w * (x / t + 1);
	printf("%lld\n", f[m]);
	return 0;
}
posted @ 2022-09-22 16:47  Chen_jr  阅读(35)  评论(1编辑  收藏  举报