hdu 6088 Rikka with Rock-paper-scissors (2017 多校第五场 1004) 【组合数学 + 数论 + 模意义下的FFT】

题目链接

首先利用组合数学知识,枚举两人的总胜场数容易得到

这还不是卷积的形式,直接搞的话复杂度大概是O(n^2)的,肯定会TLE。但似乎和卷积有点像?想半天没想出来。。多谢Q巨提醒,才知道可以用下面这个公式进行转化

最后,化得的公式为

另外注意,上式右边是一个卷积的形式,但是,所得和的第一项是不需要加上的(不过图中公式没有体现)。结合实际意义大概就是,i==0&&j==0时,gcd(i,j)不存在约数d,虽然0可以被任意正整数整除 & 第一项不为0

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
#define upmo(a,b) (((a)=((a)+(b))%mod)<0?(a)+=mod:(a))    // 相加后取模 

int n,mod;

namespace FFT_MO    //前面需要有 mod(1e8~1e9级别),upmo(a,b) 的定义
{
    const int FFT_MAXN=1<<18;
    const db pi=3.14159265358979323846264338327950288L;
    struct cp
    {
        db a,b;
        cp(double a_=0,double b_=0)
        {
            a=a_,b=b_;
        }
        cp operator +(const cp&rhs)const
        {
            return cp(a+rhs.a,b+rhs.b);
        }
        cp operator -(const cp&rhs)const
        {
            return cp(a-rhs.a,b-rhs.b);
        }
        cp operator *(const cp&rhs)const
        {
            return cp(a*rhs.a-b*rhs.b,a*rhs.b+b*rhs.a);
        }
        cp operator !()const
        {
            return cp(a,-b);
        }
    }nw[FFT_MAXN+1],f[FFT_MAXN],g[FFT_MAXN],t[FFT_MAXN];    //a<->f,b<->g,t<~>c
    int bitrev[FFT_MAXN]; 

    void fft_init()    //初始化 nw[],bitrev[]
    {
        int L=0;while((1<<L)!=FFT_MAXN) L++;
        for(int i=1;i<FFT_MAXN;i++)  bitrev[i]=bitrev[i>>1]>>1|((i&1)<<(L-1));
        for(int i=0;i<=FFT_MAXN;i++) nw[i]=cp((db)cosl(2*pi/FFT_MAXN*i),(db)sinl(2*pi/FFT_MAXN*i));
    }

    // n已保证是2的整数次幂
    // flag=1:DFT |  flag=-1: IDFT
    void dft(cp *a,int n,int flag=1)
    {
        int d=0;while((1<<d)*n!=FFT_MAXN) d++;
        for(int i=0;i<n;i++) if(i<(bitrev[i]>>d))
            swap(a[i],a[bitrev[i]>>d]);    //    NOTICE!
        for(int l=2;l<=n;l<<=1)
        {
            int del=FFT_MAXN/l*flag;    // 决定 wn是在复平面是顺时针还是逆时针变化,以及变化间距
            for(int i=0;i<n;i+=l) // ?????????????????
            {
                cp *le=a+i,*ri=a+i+(l>>1);    // ?????????????????
                cp *w=flag==1? nw:nw+FFT_MAXN;    // 确定wn的起点
                for(int k=0;k<(l>>1);k++)
                {
                    cp ne=*ri * *w;
                    *ri=*le-ne,*le=*le+ne;
                    le++,ri++,w+=del;
                }
            }
        }
        if(flag!=1) for(int i=0;i<n;i++) a[i].a/=n,a[i].b/=n;
    }

    // convo(a,n,b,m,c) a[0..n]*b[0..m] -> c[0..n+m]
    void convo(LL *a,int n,LL *b,int m,LL *c)
    {
        for(int i=0;i<=n+m;i++) c[i]=0;
        int N=2;while(N<=n+m) N<<=1;    // N+1是c扩展后的长度
        for(int i=0;i<N;i++)    //扩展 a[],b[] ,存入f[],g[],注意取模
        {
            LL aa=i<=n?a[i]:0,bb=i<=m? b[i]:0;
            aa%=mod,bb%=mod;
            f[i]=cp(db(aa>>15),db(aa&32767));
            g[i]=cp(db(bb>>15),db(bb&32767));
        }
        dft(f,N),dft(g,N);
        for(int i=0;i<N;i++)    // 频域求积 // ?????????????????
        {
            int j=i? N-i:0;
            t[i]=((f[i]+!f[j])*(!g[j]-g[i])+(!f[j]-f[i])*(g[i]+!g[j]))*cp(0,0.25);
        }
        dft(t,N,-1);
        for(int i=0;i<=n+m;i++)    upmo(c[i],(LL(t[i].a+0.5))%mod<<15);
        for(int i=0;i<N;i++)    // 频域求积 // ?????????????????
        {
            int j=i? N-i:0;
            t[i]=(!f[j]-f[i])*(!g[j]-g[i])*cp(-0.25,0)+cp(0,0.25)*(f[i]+!f[j])*(g[i]+!g[j]);
        }
        dft(t,N,-1);
        for(int i=0;i<=n+m;i++)    upmo(c[i],LL(t[i].a+0.5)+(LL(t[i].b+0.5)%mod<<30));
    }
}

//==============预处理阶乘及阶乘逆元============== 

LL qpow(LL x,LL n)        //求x^n%mod
{
    LL ret=1;
    for(; n; n>>=1)
    {
        if(n&1) ret=ret*x%mod;
        x=x*x%mod;
    }
    return ret;
}
LL inv(LL x)
{
    return qpow(x,mod-2);
}
const LL M=1e5+5;
LL fac[M+5];            //阶乘
LL inv_of_fac[M+5];        //阶乘的逆元
void init_fac()
{
    fac[0]=1;
    for(int i=1; i<=M; i++)
        fac[i]=fac[i-1]*i%mod;
    inv_of_fac[M]=qpow(fac[M],mod-2);
    for(int i=M-1; i>=0; i--)
        inv_of_fac[i]=inv_of_fac[i+1]*(i+1)%mod;
}
//================================================

//===================phi(x)打表===================

const int maxn=100007;
int phi[maxn+5];
void init_phi()
{
    memset(phi,0,sizeof(phi));  //初始化为0
    phi[1]=1;
    for(int i=2; i<=maxn; i++)
    {
        if(!phi[i])     //当i是质数时
            for(int j=i; j<=maxn; j+=i)      //筛选所有因子为i的数
            {
                if(!phi[j]) phi[j]=j;       //若未赋值过,先初始化
                phi[j]=phi[j]/i*(i-1);      //i是质因数(1-1/i)=(i-1)/i,先除再乘是为了防止越界。
            }
    }
}
//================================================

LL a[1<<18|1],b[1<<18|1],c[1<<18|1];

int main()
{
    init_phi();FFT_MO::fft_init();

    //=============debug==============

//    int n,m;
//    mod=1e9+7;
//    while(cin>>n>>m)
//    {
//        for(int i=0;i<=n;i++)    cin>>a[i];
//        for(int i=0;i<=m;i++)    cin>>b[i];
//        FFT_MO::convo(a,n,b,m,c);
//        for(int i=0;i<=n+m;i++)
//            cout<<c[i]<<‘ ‘;
//        puts("");
//    }

    //================================
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&mod);
        init_fac();
        LL ans=0;
        for(int d=1;d<=n;d++)
        {
            int N=n/d;
            for(int i=0;i<=N;i++) a[i]=b[i]=inv_of_fac[i*d];
            FFT_MO::convo(a,N,b,N,c);
            LL temp=0;
            for(int i=1;i<=N;i++) temp=(temp+c[i]*inv_of_fac[n-i*d])%mod;
            ans=(ans+temp*phi[d])%mod;
        }
        ans=ans*fac[n]%mod*qpow(3,n)%mod;
        printf("%lld\n",ans);
    }
}
时间: 2024-12-12 13:46:03

hdu 6088 Rikka with Rock-paper-scissors (2017 多校第五场 1004) 【组合数学 + 数论 + 模意义下的FFT】的相关文章

hdu多校第五场1004 (hdu6627) equation 1 计算几何

题意: 给你一个C,再给你n组a,b,让你求x取什么值的时候,$ \sum_{i=1}^n |a_i*x+b_i| =C $,要求求出解的个数,并用最简分数从小到大表示,如果有无穷多解,输出-1. 题解: 其实这些方程就是在平面上的一组曲线,都是V形的,最低点都在x轴上,求出所有的零点,以这个零点从左到右排序. 容易看出,这些函数之和也是一条曲线y=f(i),这条曲线最多有n个转折点,那么就在这n个转折点分出的n+1个区间内,和n个点上,用比例公式找和y=C的交点即可.无穷多解的情况是存在一条与

2014多校第十场1004 || HDU 4974 A simple water problem

题目链接 题意 : n支队伍,每场两个队伍表演,有可能两个队伍都得一分,也可能其中一个队伍一分,也可能都是0分,每个队伍将参加的场次得到的分数加起来,给你每个队伍最终得分,让你计算至少表演了几场. 思路 : ans = max(maxx,(sum+1)/2) :其实想想就可以,如果所有得分中最大值没有和的一半大,那就是队伍中一半一半对打,否则的话最大的那个就都包了. 1 #include <cstdio> 2 #include <cstring> 3 #include <st

2014多校第五场1010 || HDU 4920 Matrix multiplication(矩阵乘法优化)

题目链接 题意 : 给你两个n*n的矩阵,然后两个相乘得出结果是多少. 思路 :一开始因为知道会超时所以没敢用最普通的方法做,所以一直在想要怎么处理,没想到鹏哥告诉我们后台数据是随机跑的,所以极端数据是不可能会有的,而我们一开始一直在想极端数据能接受的方法......后来看了鹏哥的做法,就是把是0的地方都跳过就可以了,用矩阵保存前一个非0数的位置是多少.二师兄给我看了一个代码,人家根本没用别的优化,直接将最里层k的循环提到了最外层,然后就AC了,对此我表示无语. 1 #include <cstd

2014 HDU多校弟五场J题 【矩阵乘积】

题意很简单,就是两个大矩阵相乘,然后求乘积. 用 Strassen算法 的话,当N的规模达到100左右就会StackOverFlow了 况且输入的数据范围可达到800,如果变量还不用全局变量的话连内存开辟都开不出来 1 #pragma comment(linker, "/STACK:16777216") 2 #include <iostream> 3 #include <stdio.h> 4 #define ll long long 5 using namesp

2014多校第五场1001 || HDU 4911 Inversion (归并求逆序数)

题目链接 题意 : 给你一个数列,可以随意交换两相邻元素,交换次数不超过k次,让你找出i < j 且ai > aj的(i,j)的对数最小是多少对. 思路 : 一开始想的很多,各种都想了,后来终于想出来这根本就是求逆序数嘛,可以用归并排序,也可以用树状数组,不过我们用树状数组做错了,也不知道为什么.求出逆序数来再减掉k次,就可以求出最终结果来了.求逆序数链接1,链接2 1 #include <stdio.h> 2 3 int left[250003], right[250003];

2014 HDU多校弟五场A题 【归并排序求逆序对】

这题是2Y,第一次WA贡献给了没有long long 的答案QAQ 题意不难理解,解题方法不难. 先用归并排序求出原串中逆序对的个数然后拿来减去k即可,如果答案小于0,则取0 学习了归并排序求逆序对的方法,可以拿来当模板 TVT 贴代码了: 1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <math.h> 5 #include <iostream&g

2017多校第7场 HDU 6128 Inverse of sum 推公式或者二次剩余

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6128 题意:给你n个数,问你有多少对i,j,满足i<j,并且1/(ai+aj)=1/ai+1/aj 在%p意义下. 解法:官方题解说是用二次剩余来解,但是我并不会这玩意了.在网上看到一位大佬没有二次剩余直接通过推公式做出了这题,真是神奇.http://www.cnblogs.com/bin-gege/p/7367337.html  将式子通分化简后可得(ai2+aj2+ai*aj)%p=0 .然后两

hdu 4865 Peter&amp;#39;s Hobby(2014 多校联合第一场 E)

Peter's Hobby Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 545    Accepted Submission(s): 237 Problem Description Recently, Peter likes to measure the humidity of leaves. He recorded a leaf

SDUT 3568 Rock Paper Scissors 状压统计

就是改成把一个字符串改成三进制状压,然后分成前5位,后5位统计, 然后直接统计 f[i][j][k]代表,后5局状压为k的,前5局比和j状态比输了5局的有多少个人 复杂度是O(T*30000*25*m)m比较小,也就最多几十吧,将将过 #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <cstdlib> #include &