Atcoder Beginner Contest 260D - Draw Your Cards 解题报告

Atcoder Beginner Contest 260D - Draw Your Cards 解题报告

1 题目链接

abc260_d

2 题目大意

题目 : 抽牌
题目大意 :

给定 N N N个数字,对于每一个数字,找到已经生成的栈中栈顶大于该数字的最小值,并将该数字存入到栈中。
每当一个栈内的数据总量达到 K K K时,则“吃掉”这个栈,被“吃掉”的栈不能再存入新元素。
依次输出每一个元素被“吃掉”的时间。(对于没被吃的元素输出-1

3 解法分析

第一眼就是模拟 (最近的数据结构有点多啊)
用一个反向并查集(存储每个节点的子节点)来存储栈。
开一个top数组用于存储每个栈的栈顶并维护top数组的单调性(实际上不用维护,因为单调性一直存在)。
判断其是否大于最后一个栈的栈顶。

  • 如果是,则建立一个新栈。
  • 否则,进入下一层循环。

上述过程光是想象都觉得麻烦,于是又苦思冥想好一会儿,发觉map是个好东西。
对于这道题我们可以合理地使用指针(虽然本蒟蒻也不太会),从而达到以最短的代码实现快于模拟的程序(何乐不为?)。具体见代码。

4 解法总结

4.1 反向并查集

优点 : 思维简单。
缺点 : 代码时间TLE
关于单调性的说明 :

  • top数组为空时,显然符合单调性。
  • 若需新建栈,显然需放在最后,因需新建栈是数据更大,所以也满足单调性。
  • 若一个栈更新,则这个站的栈顶必定大于前一个栈,此时满足单调性。

综上所述,无论top数组在何时它都满足单调性。
注 : 因后面二分,所以top数组必须满足单调性。

4.2 指针

优点 : 代码量极短。
缺点 : 思维相对复杂,指针新手难以上手。
(本蒟蒻是个奇葩)

5 AC Code

蒟蒻代码 #001
#include <bits/stdc++.h>
#define N 200007
using namespace std;

int n, k, x, p[N], ans[N], under[N], top[N], idx;

int upper(int t) {
	register int l = 1, r = idx, res = 1;
	while (l < r) {
		register int mid = (l + r) >> 1;
		if (top[mid] > t)
			r = mid;
		else
			l = res = mid + 1;
	}
	return res;
}

int main() {
	memset(ans, -1, sizeof ans);
	scanf("%d%d", &n, &k);
	for (register int i = 1, t; i <= n; ++i) {
		scanf("%d", &x);
		if(x > top[idx]) {
			top[++idx] = x;
			p[x] = 1;
			t = idx;
		}
		else {
			t = upper(x);
			p[x] = p[top[t]] + 1;
			under[x] = top[t];
			top[t] = x;
		}
		if(p[x] == k) {
			while(x) {
				ans[x] = i;
				x = under[x];
			}
			for (register int j = t; j < idx; ++j)
				top[j] = top[j + 1];
			--idx;
		}
	}
	for (register int i = 1; i <= n; ++i)
		printf("%d\n", ans[i]);
	return 0;
}
蒟蒻代码 #002
#include <bits/stdc++.h>
#define N 200007
using namespace std;

int n, k, x;
map <int, vector <int> > t;//索引->最上面   值->存的东西 
//map相当于一个mulityset < pair <int, int> >
//那么指向second即时那个vector(纯属个人对于map使用指针的理解
vector <int> ans (N, -1);

int main() {
	scanf("%d%d", &n, &k);
	for (register int i = 1; i <= n; ++i) {
		scanf("%d", &x);
		register auto pos = t.lower_bound(x);//找到第一个满足条件的索引的指针 
		if (pos != t.end()) {//特判找不到 
			t[x] = move(pos/*指针*/ ->/*指向*/ second/*值*/);
					//把t[pos]里的值挪到t[x]
//move函数是个好东西,直接省了个for循环(虽然for循环也不会炸 
			t.erase(pos/*可传指针也可传下标*/);//以pos为顶的就不存在 
		}
		t[x].push_back(x);//显然不存在 
		if (t[x].size() == k) {//给它吃了 
			for (register auto it : t[x])
				ans[it] = i;
			t.erase(x);
		}
	}
	for (register int i = 1; i <= n; ++i)
		printf("%d\n", ans[i]);
    return 0;
}//指针做法

6 不正经的总结

果然代码的时间跟思考的时间是成负相关的 (对此深有体会)
这篇题解竟只用了我 50 m i n 50min 50min左右的时间!
在编写第二种写法时与神犇@2020张海洋有些许讨论,令本蒟蒻对指针有了更深的理解。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值