首页 > 其他分享 >CSP-S模拟8

CSP-S模拟8

时间:2022-09-22 16:57:13浏览次数:63  
标签:nxt return int top long maxn CSP 模拟

不愧为 \(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

</details>

## C. 幽深府邸 


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

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

这样复杂度仍然不对,

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

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

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

<details>
<summary>code</summary>

```cpp
#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;
}

标签:nxt,return,int,top,long,maxn,CSP,模拟
From: https://www.cnblogs.com/Chencgy/p/16719925.html

相关文章

  • CSP - S 模拟9
    CSP-S模拟9赛时T2想了两个小时无果,极限一小时写T3险些模拟退役,最后3分钟调出来T3AARC125C考场上打表找规律我们先把\(k\)个数放好,考虑一个一个贪心插入。每回......
  • 简单模拟一个双向链表,用java实现
    1packagecom.gsh.test05;23/**4*节点类5*@param<E>6*/7publicclassNode<E>{8privateNode<E>pre;9privateEelement;10......
  • C语言上网计费系统模拟系统
    C语言上网计费系统模拟系统程序设计题2:上网计费系统模拟出题人:朱旻如面向专业:计算机学院难度等级:41问题描述本程序模拟根据上网清单、客户资料等生成客户上网账单......
  • 如何在不模拟 useRef() 的情况下测试 useRef()?
    如何在不模拟useRef()的情况下测试useRef()?有时你需要将一些React组件的方法暴露给它的父组件使用命令句柄()和前向引用().在之前的一篇文章中,我写了关于测试这些公......
  • 数组模拟环形队列
    简介对前面的数组模拟队列的优化,充分利用数组.因此将数组看做是一个环形的。(通过取模的方式来实现即可)代码实现importjava.util.Scanner;publicclassCir......
  • 各种模拟器连接android studio
      01、雷电模拟器 3、然后在其中输入你的雷电模拟器安装目录,回车运行。   4、完成后,再输入“adb.execonnect127.0.0.1:5555”回车运行。  02、夜神......
  • CSP-S模拟8
    Cat最喜欢清北营赛制了!但是这个赛制暗示了以下全是鬼畜题…… A.选举居然可以dp,我本来以为是贪心的题,联想到了学长提到的过关得到相应的星星,可以选择拿1颗或两颗,代价......
  • 电源地、模拟地、数字地
     电路板设计时直流电源地、模拟地、数字地如何区分?如何共地?发布时间:2016-05-2311:19 阅读:917 来源:技术文章责任编辑:深圳宏力捷PCB设计部电路板设计时直流电......
  • CSP-J考试大纲
    目录2.1.1计算机基础与编程环境【1】计算机的基本构成(CPU、内存、I/O设备等)【1】Windows、Linux等操作系统的基本概念及其常见操作【1】计算机网络和Internet的基本概......
  • CSP-S模拟8 选举 港口设施 幽深府邸 长途巴士
    DP和贪心的完美结合。T1:n个州,你要从中选出K个进行演讲,每个州有键值(a,b),代表获得选票需要a时间,得到助理人需要b时间,a<=b。得到助理人之后可以同时演讲,演讲时间可以累加。问最......