Codeforces Round #788 (Div. 2)F. Jee, You See?

这篇博客解析了一道Codeforces竞赛题目,涉及数论和动态规划。核心内容包括如何通过状态转移和优化来解决a1...an的和在[l,r]范围内的组合问题,同时保证a1⨁...⨁an=z。作者介绍了关键的数学原理和dp状态转移方程,展示了如何利用递推式计算符合条件的方案数。
摘要由CSDN通过智能技术生成

Jee, You See?

https://codeforces.com/contest/1670/problem/F

理论

l < = a 1 + a 2 + . . . + a n < = r l<=a_1+a_2+...+a_n<=r l<=a1+a2+...+an<=r

a 1 ⨁ a 2 ⨁ . . . ⨁ a n = z a_1\bigoplus a_2\bigoplus...\bigoplus a_n=z a1a2...an=z

sum=a1+a2+…+an

那么只需要把sum<=r 的情况-sum<=l-1的情况就是答案数

那么只用计算sum<=x时a1a2…^an=z的方案数

z所在的位数为1时,一定有奇数个数的数当前位为1,反之则有偶数个

dp[i] [j]为到第i位时,剩下多少个1可以添加到当前位

转移时,会发现j会变的非常大,要考虑简化状态,找找有没有相同的或者对结果没有影响的状态
k 位 前 的 数 之 和 最 大 为 n ∗ ( 2 0 + 2 1 + . . . + 2 k − 1 ) = n ∗ ( 2 k − 1 ) < n ∗ ( 2 k ) , 所 以 对 于 当 前 位 , 把 剩 下 可 以 为 1 的 数 量 > = n 全 做 n 处 理 , 对 后 面 的 结 果 无 影 响 。 k位前的数之和最大为n*(2^0+2^1+...+2^k-1)=n*(2^k-1)<n*(2^k),所以对于当前位,把剩下可以为1的数量>=n全做n处理,对后面的结果无影响。 kn(20+21+...+2k1)=n(2k1)<n(2k)1>=nn

状 态 转 移 时 , 第 k + 1 位 剩 下 的 可 以 为 1 的 数 量 为 i 时 , 那 么 k 位 为 1 的 数 量 最 多 为 2 ∗ i + ( ( x < < k ) & 1 ) 状态转移时,第k+1位剩下的可以为1的数量为i时,那么k位为1的数量最多为2*i+( ( x < < k ) \& 1 ) k+11ik12i+((x<<k)&1)

得 到 递 推 式 : d p [ k ] [ m i n ( 2 ∗ i + ( ( x > > k ) & 1 ) − j , n ) ] + = d p [ k + 1 ] [ i ] ∗ C [ n ] [ j ] / / j 为 枚 举 的 当 前 位 放 置 的 数 量 得到递推式:dp[k] [min(2*i+((x>>k)\&1)-j,n)]+=dp[k+1] [i] *C[n] [j]//j为枚举的当前位放置的数量 :dp[k][min(2i+((x>>k)&1)j,n)]+=dp[k+1][i]C[n][j]//j

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define F(i, a, b) for(int i = a; i <= (b); ++i)
#define F2(i, a, b) for(int i = a; i < (b); ++i)
#define dF(i, a, b) for(int i = a; i >= (b); --i)
#define pb push_back
#define pf push_front
#define fi first
#define sc second
#define inf 0x3f3f3f3f
#define mod 998244353
#define infll 0x3f3f3f3f3f3f3f3f
#define mkp make_pair
const int N=1e3+7;
const int P=1e9+7;
int n;
ll C[N][N];
ll l,r,z;
void init(){
    C[0][0]=1;
    F(i,1,n)
        F(j,0,i)
        {
            if(!j) C[i][j]=1;
            else C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
        }
}
ll cal(ll x){
    ll dp[64][N]={0};
    dp[60][0]=1;
    for(int k=59;k>=0;k--){
         for(int i=0;i<=n;i++)//枚举前一位还能放置多少个1,大于n的都当作n,因为大于n对后续的影响都和n一样,都能让后面位全部填满
         {
            if(!dp[k+1][i]) continue;
            int now=2*i+((x>>k)&1);//当前位最多能放置的数量
            for(int j=(z>>k)&1;j<=min(n,now);j+=2){
                dp[k][min(now-j,n)]+=dp[k+1][i]*C[n][j]%P;
                dp[k][min(now-j,n)]%=P;
            }
        }
    }
    ll ans=0;
    F(i,0,n) ans+=dp[0][i];
    return ans%P;
}
void solve(){
    scanf("%d%lld%lld%lld",&n,&l,&r,&z);
    init();
    printf("%lld\n",(cal(r)-cal(l-1)+P)%P);
} 
int main(){
    //freopen("C:\\Users\\86155\\Desktop\\1.in","r",stdin);
    //freopen("C:\\Users\\86155\\Desktop\\1.out","w",stdout);
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值