洛谷P1494 小Z的袜子(莫队板子)

题目链接

题意:求区间内任取两点,颜色相同的概率。

题解:对于一个 [ l , r ] [l, r] [l,r]的区间,我们设一个 n u m num num数组, n u m [ i ] num[i] num[i]代表当前区间第 i i i个颜色的数量,那么我们最终需要求出的答案即为 ∑ i = 1 n n u m [ i ] ∗ ( n u m [ i ] − 1 ) C r − l + 1 2 \frac{\sum_{i = 1 }^n num[i]*(num[i]-1)}{C_{r - l + 1}^{2}} Crl+12i=1nnum[i](num[i]1)
然后就用莫队板子去维护一下 a n s ans ans就好了。

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

template <class T> inline void read(T &x) {
	int f = 0;x = 0;char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
	for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
	if (f) x = -x;
}

typedef long long ll;
const int maxn = 5e4 + 7;

int gcd(int a, int b) {
	return a == 0? b : gcd(b % a, a);
}

int n, m, cor[maxn], top[maxn], di[maxn], num[maxn];
int pos[maxn];
struct Node{
	int l, r, id;
}node[maxn];

bool cmp(Node a, Node b) {
    if (pos[a.l]==pos[b.l]){
        if (pos[a.l]&1) return a.r<b.r;
        else return a.r>b.r;	
    }
    else return pos[a.l]<pos[b.l];
	// if (pos[a.l] == pos[b.l]) return a.r < b.r;
	// return a.l < b.l;
}

void add(int j, int &ans) {
	ans -= (ll)num[cor[j]] * (ll)(num[cor[j]] - 1) / 2;
	num[cor[j]]++;
	ans += (ll)num[cor[j]] * (ll)(num[cor[j]] - 1) / 2;
}

void del(int j, int &ans) {
	ans -= (ll)num[cor[j]] * (ll)(num[cor[j]] - 1) / 2;
	num[cor[j]]--;
	ans += (ll)num[cor[j]] * (ll)(num[cor[j]] - 1) / 2;
}

void solve() {
	int pre = -1, ans = 0, pl = 1, pr = 0;
	for (int i = 1; i <= m; ++i) {
		int l = node[i].l, r = node[i]. r, id = node[i].id;
		//原本每次到新区间都重新计算一次,后来发现按奇偶排序,一直移快一点
		
		// if (pos[l] != pre) {
		// 	pre = pos[l];
		// 	ans = 0;
		// 	pl = l, pr = r;
		// 	memset(num, 0, sizeof(num));
		// 	for (int j = l; j <= r; ++j) {
		// 		ans -= (ll)num[cor[j]] * (ll)(num[cor[j]] - 1) / 2;
		// 		num[cor[j]]++;
		// 		ans += (ll)num[cor[j]] * (ll)(num[cor[j]] - 1) / 2;
		// 	}
		// 	int tmp = (ll)(r - l + 1) * (ll)(r - l) / 2;
		// 	if (tmp == 0) {
		// 		top[id] = 0, di[id] = 1;
		// 		continue;
		// 	}
		// 	int g = gcd(ans, tmp);
		// 	top[id] = ans / g, di[id] = tmp / g;
		// }
		// else {
			//注意自加自减的顺序
			while (l > pl) del(pl++, ans);
			while (l < pl) add(--pl, ans);
			while (r > pr) add(++pr, ans);
			while (r < pr) del(pr--, ans);
			int tmp = (ll)(r - l + 1) * (ll)(r - l) / 2;
			//题目提醒了l==r的情况,但下面的数据范围却没有,不特判会除0
			if (tmp == 0) {
				top[id] = 0, di[id] = 1;
				continue;
			}
			int g = gcd(ans, tmp);
			top[id] = ans / g, di[id] = tmp / g;
		// }
	}
}


int main() {
	read(n), read(m);
	for (int i = 1; i <= n; ++i) read(cor[i]);
	int len = sqrt(n);
	for (int i = 1; i <= n; ++i) pos[i] = (i + len - 1) / len;
	for (int i = 1; i <= m; ++i) {
		read(node[i].l), read(node[i].r);
		node[i].id = i;
	}
	sort(node + 1, node + 1 + m, cmp);
	solve();
	for (int i = 1; i <= m; ++i) {
		printf("%d/%d\n", top[i], di[i]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题可以使用贪心算法来解决。对于每一种图案的袜子,我们都要尽可能地凑成一对配套的袜子,因此我们可以分别统计左脚、右脚和左右脚都能穿的袜子的数量,然后按照以下规则进行配对: 1. 对于左脚或右脚的袜子,如果数量为偶数,则可以全部配对;如果数量为奇数,则只能配对其中的偶数个,剩下的一只袜子必须单独取出来。 2. 对于左右脚都能穿的袜子,如果数量为偶数,则可以全部配对;如果数量为奇数,则可以先取出一只袜子单独配对,然后剩下的袜子可以全部配对。 最后,如果还有剩余的袜子无法配对,则说明无法凑成一对配套的袜子,输出-1;否则,输出取出袜子的总数。 下面是代码实现: #include <iostream> #include <map> using namespace std; int main() { int n; cin >> n; map<int, int> left, right, both; int total = 0, left_cnt = 0, right_cnt = 0, both_cnt = 0; for (int i = 0; i < n; i++) { int p, m; char q; cin >> p >> q >> m; if (q == 'L') { left[p] += m; left_cnt += m; } else if (q == 'R') { right[p] += m; right_cnt += m; } else { both[p] += m; both_cnt += m; } total += m; } int cnt = 0; if (left_cnt % 2 == 1) { cnt += 1; left_cnt -= 1; } if (right_cnt % 2 == 1) { cnt += 1; right_cnt -= 1; } cnt += min(left_cnt, right_cnt); if (cnt == total) { cout << cnt << endl; return 0; } if (both_cnt % 2 == 1) { cnt += 1; both_cnt -= 1; } cnt += both_cnt; if (cnt == total) { cout << cnt << endl; return 0; } cout << -1 << endl; return 0; }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值