【学习笔记】CF643F Bears and Juice

完了,感觉一上午做不出一道题来了。

大脑超载了。无论如何都只想到了从 i = 1 i=1 i=1入手这一种思路。手玩了一下发现答案就是 n + 1 n+1 n+1

然后考虑 i = 2 i=2 i=2。(真的只能想到最笨的思路啊)发现就按 n + 1 n+1 n+1的大小分组即可,但是注意少了一个所以应该是按 n n n大小为分组,那么答案就是 n 2 + 1 n^2+1 n2+1。方便起见先不考虑 p p p的限制。

我们尝试来推一下通式。经验告诉我们这种题目都是有通式的,只是比较隐晦而已。这个通式算出来是 n i + 1 n^i+1 ni+1,看着就不是很对。

回过头去看样例,原来 p p p有限制,那么自然而然的想到设计一个两维的 D P DP DP状态,设 d p i , j dp_{i,j} dpi,j表示有 i i i天, j j j张床时最多的酒桶数目。唯一需要注意的是此时熊的数目是可以确定的,但是这个递推的式子需要仔细斟酌一下,仔细观察一下样例 3 3 3会发现问题没有那么简单,换句话说一轮少掉的熊的数目可能不只 1 1 1个,因此要换一个角度来转移。

我们之前接触过全集的剖分这个概念,用来解释这个转移是非常合适的。考虑我们得到的信息是哪些熊少掉了,一共有 U = 2 n − p + j U=2^{n-p+j} U=2np+j种情况,对于每种情况能区分出来的桶的最大数量求和,就得到了正确的转移式: d p i , j = ∑ k ≤ j ( n − p + j k ) d p i − 1 , j − k dp_{i,j}=\sum_{k\le j}\binom{n-p+j}{k}dp_{i-1,j-k} dpi,j=kj(knp+j)dpi1,jk。注意这取的是上界,可以证明我们能构造方案将这个上界取到(?)。

注意这里要处理一下边界,不妨令 p = min ⁡ ( p , n − 1 ) p=\min(p,n-1) p=min(p,n1),这样就不用考虑熊全部死光的情况了。直接拿这个式子去转移,复杂度 O ( p 2 q ) O(p^2q) O(p2q),难泵。

但是注意到这个 d p dp dp状态是二维的,很难不让人想到组合数。再瞪眼一看这个转移系数,这不就是 E G F EGF EGF吗?简单来说,要算 d p i , p dp_{i,p} dpi,p的值,将 e x i e^{xi} exi泰勒展开,有 e x i = 1 + x i + x 2 i 2 2 ! + . . . + x n i n n ! e^{xi}=1+xi+\frac{x^2i^2}{2!}+...+\frac{x^ni^n}{n!} exi=1+xi+2!x2i2+...+n!xnin,把系数拼凑一下得到 d p i , p = ∑ j ≥ n − p n ! j ! [ x n − j ] e x i = ∑ j ≤ p ( n j ) i j dp_{i,p}=\sum_{j\ge n-p}\frac{n!}{j!}[x^{n-j}]e^{xi}=\sum_{j\le p}\binom{n}{j}i^{j} dpi,p=jnpj!n![xnj]exi=jp(jn)ij。这里唯一需要提醒的就是求和的范围,非常容易被忽略。

上式可以直接 O ( p q ) O(pq) O(pq)计算。唯一恼人的是这个组合数,因为 2 32 2^{32} 232不是质数,不能直接算。但是把 2 2 2的因子全部约掉然后用欧拉定理就好了,代码并不困难。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define db double
#define inf 0x3f3f3f3f3f3f3f3f
#define uint unsigned int
using namespace std;
const int mod=1e9+7;
int n,p,q,nums[155];
uint res,tran[155];
uint fpow(uint x,ll y=(1ll<<31)-1){
    uint z(1);
    for(;y;y>>=1){
        if(y&1)z=z*x;
        x=x*x;
    }
    return z;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>p>>q,p=min(p,n-1);
    tran[0]=1;
    for(int i=1;i<=p;i++){
        int x=n-i+1,y=i;nums[i]=nums[i-1];
        while(x%2==0)x/=2,nums[i]++;
        while(y%2==0)y/=2,nums[i]--;
        assert(nums[i]>=0);
        tran[i]=tran[i-1]*x*fpow(y);
    }
    for(int i=1;i<=p;i++){
        tran[i]=tran[i]*(1ll<<nums[i]);
    }
    for(int i=1;i<=q;i++){
        uint tmp=0,mul=1;
        for(int j=0;j<=p;j++){
            tmp+=mul*tran[j];
            mul=mul*i;
        }
        res^=(tmp*i);
    }
    cout<<res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值