FFT&容斥原理 HDU4609

9 篇文章 0 订阅
3 篇文章 0 订阅

3-idiots

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 8695    Accepted Submission(s): 3010

 

Problem Description

King OMeGa catched three men who had been streaking in the street. Looking as idiots though, the three men insisted that it was a kind of performance art, and begged the king to free them. Out of hatred to the real idiots, the king wanted to check if they were lying. The three men were sent to the king's forest, and each of them was asked to pick a branch one after another. If the three branches they bring back can form a triangle, their math ability would save them. Otherwise, they would be sent into jail.
However, the three men were exactly idiots, and what they would do is only to pick the branches randomly. Certainly, they couldn't pick the same branch - but the one with the same length as another is available. Given the lengths of all branches in the forest, determine the probability that they would be saved.

 

Input

An integer T(T≤100) will exist in the first line of input, indicating the number of test cases.
Each test case begins with the number of branches N(3≤N≤105).
The following line contains N integers a_i (1≤a_i≤105), which denotes the length of each branch, respectively.

 

Output

Output the probability that their branches can form a triangle, in accuracy of 7 decimal places.

 

Sample Input

2

4

1 3 3 4

4

2 3 3 4

 

Sample Output

0.5000000

1.0000000

 

求选取3个数能组成三角形的概率有多大。

我们可以找到边之间的关系:

 ai <= aj <= ak

 ai + aj > ak

加法我们依旧可以通过多项式乘法系数得到相应答案,又因为不是=而是>,我们需要处理出前缀和。

并且,要注意去顺序。

但这些满足第二个条件的答案中,有一些不满足第一个条件,于是我们要去掉那些:

一大一小两大
两小一大一小

考虑中间点是k,左到右i从小到大,下到上j从小到大,如上陋图

(1)去掉右上角都是大于的情况,不重复取,还要去掉顺序

(2)去掉2个一样的一大一小的情况,去序

(3)去掉自身(k)和其他所有组成的情况(!没有想到!!!

嗯这个处理想起来就很难,转换为代码感觉也很复杂:

不如先排个序吧,既然跟大小关系有关的话;

然后把位置关系作为大小关系,注意去序去重等写出如下处理代码:

ans -= (1ll*(N-i-2)*(N-i-1)/2/*2大,去序*/ + 1ll*(N-i-1)*i/*2部分1大1小,去序*/ + 1ll*(N-1)/*1本身1其他*/) ;

好了,那我们来思考一下把位置关系直接作为大小关系操作的正确性吧,去一大一小时遇到同样的数为什么也可以这样?

因为3个相同数是因为顺序而存在重复,而一个小于的数包括相同数和一个大于的数包括相同数也都是要去掉的部分,遍历下来是都去了一遍的,感觉挺对的

因为FFT然后去本身/去重(3)后,3个相同数是算的x*(x-1)*(x-2)-x*(x-1)

而正确的数目应是C(3,x)

没有定量细想,不过这个考量和思路学到了orz

 

之前有一个因为数组开小了,越界改了奇怪东西导致TLE的崩,这次更厉害呢…

一开始一直MLE,疯狂去数组,6个去到了只剩必要的3个还是MLE,就很绝望,才发现maxn=1<<18+5,移位运算没加括号,+优先级比<<高,于是…我一直开的是1<<23的数组…

然后之后又一直wa,各种加longlong还是wa,后来发现有一个longlong加在乘法运算括号外了,已经爆int了我emmm

菜哭

 

 

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

typedef long long LL;
const double PI=acos(-1.0);
const int maxn=(1<<18)+5;

struct Complex
{
	double re,im;
	Complex(double r=0.0,double i=0.0) {re=r,im=i;}
};
Complex operator +(const Complex&A,const Complex&B) {return Complex(A.re+B.re,A.im+B.im);}
Complex operator -(const Complex&A,const Complex&B) {return Complex(A.re-B.re,A.im-B.im);}
Complex operator *(const Complex&A,const Complex&B) {return Complex(A.re*B.re-A.im*B.im,A.re*B.im+A.im*B.re);}

Complex a[maxn<<1];
int n,N;
int stick[maxn];//,cnt[maxn<<1];
LL ans,sum[maxn<<1];
double res;

void FFT(Complex*a,int f)
{
	for(int i=1,j=n>>1,k;i<n-1;++i){
        if(i<j) swap(a[i],a[j]);
        k=n>>1;
        while(j>=k){
            j-=k;
            k>>=1;
        }
        if(j<k) j+=k;
	}
	for(int i=1;i<n;i<<=1){
		Complex wn(cos(f*2*PI/(i<<1)),sin(f*2*PI/(i<<1)));
		for(int j=0;j<n;j+=(i<<1)){
			Complex w(1,0);
			for(int k=j;k<j+i;++k){
				Complex x,y;
				x=w*a[k+i];
				y=a[k];
				a[k]=y+x;
				a[k+i]=y-x;
				w=w*wn;
			}
		}
	}
	if(f==-1)	//换了!
		for(int i=0;i<n;++i)
			a[i].re/=n;
}

int main()
{
	int T;

	scanf("%d",&T);
	while(T--){
		memset(a,0,sizeof(a));
		memset(sum,0,sizeof(sum));
		//memset(cnt,0,sizeof(cnt));
		ans=0;
		scanf("%d",&N);
		for(int i=0;i<N;++i){
			scanf("%d",&stick[i]);
			++a[stick[i]].re;
			//++cnt[stick[i]+stick[i]];
		}
		sort(stick,stick+N);
		n=1;
        while(n<(stick[N-1]+1)<<1) n<<=1;
		FFT(a,1);
		for(int i=0;i<n;++i)
			a[i]=a[i]*a[i];
		FFT(a,-1);
		for(int i=0;i<N;++i)
            --sum[stick[i]+stick[i]];
		for(int i=0;i<n;++i){
			sum[i]+=(LL)(a[i].re+0.5);
			sum[i]/=2;
            //卷积结果去自身去序
			if(i) sum[i]+=sum[i-1];	//前缀和
		}
		for(int i=1;i<N;++i){	//咕//不需要管是否相同,考虑位置关系emmm反正都要去emmm
			ans-=(1ll*(N-i-2)*(N-i-1)/2/*2大,去序!*/+1ll*(N-i-1)*i/*1大1小,去序!*/+1ll*(N-1)/*1本身1其他*/);
			ans+=(sum[n-1]-sum[stick[i]]);
		}
		res=ans*6*1.0/(1ll*N*(N-1)*(N-2));    //LL!
		printf("%.7f\n",res);
	}

	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值