HDU 4609 3-idiots FFT
传送门: http://acm.hdu.edu.cn/showproblem.php?pid=4609
题意
给n条边,问任取三条边是否能构成三角形的概率。
思路
首
先
把
题
意
转
化
一
下
,
即
找
下
列
式
子
的
个
数
:
首先把题意转化一下,即找下列式子的个数:
首先把题意转化一下,即找下列式子的个数:
∑
i
=
1
n
∑
j
=
1
n
∑
k
=
1
n
a
i
+
a
j
>
a
k
C
n
3
(
i
≠
j
≠
k
且
a
i
和
a
j
是
较
小
的
两
条
边
)
\frac{\sum_{i=1}^n\sum_{j=1}^n\sum_{k=1}^na_i+a_j>a_k}{C_n^3}\;\;(i\ne j\ne k且a_i和a_j是较小的两条边)
Cn3∑i=1n∑j=1n∑k=1nai+aj>ak(i=j=k且ai和aj是较小的两条边)
看 到 三 角 形 , 首 先 想 到 就 是 把 所 有 的 a i + a j 的 个 数 存 在 一 个 数 组 里 , 然 后 再 逐 一 判 断 每 一 个 a k 是 否 符 合 。 看到三角形,首先想到就是把所有的a_i+a_j的个数存在一个数组里,然后再逐一判断每一个a_k是否符合。 看到三角形,首先想到就是把所有的ai+aj的个数存在一个数组里,然后再逐一判断每一个ak是否符合。
但 是 , O ( n 2 ) 的 复 杂 度 是 会 T 的 , 但 是 O ( n l o g n ) 的 复 杂 度 不 会 T , 那 么 就 需 要 用 到 F F T 的 知 识 了 。 \red{但是,O(n^2)的复杂度是会T的,但是O(nlogn)的复杂度不会T,那么就需要用到FFT的知识了。} 但是,O(n2)的复杂度是会T的,但是O(nlogn)的复杂度不会T,那么就需要用到FFT的知识了。
F F T 是 两 个 多 项 式 相 乘 , 但 是 上 面 是 要 让 我 们 两 个 相 加 , 没 关 系 , 我 们 让 a i 为 x 的 指 数 , a i 出 现 的 次 数 为 该 x 的 系 数 , 用 一 个 c n t 数 组 保 存 , 即 : FFT是两个多项式相乘,但是上面是要让我们两个相加,没关系,我们让a_i为x的指数,a_i出现的次数为该x的系数,用一个cnt数组保存,即: FFT是两个多项式相乘,但是上面是要让我们两个相加,没关系,我们让ai为x的指数,ai出现的次数为该x的系数,用一个cnt数组保存,即:
A ( x ) = c n t [ a [ 0 ] ] x a [ 0 ] + c n t [ a [ 1 ] ] x a [ 1 ] + . . . + c n t [ a [ n − 1 ] ] x a [ n − 1 ] A(x)=cnt[a[0]]x^{a[0]}+cnt[a[1]]x^{a[1]}+...+cnt[a[n - 1]]x^{a[n-1]} A(x)=cnt[a[0]]xa[0]+cnt[a[1]]xa[1]+...+cnt[a[n−1]]xa[n−1]
所 以 我 们 让 A ( x ) × A ( x ) , 就 可 以 得 到 任 意 两 条 边 之 和 , 并 且 得 到 该 和 的 个 数 , 并 存 在 n u m 数 组 里 。 当 然 , 这 里 会 出 现 很 多 问 题 。 所以我们让A(x)\times A(x),就可以得到任意两条边之和,并且得到该和的个数,并存在num数组里。当然,这里会出现很多问题。 所以我们让A(x)×A(x),就可以得到任意两条边之和,并且得到该和的个数,并存在num数组里。当然,这里会出现很多问题。
1
、
i
≠
j
,
所
以
要
让
a
i
+
a
i
的
个
数
减
1
,
即
n
u
m
[
a
[
i
]
+
a
[
i
]
]
−
−
;
1、i\ne j,所以要让a_i+a_i的个数减1,即num[a[i]+a[i]]--;
1、i=j,所以要让ai+ai的个数减1,即num[a[i]+a[i]]−−;
2
、
先
取
a
1
后
取
a
3
和
先
取
a
3
后
取
a
1
是
一
样
的
,
所
以
我
们
需
要
让
每
一
个
和
都
要
除
2
,
即
n
u
m
[
i
]
/
=
2
;
2、先取a_1后取a_3和先取a_3后取a_1是一样的,所以我们需要让每一个和都要除2,即num[i]/=2;
2、先取a1后取a3和先取a3后取a1是一样的,所以我们需要让每一个和都要除2,即num[i]/=2;
然 后 我 们 用 一 个 s u m 数 组 存 下 n u m 数 组 的 前 缀 和 , 然 后 遍 历 每 条 边 , 取 两 条 边 之 和 比 该 条 边 大 的 个 数 , 即 a n s + = s u m [ a [ n − 1 ] ∗ 2 ] − s u m [ a [ i ] ] , 前 面 的 a [ n − 1 ] ∗ 2 是 前 缀 和 最 大 的 地 方 。 然后我们用一个sum数组存下num数组的前缀和,然后遍历每条边,取两条边之和比该条边大的个数,即ans+=sum[a[n - 1]*2]-sum[a[i]],前面的a[n-1]*2是前缀和最大的地方。 然后我们用一个sum数组存下num数组的前缀和,然后遍历每条边,取两条边之和比该条边大的个数,即ans+=sum[a[n−1]∗2]−sum[a[i]],前面的a[n−1]∗2是前缀和最大的地方。
但 是 , 还 是 有 很 多 问 题 ! ! ! 在 刚 开 始 的 时 候 说 过 , 两 条 边 之 和 必 须 是 两 条 较 小 的 边 , 所 以 还 要 处 理 下 面 几 种 情 况 : \red{但是,还是有很多问题!!!}在刚开始的时候说过,两条边之和必须是两条较小的边,所以还要处理下面几种情况: 但是,还是有很多问题!!!在刚开始的时候说过,两条边之和必须是两条较小的边,所以还要处理下面几种情况:
1
、
两
条
边
都
大
于
a
[
i
]
,
即
a
n
s
−
=
(
n
−
i
−
1
)
∗
(
n
−
i
−
1
)
/
2
;
1、两条边都大于a[i],即ans-=(n-i-1)*(n-i-1)/2;
1、两条边都大于a[i],即ans−=(n−i−1)∗(n−i−1)/2;
2
、
一
条
边
大
于
a
[
i
]
,
一
条
边
小
于
a
[
i
]
,
即
a
n
s
−
=
(
n
−
i
−
1
)
∗
i
;
2、一条边大于a[i],一条边小于a[i],即ans-=(n-i-1)*i;
2、一条边大于a[i],一条边小于a[i],即ans−=(n−i−1)∗i;
3
、
两
条
边
中
有
一
条
边
等
于
a
[
i
]
,
即
a
n
s
−
=
(
n
−
1
)
;
3、两条边中有一条边等于a[i],即ans-=(n-1);
3、两条边中有一条边等于a[i],即ans−=(n−1);
最 后 的 a n s 就 是 满 足 条 件 的 所 有 个 数 , 然 后 计 算 C n 3 , 最 后 算 概 率 , 保 留 7 位 小 数 。 最后的ans就是满足条件的所有个数,然后计算C_n^3,最后算概率,保留7位小数。 最后的ans就是满足条件的所有个数,然后计算Cn3,最后算概率,保留7位小数。
但 是 , 博 主 t s b 了 ! ! ! 明 知 道 n 有 1 e 5 , 然 后 计 算 C n 3 会 爆 i n t , 所 以 我 还 用 一 个 l o n g l o n g 的 变 量 保 存 下 来 , 即 t = n ∗ ( n − 1 ) ∗ ( n − 2 ) / 6 , 是 不 是 有 一 点 问 题 , 那 就 是 要 在 n 之 前 加 一 个 ( l o n g l o n g ) 强 制 转 换 一 下 , 我 不 过 在 这 个 地 方 错 了 10 多 发 吧 。 。 。 \red{但是,博主tsb了!!!}明知道n有1e5,然后计算C_n^3会爆int,所以我还用一个longlong的变量保存下来,即t=n*(n-1)*(n-2)/6,是不是有一点问题,那就是要在n之前加一个\red{(longlong)}强制转换一下,我不过在这个地方错了10多发吧。。。 但是,博主tsb了!!!明知道n有1e5,然后计算Cn3会爆int,所以我还用一个longlong的变量保存下来,即t=n∗(n−1)∗(n−2)/6,是不是有一点问题,那就是要在n之前加一个(longlong)强制转换一下,我不过在这个地方错了10多发吧。。。
Code(826MS)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;
#define INF 0x3f3f3f3f
#define lowbit(x) x & (-x)
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)
// const ll mod = 998244353;
// const ll mod = 1e9 + 7;
// const double eps = 1e-6;
const double PI = acos(-1);
const int N = 4e5 + 10;
struct Complex {
double r, i;
Complex() {r == 0; i = 0;};
Complex(double real, double imag) : r(real), i(imag) {};
}A[N], B[N];
Complex operator + (Complex a, Complex b) {
return Complex(a.r + b.r, a.i + b.i);
}
Complex operator - (Complex a, Complex b) {
return Complex(a.r - b.r, a.i - b.i);
}
Complex operator * (Complex a, Complex b) {
return Complex(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r);
}
int rev[N], len, lim = 1;
void FFT(Complex *a, int opt) {
for(int i = 0;i < lim; i++) {
if(i < rev[i])
swap(a[i], a[rev[i]]);
}
for(int dep = 1;dep <= log2(lim); dep++) {
int m = 1 << dep;
Complex wn = Complex(cos(2.0 * PI / m), opt * sin(2.0 * PI / m));
for(int k = 0;k < lim; k += m) {
Complex w = Complex(1, 0);
for(int j = 0;j < m / 2; j++) {
Complex t = w * a[k + j + m / 2];
Complex u = a[k + j];
a[k + j] = u + t;
a[k + j + m / 2] = u - t;
w = w * wn;
}
}
}
if(opt == -1) { // 逆变换
for(int i = 0;i < lim; i++) a[i].r /= lim;
}
}
int getBit(int n) {
while(lim < (n << 1)) {
lim <<= 1;
len++;
}
for(int i = 0;i < lim; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
}
ll a[N], cnt[N];
ll sum[N], num[N];
void solve() {
int T;
scanf("%d",&T);
while(T--) {
mem(rev, 0);
mem(A, 0);
mem(num, 0);
mem(cnt, 0);
int n;
len = 0, lim = 1;
scanf("%d",&n);
for(int i = 0;i < n; i++) {
scanf("%lld",&a[i]); // 把a[i]看成x的指数,使乘法变加法
cnt[a[i]]++; // 把a[i]看成每个x^{a_i}的系数,然后再FFT
}
sort(a, a + n);
getBit(a[n - 1] + 1);
int Max = a[n - 1] * 2;
for(int i = 0;i <= a[n - 1]; i++) {
A[i].r = cnt[i];
}
FFT(A, 1);
for(int i = 0;i < lim; i++) {
A[i] = A[i] * A[i];
}
FFT(A, -1);
for(int i = 0;i < lim; i++) {
num[i] = A[i].r = (ll)(A[i].r + 0.5); // FFT之后每个a_i+a_j的系数存在num里
}
for(int i = 0;i < n; i++) {
num[a[i] + a[i]]--; // 把i=j的部分减掉
}
sum[0] = 0;
for(int i = 0;i <= Max; i++) {
num[i] /= 2; // 重复取,除2即可
sum[i] = sum[i - 1] + num[i]; // 取前缀和,最后判断的时候方便一点
}
ll ans = 0;
for(int i = 0;i < n; i++) {
ans += sum[Max] - sum[a[i]]; // 统计大于a[i]
}
for(int i = 0;i < n; i++) { // 处理符合两边大于a_k但是不正确的个数(因为判断是否为三角形必须是两条较小的边大于最长边)
ans -= ll(n - i - 1) * (n - i - 2) / 2; // 两边都大于a[i]
ans -= ll(n - i - 1) * i; // 一边大于a[i],一边小于a[i]
ans -= ll(n - 1); // 两边边中有一条边等于a[i]
}
ll t = 1ll * n * (n - 1) * (n - 2) / 6;
printf("%.7lf\n",(double)ans / t);
}
}
signed main() {
ios_base::sync_with_stdio(false);
//cin.tie(nullptr);
//cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed test_index_for_debug = 1;
char acm_local_for_debug = 0;
do {
if (acm_local_for_debug == '$') exit(0);
if (test_index_for_debug > 20)
throw runtime_error("Check the stdin!!!");
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
solve();
#endif
return 0;
}