思维题合集

猜骆驼

题意:

三个数组,每个都是1到n的排列, 定义<i, j> 为good pair,当且仅当,在三个数组中,i和j的相对关系一样。给出三个数组,求good pair的数目。

Sample InputSample Output
4
2 3 1 4
2 1 4 3
2 4 3 1
3

解析

先考虑两两数组,可以通过以一组为基准(rank,相对大小),求解另一组的逆序对数目,即not good pair的数目。
那三个数组呢?考虑n=2,只有四种情况:
12 12 12
21 21 21
12 12 21
21 21 12
所以可以对三个数组两两求解逆序对,最后计算good pair数目。

神奇的分割

题意

一组和为 x x x的正整数集合称为 x x x的一个分割,形如 x = a 1 + a 2 + . . . + a n x = a_1 + a_2 + ... + a_n x=a1+a2+...+an的分割满足以下条件时,称作神奇的分割。

  1. a i − 1 ≤ a i ≤ a i − 1 + 1 a_{i-1}\leq a_{i} \leq a_{i-1}+1 ai1aiai1+1
  2. a n = a 1 + 2 a_n = a_1 + 2 an=a1+2

f ( x ) f(x) f(x)表示n的神奇分割的种类数,询问给出 l , r l,r l,r,求 f ( l ) + f ( l + 1 ) + . . . + f ( r ) f(l) + f(l + 1) + ... + f(r) f(l)+f(l+1)+...+f(r)

分析

  • 拆分肯定由三个连续的数字构成,比如 l , l + 1 , l + 2 l, l + 1, l + 2 l,l+1,l+2,并且每种至少有一个。
  • 看一看能不能枚举 l l l来初始化 f ( x ) f(x) f(x)
  • 再枚举拆分中数的个数为t,则拆分的和可以表示为 l ∗ t + x + 2 ∗ y l * t + x + 2 * y lt+x+2y,x表示l + 1的个数,y表示 l + 2的个数
  • 找规律:y = 1时,可以表示的范围 [ l ∗ t + 3 , l ∗ t + t ] [l * t + 3, l * t + t] [lt+3,lt+t], y = 2时,可以表示的范围 [ l ∗ t + 5 , l ∗ t + 1 ] [l * t + 5, l * t + 1] [lt+5,lt+1], y = 3时,可以表示的范围 [ l ∗ t + 7 , l ∗ t + 2 ] [l * t + 7, l * t + 2] [lt+7,lt+2],…,y = t - 2时,可以表示的范围 [ l ∗ t + 2 ∗ t − 3 , l ∗ t + 2 ∗ t − 3 ] [l * t + 2 * t - 3, l * t + 2 * t - 3] [lt+2t3,lt+2t3],发现,y确定的时候,对应一段区间的f值都++,涉及到区间加,可以使用差分数组,实现O(1)更新。
  • 但还是有一个问题,如果枚举y,那么复杂度会超时。再仔细观察以下,当l和t固定的时候,更新差分数组时,对应了t - 2个左端点(每个间距2)加一,t - 2个右端点(间距1)减一。容易想到,-1操作也是区间操作,可以二次差分,那么+1操作可以二次差分吗?也是可以的,虽然常见的差分是指和上一个差分,但其实步长为2(包括其他值)也是可以的,最后也能通过累加得到正确值,所以可以枚举l和t,维护两个二次差分数组,一个步长为一,一个步长为2,最后分别求和,再合并,最终再求和得到f数组。

重点:找规律+二次差分
区间更新 - > 差分

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 400000 + 10;
ll tow[maxn], one[maxn], ans[maxn];
void init()
{
	int n = 100000;
	for(int i = 1; i <= n; ++i)
		for (int j = 3; j * i <= n; ++j)
		{
			//更新左端点区间加(步长为2)
			tow[i * j + 3]++;
			tow[i * j + 2 * j - 3 + 2]--;
			//更新右端点区间减(步长为1)
			one[i * j + j + 1]--;
			one[i * j + 2 * j - 3 + 2]++;//如果数组只有1e5会越界
		}
	//计算一次差分数组
	for (int i = 2; i <= n; ++i)tow[i] += tow[i - 2];
	for (int i = 1; i <= n; ++i)one[i] += one[i - 1];
	//合并一次差分数组
	for (int i = 1; i <= n; ++i)ans[i] = tow[i] + one[i];
	//计算f(x)
	for (int i = 1; i <= n; ++i)ans[i] += ans[i - 1];
	//计算f(x)的前缀和
	for (int i = 1; i <= n; ++i)ans[i] += ans[i - 1];
}
int main()
{
	init();
	int t; scanf("%d", &t);
	for(int tc = 1; tc <= t; ++tc)
	{
		int l, r; scanf("%d%d", &l, &r);
		printf("Case #%d: %lld\n",tc, ans[r] - ans[l - 1]);
	}
	system("pause");
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值