【解题报告(部分)】HDU 多校七 | 03 + 04 + 05 + 11

  • 话说第一次做出 / 想出一些生成函数的内容,纪念一下…
  • HDU 多校七

1003 Fall with Trees

题意

  • HDU7046 Fall with Trees
    完美二叉树的定义:
    (1)是完全二叉树
    (2)对于所有层,上一层和下一层的纵坐标的差都相同
    (3)同一层,相邻兄弟的横坐标的差都相同
    (4)对于每一个节点,它的横坐标就是它所有儿子的横坐标的平均值
    请添加图片描述
  • 给定根节点和它的左儿子和右儿子的坐标,给定树的深度 k k k
    求这个完美二叉树的凸包的面积
  • 样例组数 T < 2 × 1 0 5 T <2\times 10^5 T<2×105
    2 ≤ k ≤ 1 0 4 2\le k\le 10^4 2k104

思路

  • 把凸包面积拆成一个三角形和一个个的梯形
    然后发现,第一个梯形相当于上底为 0 0 0 的梯形,差不多等价
    设根节点的两个儿子的横坐标差为 a a a
    然后可以推出下一个梯形,下下个梯形的底边宽度是多少
    应该是:
    2 k + 1 − 1 2 k \frac{2^{k+1}-1}{2^k} 2k2k+11
    然后梯形面积公式套一下,再求和公式套一下,发现是一个等比数列,直接求和即可

代码

  • 时间复杂度: O ( T ) O(T) O(T) 可以预处理出 p 2 [ i ] = ( 1 2 ) n p2[i]=(\frac{1}{2})^n p2[i]=(21)n
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 1e4+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

double p2[MAX];

int main()
{
    p2[0] = 1.0;
    for(int i = 1;i <= 10000;++i){
        p2[i] = p2[i-1] / 2.0;
    }
    int T;
    scanf("%d",&T);
    while(T--){
        int k;
        double xr,yr,x1,y1,x2,y2;
        scanf("%d",&k);
        scanf("%lf%lf%lf%lf%lf%lf",&xr,&yr,&x1,&y1,&x2,&y2);
        double h = yr - y1;
        double a = x2 - x1;
        k-=2;
        double ans = a * h / 2 * (4.0 * k - 2 + 3.0 * p2[k]);	// 公式自己推吧...
        printf("%.3f\n",ans);
    }
    return 0;
}

1004 Link with Balls

题意

  • HDU7047 Link with Balls
    2 n 2n 2n 个桶
    2 x − 1 2x-1 2x1 个桶你可以拿 k x kx kx 个球 ( k ≥ 0 k\ge 0 k0
    2 x 2x 2x 个桶你最多拿 x x x 个球
    问你拿 m m m 个球的方案数
  • 样例组数 T ≤ 1 0 5 T\le 10^5 T105
    1 ≤ n , m ≤ 1 0 6 1\le n,m\le 10^6 1n,m106

思路

  • 貌似直接就是生成函数裸题了…
    2 t − 1 2t-1 2t1 个桶 的生成函数可以写成:
    f ( 2 t − 1 ) = x 0 + x t + x 2 t + ⋯ + x k t + ⋯ = 1 1 − x t f(2t-1)=x^0 +x^{t}+x^{2t}+\cdots +x^{kt}+\cdots =\frac{1}{1-x^t} f(2t1)=x0+xt+x2t++xkt+=1xt1
    2 t 2t 2t 个桶 的生成函数可以写成:
    f ( 2 t ) = x 0 + x 1 + x 2 + ⋯ + x t = 1 − x t + 1 1 − x f(2t)=x^0+x^1+x^2+\cdots+x^t=\frac{1-x^{t+1}}{1-x} f(2t)=x0+x1+x2++xt=1x1xt+1
    最后所有桶拿完了,总的生成函数就是:
    ∏ f = f ( 1 ) ⋅ f ( 2 ) ⋯ f ( 2 n ) = [ 1 1 − x 1 ⋅ 1 1 − x 2 ⋯ 1 1 − x n ] × [ 1 − x 2 1 − x ⋅ 1 − x 3 1 − x ⋯ 1 − x n + 1 1 − x ] = 1 − x n + 1 ( 1 − x ) n + 1 \begin{aligned} \prod f&=f(1)\cdot f(2)\cdots f(2n)\\ &=\Big[ \frac{1}{1-x^1}\cdot\frac{1}{1-x^2}\cdots\frac{1}{1-x^n} \Big]\times\Big[ \frac{1-x^2}{1-x}\cdot \frac{1-x^3}{1-x}\cdots \frac{1-x^{n+1}}{1-x} \Big]\\ &=\frac{1-x^{n+1}}{(1-x)^{n+1}} \end{aligned} f=f(1)f(2)f(2n)=[1x111x211xn1]×[1x1x21x1x31x1xn+1]=(1x)n+11xn+1
    最后求拿 m m m 个球的方案数,也就是求 [ x m ] [x_m] [xm]
    注意到分母部分,我们有: 1 ( 1 − x ) n \frac{1}{(1-x)^n} (1x)n1 [ x m ] = C m + n − 1 n − 1 [x_m]=C_{m+n-1}^{n-1} [xm]=Cm+n1n1
    注意到分子部分,次数为 0 0 0 n + 1 n+1 n+1,两者是选其一的,所以我们对于分母部分,我们只能拿次数为 m m m 的与次数为 m − n − 1 m-n-1 mn1
    所以最终的答案为: C n + m n − C m − 1 n C_{n+m}^n-C_{m-1}^n Cn+mnCm1n

代码

  • 时间复杂度: O ( T + 2 e 6 ) O(T+2e6) O(T+2e6)
ll fac[MAX],ivfac[MAX];

void init(int n){
    fac[0] = 1;
    for(int i = 1;i <= n;++i){
        fac[i] = fac[i-1] * i % MOD;
    }
    ivfac[n] = inv(fac[n]);
    for(int i = n-1;~i;--i){
        ivfac[i] = ivfac[i+1] * (i + 1) % MOD;
    }
}
ll C(int n,int m){
    if(n < 0 || m > n)return 0;
    return fac[n] * ivfac[m] % MOD * ivfac[n - m] % MOD;
}


int main()
{
    init(2000000);
    int T = read();
    while(T--){
        int n,m;n = read();m = read();
        ll ans = (C(n+m,n) - C(m-1,n) + MOD) % MOD;
        Print(ans,'\n');
    }
    Write();
    return 0;
}

1005 Link with EQ

题意

  • HDU7048 Link with EQ
    有一个长条桌子,有 n n n 个凳子
    然后开始来人,坐进去。初始来人了,上面都没有人,就随便选一个位置来坐。
    接下来,来人了,就选择其中的位置,这个位置距离其他有人的最近距离需最大;若有多个位置,则随机选一个位置
    若这个最大距离为 1 1 1 ,就坐不进了,结束。
    问,期望坐进去的人数?
  • 样例组数 T ≤ 1 0 5 T\le 10^5 T105
    1 ≤ n ≤ 1 0 6 1\le n\le 10^6 1n106

思路

  • 容易想到,初始情况比较复杂;
    但是若选定一个位置之后,剩下的坐法其实是固定的
    即使对于某个人,他有多个可选的位置,
    但是这个意味着他选完之后,再进来的人可能仍会从这多个剩下的可选位置去选(因为他们目前是最优的)
    或者选完之后,最优位置变为其他的一个或多个位置,或者直接结束了
    所以除了初始的人选的位置,其他的人不管怎么坐(当然要坐的合法的位置),后面坐进去的总人数是固定的!
  • 那就好做了。我们设 d p [ x ] [ 3 ] dp[x][3] dp[x][3] 数组
    其中 x x x 表示中间一段空的位置的长度
    其中 d p [ x ] [ 0 ] dp[x][0] dp[x][0] 表示这段区间的左边有人坐了,中间这段区间能做多少个人(这是固定的)
    其中 d p [ x ] [ 1 ] dp[x][1] dp[x][1] 表示这段区间的右边有人坐了
    其中 d p [ x ] [ 2 ] dp[x][2] dp[x][2] 表示这段区间的左右边都有人坐了
  • 然后我们去坐位置,假设左边有人坐了,那肯定要坐最右边的位置(让最近距离最大)
    假设右边有人坐了,那也是坐到最左边
    假设两边都有人坐了,我们分奇偶讨论即可,然后写成记忆化搜索,不是很困难
  • 考虑,我们最后的答案是什么?就是枚举第一个人坐的位置,然后概率乘以期望
    但是貌似是 O ( T N ) O(TN) O(TN) 的呀?不要紧,我们看一下式子:
    A n s = 1 n ( d p [ 0 ] [ 1 ] + d p [ n − 1 ] [ 0 ] ) + 1 n ( d p [ 1 ] [ 1 ] + d p [ n − 2 ] [ 0 ] ) ⋮ + 1 n ( d p [ n − 1 ] [ 1 ] + d p [ 0 ] [ 0 ] ) \begin{aligned} Ans&=\frac{1}{n}(dp[0][1]+dp[n-1][0])\\ &+\frac{1}{n}(dp[1][1]+dp[n-2][0])\\ &\qquad\qquad\qquad \vdots\\ &+\frac{1}{n}(dp[n-1][1]+dp[0][0])\\ \end{aligned} Ans=n1(dp[0][1]+dp[n1][0])+n1(dp[1][1]+dp[n2][0])+n1(dp[n1][1]+dp[0][0])
  • 原来如此,我们记两个前缀和
    p r e [ x ] [ 0 ] = ∑ i = 0 n d p [ i ] [ 0 ] p r e [ x ] [ 1 ] = ∑ i = 0 n d p [ i ] [ 1 ] pre[x][0]=\sum_{i=0}^n dp[i][0]\\ pre[x][1]=\sum_{i=0}^n dp[i][1]\\ pre[x][0]=i=0ndp[i][0]pre[x][1]=i=0ndp[i][1]
    那么答案就是:
    A n s = 1 n ( p r e [ n − 1 ] [ 0 ] + p r e [ n − 1 ] [ 1 ] ) Ans=\frac{1}{n}(pre[n-1][0]+pre[n-1][1]) Ans=n1(pre[n1][0]+pre[n1][1])

代码

  • 时间复杂度: O ( T + 1 e 6 ) O(T+1e6) O(T+1e6)
/**
0  x[]
1   []x
2  x[]x
*/


int dp[MAX][3];
ll pre[MAX][2];
int work(int n,int st){
    if(~dp[n][st])return dp[n][st];
    int cnt = 0;

    // Error State
    if(n == 0){return dp[n][st] = 0;}

    if(st == 0){
        if(n == 1){return dp[n][st] = 0;}
        cnt = 1 + work(n-1,2);
    }else if(st == 1){
        if(n == 1){return dp[n][st] = 0;}
        cnt = 1 + work(n-1,2);
    }else{
        if(n & 1){
            if(n == 1)cnt = 0;
            else cnt = 1 + 2 * work(n/2,2);
        }else{
            if(n == 2)cnt = 0;
            else cnt = 1 + work(n/2,2) + work(n/2-1,2);
        }
    }
    return dp[n][st] = cnt;
}

int main()
{
    memset(dp,-1,sizeof(dp));
    for(int i = 1;i <= 1000000;++i){
        work(i,0);work(i,1);
        pre[i][0] = (pre[i-1][0] + dp[i][0]) % MOD;
        pre[i][1] = (pre[i-1][1] + dp[i][1]) % MOD;
    }
    int T = read();
    while(T--){
        int n = read();
        ll prob = inv(n);
        ll ans = (pre[n-1][1] + pre[n-1][0]) % MOD * prob % MOD;
        ans = (ans + 1) % MOD;
        Print(ans,'\n');
    }
    Write();
    return 0;
}

1011 Yiwen with Formula

题意

  • HDU7054 Yiwen with Formula
    给定一个长度为 n n n 的序列 A [ n ] A[n] A[n]
    求出它的所有子序列的和的乘积
    就是:
    ∏ b 是 a 的 子 序 列 ( ∑ x 在 b 中 x ) \prod_{b是a的子序列}\Big(\sum_{x在b中}x\Big) ba(xbx)
  • 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1n105
    0 ≤ A [ i ] ≤ 1 0 5 0\le A[i]\le 10^5 0A[i]105
    满足 ∑ A [ i ] ≤ 1 0 5 \sum A[i]\le 10^5 A[i]105

思路

  • 首先,若枚举子序列,复杂度为 O ( 2 n × n ) O(2^n\times n) O(2n×n),肯定不行
    然后,我们想到背包,或者 d p dp dp ,记 d p [ x ] dp[x] dp[x] 表示子序列的和为 x x x 的子序列的个数
    最后答案就是:
    A n s = ∏ x = 0 ∑ A x d p [ x ] Ans=\prod_{x=0}^{\sum A} x^{dp[x]} Ans=x=0Axdp[x]
    背包转移,复杂度是 O ( n 2 ) O(n^2) O(n2) 的,貌似也不大行…
  • 但是,这个是一个 01 01 01 背包,很套路的,我们 01 01 01 背包都有第二类解法!(没多惊讶,就是自己突然就发现了!但是发现其实之前是讲过的!)
    对于物品 i i i ,若不选,我们受益为 0 0 0 ,方案数为 1 1 1
    若选,我们受益为 A [ i ] A[i] A[i] ,方案数为 1 1 1
    也就是对于物品 i i i ,我们的生成函数 f ( i ) = ( 1 + x A i ) f(i)=(1+x^{A_i}) f(i)=(1+xAi)
    那么这个背包的总的生成函数当然张这个样子:
    ∏ f = ( 1 + x A 1 ) ( 1 + x A 2 ) ⋯ ( 1 + x A n ) \prod f=(1+x^{A_1})(1+x^{A_2})\cdots (1+x^{A_n}) f=(1+xA1)(1+xA2)(1+xAn)
    然后我们的答案就变成了:
    A n s = ∏ x = 0 ∑ A x ( x 项 的 系 数 ) Ans=\prod_{x=0}^{\sum A} x^{(x项的系数)} Ans=x=0Ax(x)
  • 但是问题又来了!这里有 n n n 个多项式,若我们直接用 F F T FFT FFT 去做,由于我们的最高项数为 ∑ A \sum A A,所以我们的复杂度就变成了 O ( n 2 log ⁡ n ) \color{red}{O(n^2\log n)} O(n2logn) ,这不是比暴力都慢?
    因为我们每次都拿最大的多项式和最小的多项式去算,当然慢…
    但是我们可以使用分治的工具!我们每次合并最近的那两个多项式,类似 c d q cdq cdq 分治那样,就显然更优了
  • 还有一个问题…
    因为我们直接使用 F F T FFT FFT,精度低的可怕;使用 N T T NTT NTT,要求模数为 998244353 998244353 998244353,这里的模数要求也正好是 998244353 998244353 998244353,所以我们直接套 N T T NTT NTT ? 错 了 ! ! \color{red}{错了!!}
    因为我们计算的系数其实最后是当做指数 来的,欧拉降幂的要求,就是 指 数 需 要 取 模 φ ( P ) = P − 1 \color{green}{指数需要取模 \varphi(P)=P-1} φ(P)=P1
    所以我们只能使用 任意模数 M T T MTT MTT ,貌似有两种做法,一种是把数字拆成 x = a M + b x=aM+b x=aM+b 然后去做几遍 F F T FFT FFT 保证精度;或者取多个质数模数,然后使用 C R T CRT CRT 合并答案,应该都是可以的
  • 还有一个小优化,如果 A i = 0 A_i=0 Ai=0 了,答案就直接是 0 0 0 了,也是容易得到的

代码

  • 时间复杂度: O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 2e5+50;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

namespace MTT
{
    typedef long long ll;
    typedef long double ld;
    const int MAXP=1<<20;
//    const int MOD=998244353;
    const int PHI=MOD-1;
    const int M=32768;
    const ld pi=acos(-1.0);
    struct cp{
        ld x,y;
        cp(){x=y=0;}
        cp(ld _x,ld _y){x=_x,y=_y;}
        friend cp operator +(cp l,cp r){return cp(l.x+r.x,l.y+r.y);}
        friend cp operator -(cp l,cp r){return cp(l.x-r.x,l.y-r.y);}
        friend cp operator *(cp l,cp r){return cp(l.x*r.x-l.y*r.y,l.x*r.y+r.x*l.y);}
    }a1[MAXP+5],a2[MAXP+5],b1[MAXP+5],b2[MAXP+5],c1[MAXP+5],c2[MAXP+5],c3[MAXP+5];
    int rev[MAXP+5];
    void FFT(cp *a,int len,int typ){
        int lg=31-__builtin_clz(len);
        for(int i=0;i<len;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
        for(int i=0;i<len;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
        for(int i=2;i<=len;i<<=1){
            cp W=cp(cos(2*pi/i),typ*sin(2*pi/i));
            for(int j=0;j<len;j+=i){
                cp w=cp(1,0);
                for(int k=0;k<(i>>1);k++,w=w*W){
                    cp X=a[j+k],Y=w*a[(i>>1)+j+k];
                    a[j+k]=X+Y;a[(i>>1)+j+k]=X-Y;
                }
            }
        }
        if(typ==-1)for(int i=0;i<len;i++)a[i].x=ll(a[i].x/len+0.5);
    }
    vector<int> conv(vector<int> a,vector<int> b){
        vector<int> res;int LEN=1;while(LEN<a.size()+b.size())LEN<<=1;
        for(int i=0;i<LEN;i++)a1[i]=a2[i]=b1[i]=b2[i]=c1[i]=c2[i]=c3[i]=cp(0,0);
        for(int i=0;i<a.size();i++)a1[i].x=a[i]/M,a2[i].x=a[i]%M;
        for(int i=0;i<b.size();i++)b1[i].x=b[i]/M,b2[i].x=b[i]%M;
        FFT(a1,LEN,1);FFT(a2,LEN,1);FFT(b1,LEN,1);FFT(b2,LEN,1);
        for(int i=0;i<LEN;i++)c1[i]=a1[i]*b1[i],c2[i]=a1[i]*b2[i]+a2[i]*b1[i],c3[i]=a2[i]*b2[i];
        FFT(c1,LEN,-1);FFT(c2,LEN,-1);FFT(c3,LEN,-1);
        for(int i=0;i+1<a.size()+b.size();i++)
            res.push_back((1LL*((ll)(c1[i].x)%PHI)*M%PHI*M%PHI+1LL*((ll)(c2[i].x)%PHI)*M%PHI+(ll)(c3[i].x)%PHI)%PHI);
        return res;
    }
};
using namespace MTT;


int aa[MAX];
vector<int>cdq(int l,int r){
    if(l == r){
        vector<int>res(aa[l]+1,0);
        res[0]++;
        res[aa[l]]++;
        return res;
    }
    int mid = l + r >> 1;
    return conv(cdq(l,mid),cdq(mid+1,r));
}



int main() {
    int T = read();
    while(T--){
        int n = read();
        int sum = 0;
        for(int i = 1;i <= n;++i)aa[i] = read(),sum += aa[i];
        vector<int>V = cdq(1,n);
        ll ans = qpow(0,V[0]-1);
        for(int i = 1;i <= sum;++i)ans = ans * qpow(i,V[i]) % MOD;
        Print(ans,'\n');
    }
    Write();
    return 0;
}
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值