Codeforces 691E题解 DP+矩阵快速幂

12 篇文章 0 订阅
10 篇文章 0 订阅

题面

传送门:http://codeforces.com/problemset/problem/691/E
E. Xor-sequences
time limit per test3 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
You are given n integers a1,  a2,  …,  an.

A sequence of integers x1,  x2,  …,  xk is called a “xor-sequence” if for every 1  ≤  i  ≤  k - 1 the number of ones in the binary representation of the number xi xi  +  1’s is a multiple of 3 and for all 1 ≤ i ≤ k. The symbol is used for the binary exclusive or operation.

How many “xor-sequences” of length k exist? Output the answer modulo 109 + 7.

Note if a = [1, 1] and k = 1 then the answer is 2, because you should consider the ones from a as different.

Input
The first line contains two integers n and k (1 ≤ n ≤ 100, 1 ≤ k ≤ 1018) — the number of given integers and the length of the “xor-sequences”.

The second line contains n integers ai (0 ≤ ai ≤ 1018).

Output
Print the only integer c — the number of “xor-sequences” of length k modulo 109 + 7.

Examples
inputCopy
5 2
15 1 2 4 8
outputCopy
13
inputCopy
5 1
15 1 2 4 8
outputCopy
5
题目大意:给定长度为n的序列,从序列中选择k个数(可以重复选择),使得得到的排列满足xi与xi+1异或的二进制中1的个数是3的倍数。问长度为k的满足条件的序列有多少种?

分析:

1.DP方程的推导

dp[i][j] d p [ i ] [ j ] 表示前i位以j结尾的方案数

dp[i][j]=ix=1dp[i1][x] d p [ i ] [ j ] = ∑ x = 1 i d p [ i − 1 ] [ x ]
(bitcount(a[x] ( b i t c o u n t ( a [ x ] xor x o r a[i])mod a [ i ] ) m o d 31) 3 ≡ 1 )
bitcount(x)表示x的二进制中1的个数

2.矩阵快速幂的优化

我们发现,状态转移方程的形式类似矩阵乘法
因为可以这样变形: dp[i][j]=ix=1dp[i1][x]×(bitcount(a[x] d p [ i ] [ j ] = ∑ x = 1 i d p [ i − 1 ] [ x ] × ( b i t c o u n t ( a [ x ] xor x o r a[i])mod a [ i ] ) m o d 3==1) 3 == 1 )

所以状态转移方程可以写成这样
dp[i][1]dp[i][2]dp[i][n]=1010111×dp[i1][1]dp[i1][2]dp[i1][n] [ d p [ i ] [ 1 ] d p [ i ] [ 2 ] ⋮ d p [ i ] [ n ] ] = [ 1 0 0 … 1 1 1 ⋱ … 1 ] × [ d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 2 ] ⋮ d p [ i − 1 ] [ n ] ]

右边那个很多1和0 的矩阵是 n×n n × n 的状态转移矩阵,第i行第j列为1代表 bitcount(a[x] b i t c o u n t ( a [ x ] xor x o r a[i])mod a [ i ] ) m o d 31 3 ≡ 1 ,否则为0
显然对角线全部为1(一个数异或它本身为0)
在DP之前我们可以预处理这个矩阵

由于最终答案为 nj=1dp[k][j] ∑ j = 1 n d p [ k ] [ j ] ,
状态转移方程可以改写为
dp[k][1]dp[k][2]dp[k][n]=1010111k1×dp[1][1]dp[1][2]dp[1][n] [ d p [ k ] [ 1 ] d p [ k ] [ 2 ] ⋮ d p [ k ] [ n ] ] = [ 1 0 0 … 1 1 1 ⋱ … 1 ] k − 1 × [ d p [ 1 ] [ 1 ] d p [ 1 ] [ 2 ] ⋮ d p [ 1 ] [ n ] ]

首先很显然每个数构成一个满足条件的序列,所以dp[1][j]=1
所以没有必要存储dp数组,直接计算出矩阵k-1次方,再将矩阵内所有的值加起来即可

时间复杂度分析:
预处理时间复杂度 O(n2) O ( n 2 )
矩阵乘法时间复杂度 O(n3) O ( n 3 )
快速幂时间复杂度 O(log2k) O ( l o g 2 k )
总时间复杂度 O(n3log2k) O ( n 3 l o g 2 k )

代码:

//CF 691E
#include<iostream>
#include<cstdio>
#include<cstring> 
#define SIZE 105
#define maxn 105
using namespace std;
const long long mod=1000000007;
int n;
long long k;
long long num[maxn];
struct matrix {//矩阵
    int n;//长
    int m;//宽
    long long a[SIZE][SIZE];
    matrix() {//构造函数
        n=2;
        m=2;
        memset(a,0,sizeof(a));
    }
    matrix(int x,int y) {
        n=x;
        m=y;
        memset(a,0,sizeof(a));
    }
    void print() {
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=m; j++) {
                printf("%d ",a[i][j]);
            }
            printf("\n");
        }
    }
    void setv(int x) {//初始化
        if(x==0) {
            memset(a,0,sizeof(a));
        }
        if(x==1) {
            memset(a,0,sizeof(a));
            for(int i=1; i<=n; i++) a[i][i]=1;
        }
    }
    friend matrix operator *(matrix x,matrix y) {//矩阵乘法
        matrix tmp=matrix(x.n,y.m);
        for(int i=1; i<=x.n; i++) {
            for(int j=1; j<=y.m; j++) {
                tmp.a[i][j]=0;
                for(int k=1; k<=y.n; k++) {
                    tmp.a[i][j]+=(x.a[i][k]*y.a[k][j])%mod;
                }
                tmp.a[i][j]%=mod;
            }
        }
        return tmp;
    }
};
matrix fast_pow(matrix x,long long k) {//矩阵快速幂
    matrix ans=matrix(n,n);
    ans.setv(1);//初始化为1
    while(k>0) {//类似整数快速幂
        if(k&1) {
            ans=ans*x;
        }
        k>>=1;
        x=x*x;
    }
    return ans;
}
long long count_1(long long x) {//算1的个数
    long long ans=0;
    while(x>0){
        if(x&1) ans++;
        x/=2;
    }
    return ans;
}
int main() {
    scanf("%d %I64d",&n,&k);
    for(int i=1; i<=n; i++) scanf("%I64d",&num[i]);
    matrix xor_mat=matrix(n,n);
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=n; j++) {
            if(count_1(num[i]^num[j])%3==0 )xor_mat.a[i][j]=1;
            else xor_mat.a[i][j]=0;
        }
    }
    xor_mat=fast_pow(xor_mat,k-1);
    long long ans=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            ans=ans+xor_mat.a[i][j];
        }
        ans%=mod;
    }
    printf("%I64d\n",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值