hihoCoder 1076 与链 (数位dp)

题意:

给定 n 和 k。计算有多少长度为 k 的数组 a1, a2, ..., ak,(0≤ai) 满足:a1 + a2 + ... + ak = n。
对于任意的 i = 0, ..., k - 1 有 ai AND ai + 1 = ai + 1。其中AND是与操作.

题解:

分析ai&ai+1=ai+1这个操作,我们会发现,ai+1必须比ai小或者等于ai才能满足,并且将其化成二进制会发现:

例如 ai+1=10 那么ai可以使:10 110 1110 11110....既然得到这个规律那么我们就可以根据数位dp按照位进行阶段划分,然后求解。dp[i][j]表示到第i为位置,和为j的个数。那么枚举这两状态外,还要枚举下一位1的个数,根据规律发现要满足条件时,某位有1都是往前靠的,比如说现在位i=2 那么这个为的1的排列只能是这样:

1 1 1 1、1 1 1 0、1 1 0 0、1 0 0 0、0 0 0 0.那么枚举1的个数其实就直接得到排列了,那么只要加上这位对应的和就ok了,不用考虑1的排列问题。

状态:dp[i+1][j+k]+=dp[i][j];

总的复杂度是O(n*k*17),但是想到这里就直接放弃不写了,因为这复杂度太高了,又想不到好的方法解决。。事实上复杂度没这么高!因为有一个地方可以优化!那么就是枚举1的个数时,我们令sum表示目前位置的总和,那么只要sum>n就break,这样时间优化非常多,至少10倍,于是就可以过了!

#include<iostream>
#include<math.h>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define B(x) (1<<(x))
typedef long long ll;
void cmax(int& a,int b){ if(b>a)a=b; }
void cmin(int& a,int b){ if(b<a)a=b; }
const int oo=0x3f3f3f3f;
const int MOD=1000000009;
const int maxn=10005;
ll dp[20][maxn];

int main(){
    //freopen("E:\\read.txt","r",stdin);
    int k,n,T;
    scanf("%d",&T);
    while(T--){
        scanf("%d %d",&k,&n);
        memset(dp,0,sizeof dp);
        dp[0][0]=1;
        for(int i=0;i<17;i++){
            for(int j=0;j<=n;j++){
                if(dp[i][j]==0)continue;
                for(int l=0;l<=k;l++){
                    ll sum=j+(l<<i);
                    if(sum>n)break;
                    dp[i+1][sum]=(dp[i+1][sum]+dp[i][j])%MOD;
                }
            }
        }
        cout<<dp[17][n]<<endl;
    }
	return 0;
}






你可能感兴趣的:(hihoCoder 1076 与链 (数位dp))