BZOJ3434: [Wc2014]时空穿梭

42 篇文章 0 订阅

我们考虑怎么选取c个点满足有直线同时经过他们
Ai 表示第i个点,用向量 bi=(x1,x2,x3...xn) 表示 Ai+1 Ai 的差
由于每一维的 vixi=pi+tvi 是不变的,对于相邻的两个点, Δt 是相同的,所以任一向量 bi=(x1,x2,x3...xn) xi 之间的比值是不变的,即 vi 之间的比值,所以对于任一向量 bi=(x1,x2,x3...xn) ,都可以将它写成 bi=(Δtv1,Δtv2...Δtvn) ,将 vi 之间的比值写成整数 ai 之间的比值,得 bi=(kc1,kc2,...kcn)

我们考虑枚举每一维的极差 a1,a2,...an ,设 d=gcd(a1,a2,...an) ,则 ai=cid ,则将 d 分给这c1 bi ,共有 Cc2d1 种分法,原理是插板法,即因为每一维严格递增,所以每个 bi 分到的不能为0,相当于 1d ,之间有 d1 个空位,在这些中选出 c2 个位置插隔板代表这 c1 个数之间的分界线(感觉没讲清楚,意会一下?)
因为所有坐标为正整数,第 i 维坐标不超过Mi,所以极差不超过 Mi1
mi=Mi1
所以有

ans=a1=1m1(M1a1)a2=1m2(M2a2)...an=1mn(Mnan)Cc2gcd(a1,a2...an)1

=dCc2d1ni=1midai=1(Miaid) ϵ((a1,a2,...an)=1)
反演一下

=dCc2d1ni=1midai=1(Miaid)D|(a1,a2...an)μ(D)

=dCc2d1Dμ(D)ni=1miDdai=1(MiaiDd)

枚举 D=Dd
=Dd|DCc2d1μ(Dd)ni=1miDai=1(MiaiD)

可以发现 f(D,c)=d|DCc2d1μ(Dd) 这个东西只与 D c有关, c 的范围不大,可以对所有的D c 预处理出f(D,c)(枚举 c 后,对每个d,枚举它的倍数 D ,根据调和级数,这样总的复杂度是O(cmlogm)

到这里,因为后面的柿子和D仍然相关,仍然需要枚举 m D,因为 m 很大,而且有多组数据,观察到使每个miD相同的 D 最多有O(nm)段,这个复杂度可以接受,所以考虑继续化,

观察 ni=1miDai=1(MiaiD) 这个东西,可以化成
ni=1MimiD(D+DmiD)miD2

=ni=1MimiDD(1+miD)miD2

接着把这 n 个多项式相乘O(n2)展开成形如 a0+a1D+a2D2+.....anDn 的多项式,对于 Di 的系数 ai ,只与 miD 有关

所以在维护了 g(D,c,j)=f(D,c)Dj 的前缀和后,可以将原式按 miD 分成 O(nm) 段,每段内暴力 O(n2) 展开多项式各项系数,乘上前面的柿子这一段的和

时间复杂度 O(nmc+cmlogm+Tn3m)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void down(int &x,const int y){if(x>y)x=y;}
const int maxn = 12;
const int maxc = 21;
const int maxm = 110000;
const int Mod = 10007;

int p[maxm],pri,miu[maxm];
bool v[maxm];
int C[maxm][maxc],f[maxc][maxm],pg[maxc][maxm][maxn];
//                 miu*C            pre G(i,c)×i^j
void pre()
{
    C[0][0]=1;
    for(int i=1;i<maxm;i++)
    {
        C[i][0]=1;
        for(int j=1;j<maxc;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%Mod;
    }

    miu[1]=1; ///
    for(int i=2;i<maxm;i++)
    {
        if(!v[i]) miu[i]=-1,p[++pri]=i;
        for(int j=1;j<=pri;j++)
        {
            int k=i*p[j];
            if(k>=maxm) break;
            v[k]=true;
            if(i%p[j]==0) break;
            miu[k]=-miu[i];
        }
    }

    for(int c=2;c<maxc;c++)
    {
        for(int i=1;i<maxm;i++)
        {
            for(int j=i,ji=1;j<maxm;j+=i,ji++)
                (f[c][j]+=C[i-1][c-2]*miu[ji]%Mod)%=Mod;
        }
    }

    for(int c=2;c<maxc;c++)
    {
        for(int i=1;i<maxm;i++)
        {
            for(int j=0,ij=1;j<maxn;j++,ij=ij*i%Mod)
            {
                pg[c][i][j]=(pg[c][i-1][j]+f[c][i]*ij%Mod)%Mod;
            }
        }
    }
}

int n,c;
int m[maxn];
int a[maxn],an;

int main()
{
    pre();

    int t; scanf("%d",&t);
    while(t--)
    {
        int mn=maxm;
        scanf("%d%d",&n,&c);
        for(int i=1;i<=n;i++) scanf("%d",&m[i]),m[i]--,down(mn,m[i]);

        int ret=0;
        for(int i=1;i<=mn;)
        {
            int r=mn;
            for(int j=1;j<=n;j++) down(r,m[j]/(m[j]/i));

            a[an=0]=1;
            for(int j=1;j<=n;j++)
            {
                int mi=(m[j]/i)%Mod;
                int x1=(m[j]+1)*mi%Mod,x2=-mi*(mi+1)/2%Mod;
                a[an+1]=a[an]*x2%Mod;
                for(int l=an;l>=0;l--) a[l]=(a[l]*x1%Mod+(l?a[l-1]:0)*x2%Mod)%Mod;
                an++;
            }
            for(int j=0;j<=an;j++) (ret+=a[j]*((pg[c][r][j]-pg[c][i-1][j])%Mod)%Mod)%=Mod;

            i=r+1;
        }
        if(ret<0) ret+=Mod;
        printf("%d\n",ret);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值