您的位置:首页 > 其它

hdu 4790 数论 实现 组合数学

2013-11-18 18:52 344 查看
题意:从给定的[a,b]区间选出一个数x,从[c,d]区间选出数y,问有多少对(x,y)满足(x+y) % p = m

思路:

其实成都现场赛的时候我在网上就看到题了,觉得这道题我自己的算法编码好麻烦,现场这么多大神过,应该有什么简单的解法吧。。然后,然后成都现场赛重现之后看了几个题解,编码都挺复杂的------对我这种一分多种情况考虑就跪的菜鸟来说。于是硬着头皮按自己的思路写了一发,1Y,调试挺久,但代码看题来还挺简单。

我的第一个想法就是把‘模p等于m’ 变成‘模p等于0’,A ≡ m (mod p) A-m ≡ 0 (mod p)

所以,‘模p等于m’变成‘模p等于0’很简单,把区间[a,b]或者[c,d]左移m位即可,变成[a-m, b-m] 或者[c-m, d-m],我这里移的是[a,b]。要是a-m小于0的话根据模的性质,我们可以再右移p个单位,变成[a
- m + p, b - m + p]

然后想到fushuai提出的容斥定理,猜想 f( [a,b], [c,d] ) = f( [0, b], [0,d] ) + f( [0, a-1], [0, c-1] ) - f( [0, a-1], [0, d] ) - f( [0, c-1], [0, b] )

写好点 ans = f(b,d) + f(a-1, b-1) - f(a-1, d) - f(c-1, b)

我也不知道为什么这里容斥是对的,组合学的证明对我来说总是太难了。。。那我们目标就变成了求给定区间[0, a], [0,b],从中选出x,y,使得(x+y) % p = 0有多少对,记为f(a,b)。

[0,a]区间可以拆成 : 0, [1..p], [1..2p],...,[1..kp], kp+1, kp+2...a

就是按单位长度为p,把区间[0,a]拆开。。中间那部分可以合成一部分,则变成 : 0, [1..kp], kp+1, kp+2...a

那[0,a]就被我们拆成了3部分,第一部分单独一个 0 ,第二部分是k个p的完全剩余类[1..kp],第三部分是不足p的完全剩余类的部分。

同理拆[0, b]。 为什么要把0出来?我说不清,我也是在调试的时候发现的,一开始是把0归到第一个剩余类里面的。。只能说知道,还不能说理解。。有理解地比较好的大神求教一下。

接下来就是分情况计算啦。。可能我说的比较啰嗦,其实下面大家可以自己画个图推了。。

(1)对于[0,a]的第一部分0,有b/p+1种情况使得0 + y ≡ 0 (mod p),[0,b]的0有a/p+1种情况使得x + 0 ≡ 0 (mod p),因为两个0 + 0重复了一次,减1

(2)对于[0,a]的第二部分,有p * (a/p) * (b/p) 种情况

(3)对于[0,a]的第三部分,从b的第二部分[1..kp]选出y的话,有(a % p) * (b/p) 种情况,a%p表第三部分的大小,b/p表第三部分的这些数可以在[0,b]的第二部分分别找到多少个y使得x
+ y ≡ 0 (mod p)。同理[0,b]有(b%p) * (a/p)

然后,从[0,a]的第三部分选出x,从[0,b]的第三部分选出y,有多少种情况,我推出来的公式是max{0,
b%p - (p-a%p) + 1) } , 其中a%p >= b%p

#include <algorithm>
#include <stdio.h>
using namespace std;
typedef		long long 		ll;
ll p, m;
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a%b); }
ll calc(ll a, ll b)
{
	if(a < 0 || b < 0) return 0;
	ll ans = 0;

        ans += p * (a/p) * (b/p) + (a % p) * (b/p) + (b % p) * (a/p);
	ans += a/p + b/p + 1;	//x=0,y=kp和y=0,x=kp的情况
	a = a % p, b = b % p;
	if(a < b) swap(a, b);
	ans += max((ll)0, b - (p-a) + 1); //[0,a],[0,b]第三部分选x/y的情况

	return ans;
}
int main()
{
	int cases, Cas = 0;  ll a, b, c, d;
	scanf("%d", &cases);
	while(cases--) {
		scanf("%lld %lld %lld %lld %lld %lld", &a, &b, &c, &d, &p, &m);
		ll tot = (b - a + 1) * (d - c + 1);
		if(a >= m) a -= m, b -= m;                       //左移区间
		else 	   a = a - m + p, b = b - m + p;

		ll ans = calc(b, d) + calc(a-1, c-1) - calc(a-1, d) - calc(c-1, b);
		ll d = gcd(ans, tot);
		ans /= d, tot /= d;
		printf("Case #%d: %lld/%lld\n", ++Cas, ans, tot);
	}
	return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: