P9809 [SHOI2006] 作业 Homework 浅显易懂讲解这道题为什么根号分治

题目:

我们有一堆数,找出模Y的最小值。

思路:

我们初步思考,会发现每个Y是一段,比如 1~Y , Y~2Y , 2Y~3Y ...

每个区间都可能有最小的答案。

这里对Y可以使用根号分治,因为:

当Y足够大时,每个区间都很大,区间数就很少。

而当Y足够小时,我们可以暴力这部分Y。

当Y足够大时,区间很大,我们对区间做处理:找大于1的最小值,大于Y的最小值,大于2Y的最小值,只需要找几次就能找完。

————

暂时规定Y小于V时,Y足够小。

A操作:

我们每插入一个数,挨着取模1~V并记录对应的最小值。(这里就是暴力,只记录1~V是可以承受的)

这样取模1~V的时候我们直接得到最小值。可以用map存。

(同时插入的数存入一个set中。)

B操作:

当模数小于V时,我们已经存好了,直接得到答案。

当模数大于V时,我们在每个区间找最小值。(因为区间很大,过几个区间可能就没有数了,结束就好了)

根号:

V怎么取呢?这。。

根号分治!

本题 Y ≤ 3e5,取个几百差不多,V*B == 3e5就行。(这个B对应大区间数目,大区间最小是V的时候也要覆盖整个Y,所以有个B)

可以取 : 480*625        近似根号了,因为我们区间长度是整数

代码细节:

可以直接看代码,也可以看这里帮助理解写法:

set存数,lower_bound是下界的意思(参考set::lower_bound - C++ Reference (cplusplus.com)

(可以测试lower_bound,当找的那个下界x不存在时,返回的是第一个比x大的数的迭代器

而up_bound找的就是第一个比x大的数的迭代器。这里我们要找最小的大于等于区间左边的数)

代码:


#define V 480
#define B 625

void solve()
{
	int n;
	cin >> n;
	set<int>s;
	unordered_map<int, int>m;
	for (int i = 1; i <= n; i++)
	{
		char op;
		int val;
		cin >> op >> val;
		if (op == 'A')
		{
			s.insert(val);
			for (int i = 1; i < V; i++)
			{
				if (m.count(i))m[i] = min(m[i], val % i);
				else m[i] = val % i;
			}
		}
		else
		{
			int ans = LLONG_MAX;
			if (val >= V)
			{
				for (int i = 0; i <= B; i++)
				{
					auto it = s.lower_bound(i * val);
					if (it == s.end())
						break;//都比i*val大了
					ans = min(ans, *it - (i * val));
				}
			}
			else
			{
				ans = m[val];
			}
			cout << ans << endl;
		}

	}
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
	return 0;
}

然而。。。:

值域分块可以再优化,我不会。

我导师的讲解:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值