中石油训练赛 - Cafebazaar’s Chess Tournament(FFT)

题目大意:给出 n 个队伍,每个队伍有两个属性分别记为 a 和 b ,如果队伍 i 和队伍 j 比赛:

  1. a[ i ] > a[ j ] && b[ i ] > b[ j ] :i 队伍获胜
  2. a[ j ] > a[ i ] && b[ j ] > b[ i ] :j 队伍获胜
  3. 其余情况全部平局

获胜加一分,平局加 0.5 分,失败不扣分

现在有一个新加入的队伍,让这个队伍和已有的 n 支队伍互相比赛,该队伍的属性可以在比赛前设置为 n 支队伍中未出现过的任意实数,现在问该队伍能有多少种不同的得分结果

题目分析:首先转换模型,将两个属性映射到 x 轴和 y 轴上,然后一个队伍扔到二维平面坐标系上,会出现这种情况:

因为题目中约束了新加入队伍的属性不能与原有队伍的属性值相同,换句话说新加入的队伍只能出现在四个方格中,而不会出现在蓝色的直线或交点上,因为平局和必胜之间的加分是两倍的关系,所以可以令其相互独立,语言描述的话就是平行于 y 轴的直线右侧可以加 0.5 分,平行于 x 轴的直线上侧可以加 0,5 分,大概下图的样子:

然后将 n 个互不相同的点都扔到二维平面坐标系中,就会出现 n 条平行于 x 轴的直线和 n 条平行于 y 轴的直线,大概会交出很多很多的方格:

现在讨论一下,如果新加入的队伍位于红色方格时的贡献:

  1. 其下方有两条直线,可以得到 0.5 * 2 分
  2. 其左侧有两条直线,可以得到 0.5 * 2 分

总共可以得到两分,对于每个集合都可以这样计算一个分数,最后我们只需要对所有的方格内的数去重就是答案了

不难看出上述模型实质上就是对属性 a 进行排序后求个前缀和,对属性 b 排序后求个前缀和,然后从 a 的前缀和中遍历所有元素,与 b 的前缀和中的所有元素分别加和计算,共有 a * b 个答案,去重后就是答案

现在得到了一个朴素的 n * n 的模型,考虑优化

设属性 a 的前缀和的元素为集合 A ,属性 b 的前缀和的元素为集合 B,A 中的元素分别为 A[ 0 ] ~ A[ n ] ,B 中的元素分别为 B[ 0 ] ~ B[ m ] ,考虑多项式卷积,令 F(x)=a_0x^0+a_1x^1+...+a_nx^nG(x)=b_0x^0+b_1x^1+...+b_mx^m,在原模型中对应的元素相加,即 A[ x ] + B[ y ] ,在多项式卷积中对应(a_{A[x]}*a_{B[y]})x^{A[x]+B[y]},考虑系数都为 1 的话,得到的也将是x^{A[x]+B[y]},换句话说,在进行卷积之后的答案多项式,系数非零的位置对应的也就是上述模型去重之后的答案

直接卷积时间复杂度也是 n * m 级别的,考虑 FFT 优化,套个板子就好了,时间复杂度 nlogn

代码:
 

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
#include<complex>
#define cp complex < double >
using namespace std;
const double pi=acos(-1);
 
typedef long long LL;            
 
typedef unsigned long long ull;
 
const int inf=0x3f3f3f3f;
 
const int N=2e5+100;
 
int cnt1[N],cnt2[N];
 
bool vis1[N],vis2[N];
 
int lena=0,lenb=0,n,res[N<<2];
 
cp F[N<<2],G[N<<2],arr[N<<2],inv[N<<2];
 
void init()
{
    for (int i=0;i<n;i++)
    {
        arr[i]=cp(cos(2*pi*i/n),sin(2*pi*i/n));
        inv[i]=conj(arr[i]);
    }
}
void FFT(cp *a,cp *arr)
{
    int lim=0;
    while ((1<<lim)<n) lim++;
    for (int i=0;i<n;i++)
    {
        int t=0;
        for (int j=0;j<lim;j++)
            if ((i>>j) & 1) t|=1<<(lim-j-1);
        if (i<t) swap(a[i],a[t]);
    }
    for (int l=2;l<=n;l*=2)
    {
        int m=l/2;
        for (cp *buf=a;buf!=a+n;buf+=l)
            for (int i=0;i<m;i++)
            {
                cp t=arr[n/l*i]*buf[i+m];
                buf[i+m]=buf[i]-t;
                buf[i]+=t;
            }
    }
}
 
int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        cnt1[a]++,cnt2[b]++;
    }
    int sum=0;
    vis1[sum]=true;
    for(int i=1;i<N;i++)
    {
        if(!cnt1[i])
            continue;
        sum+=cnt1[i];
        vis1[sum]=true;
    }
    sum=0;
    vis2[sum]=true;
    for(int i=1;i<N;i++)
    {
        if(!cnt2[i])
            continue;
        sum+=cnt2[i];
        vis2[sum]=true;
    }
    for(int i=0;i<N;i++)
    {
        if(vis1[i])
            lena=i+1;
        if(vis2[i])
            lenb=i+1;
        F[i].real(vis1[i]);
        G[i].real(vis2[i]);
    }
    n=1;
    while(n<(lena+lenb))
        n<<=1;
    init();
    FFT(F,arr);FFT(G,arr);
    for(int i=0;i<n;i++)
        F[i]*=G[i];
    FFT(F,inv);
    for(int i=0;i<n;i++)
        res[i]=floor(F[i].real()/n+0.5);
    int ans=0;
    for(int i=0;i<lena+lenb-1;i++)
        ans+=!!res[i];
    printf("%d\n",ans);
 
   return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/108816969
今日推荐