NOIP模拟(11.03)T1 区间

区间

题目背景:

11.03 NOIP模拟T1

分析:暴力

 

被今天的题毒瘤到了······T1全场一个AC,两个60分,其他全部是30及以下······

直接讲题,首先比较显然的,对于两个给定的数字,我们直接将1 ~ i当中两者出现次数之差算出来,那么前面出现同样的差值的位置到i就应该是一个合法的区间,这样做的话,我们的复杂度是nq,因为每一次询问都要扫一遍,考虑60分档当中说的,不同数字只会有最多50个,那么相当于只有2500组询问我们直接将所有的答案预处理出来就可以了。

Source:

/*
	created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <set>
#include <ctime>

const int MAXN = 8000 + 10;

int n, q, x, y, top;
int c[MAXN], rk[MAXN];
int cnt[MAXN][MAXN], ans[MAXN][MAXN];

struct data {
	int num, ori;
	inline bool operator < (const data &a) const {
		return num < a.num;
	}
} a[MAXN];

inline void read_in() {
	std::cin >> n >> q;
	for (int i = 1; i <= n; ++i) std::cin >> a[i].num, a[i].ori = i;
}

inline void pre_work() {
	std::sort(a + 1, a + n + 1);
	int i = 1;
	while (i <= n) {
		int j = i;
		while (a[i].num == a[i + 1].num) {
			if (i == n) break ;
			++i;
		}
		++top, rk[top] = a[i].num;
		for (int k = j; k <= i; ++k) c[a[k].ori] = top;
		++i;
	}
	static int temp[MAXN * 2];
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j <= top; ++j) cnt[i][j] = cnt[i - 1][j];
		cnt[i][c[i]]++;
	}	
	for (int i = 0; i <= top; ++i)
		for (int j = 0; j <= top; ++j) {
			memset(temp, 0, sizeof(int) * (n + 1 + n));
			for (int k = 0; k <= n; ++k) {
				ans[i][j] += temp[cnt[k][i] - cnt[k][j] + n];
				temp[cnt[k][i] - cnt[k][j] + n]++;
			}
		}
}

inline int binary(int x) {
	int l = 0, r = top + 1;
	while (l + 1 < r) {
		int mid = l + r >> 1;
		rk[mid] <= x ? l = mid : r = mid;
	}
	return (rk[l] == x) ? (l) : 0;
}

inline void solve() {
	while (q--) 
		std::cin >> x >> y, std::cout << ans[binary(x)][binary(y)] << '\n';
}

int main() {
//	freopen("interval.in", "r", stdin);
//	freopen("interval.out", "w", stdout );
	std::ios::sync_with_stdio(NULL);
	std::cin.tie(NULL), std::cout.tie(NULL);
	read_in();
	pre_work();
	solve();
	return 0;
}

考虑100分该怎么做,上一个算法的瓶颈在于,每一次都是O(n)的枚举,但是我们发现,如果当前位置上既不是x,也不是y的时候,其实它的贡献和上一个位置应该是相同的,那么我们可以考虑将贡献相同的位置直接算作一个位置,然后直接计算贡献之间和同一位置内部的贡献,这样的话,一次询问的复杂度就是两种数字出现的次数之和,考虑对于一个位置的枚举,显然只会在和其他的8000种颜色询问过程中被用到,那么也就是说最多只会被枚举8000次,那么总复杂度应该是O(n2)的。

注意:对于没有出现x, y的位置,应该与上一个出现这两个数字的位置等价,分段的时候内部的贡献应该为cnt * (cnt - 1) / 2,并且每次加上当前应该加上cnt而不是加1,边界有点恶心。

Source:

/*
	created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <set>
#include <ctime>

const int MAXN = 8000 + 10;
const int INF = 1000000000;

int n, q, x, y, top, ans;
int c[MAXN], rk[MAXN];
std::vector<int> pos[MAXN];

struct data {
	int num, ori;
	inline bool operator < (const data &a) const {
		return num < a.num;
	}
} a[MAXN];

inline void read_in() {
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i].num), a[i].ori = i;
}

inline void pre_work() {
	std::sort(a + 1, a + n + 1);
	int i = 1;
	while (i <= n) {
		int j = i;
		while (a[i].num == a[i + 1].num) {
			if (i == n) break ;
			++i;
		}
		++top, rk[top] = a[i].num;
		for (int k = j; k <= i; ++k) c[a[k].ori] = top;
		++i;
	}
	for (int i = 1; i <= n; ++i) pos[c[i]].push_back(i);
	for (int i = 0; i <= top; ++i) pos[i].push_back(n + 1);
}

inline int binary(int x) {
	int l = 0, r = top + 1;
	while (l + 1 < r) {
		int mid = l + r >> 1;
		rk[mid] <= x ? l = mid : r = mid;
	}
	return (rk[l] == x) ? (l) : 0;
}

inline int calc(int x) {
	return (x == 0) ? (0) : (x * (x - 1) / 2);
}

std::vector<int> p;
inline void solve() {
	while (q--) {
		scanf("%d%d", &x, &y), x = binary(x), y = binary(y), ans = 0;
		int h1 = 0, h2 = 0, last = 0, cur_sum = 0;
		static int temp[MAXN * 2];
		temp[n]++;
		while (last != n) {
			if (pos[x][h1] < pos[y][h2]) {
				int len = pos[x][h1] - 1 - last;
				ans += len * temp[cur_sum + n];
				ans += calc(len), temp[cur_sum + n] += len;
				last = pos[x][h1] - 1, ++h1, p.push_back(cur_sum + n);
				cur_sum++;
			} else {
				int len = pos[y][h2] - 1 - last;
				ans += len * temp[cur_sum + n];
				ans += calc(len), temp[cur_sum + n] += len;
				last = pos[y][h2] - 1, ++h2, p.push_back(cur_sum + n);
				cur_sum--;
			}
		}
		while (!p.empty()) temp[p.back()] = 0, p.pop_back();
		std::cout << ans << '\n';
	}
}

int main() {
//	freopen("interval.in", "r", stdin);
//	freopen("interval.out", "w", stdout);
	read_in();
	pre_work();
	solve();
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值