先看题意,只对后2位操作,再看数据范围,这大概是一个O(n)或O(nlogn)复杂度的题目,
但由于题目不允许排序,这样会破坏后2位的位置,所以大概是一个O(n)的做法,题目要求计数,脑海里想到了是否是某数学题目,但题目要求不太可能是,先从小范围考虑。
若n=1,此时当且仅当a[1]=i的方案数为1,其他为0,当a[1]不是一位数时,方案数全0.
若n=2,此时只可能是a[n]+a[n-1]和a[n]*a[n-1]各自的数字取个位数后的2个数字有一个方案数
若n=i,我们现在如果要把第i项改成数字j(j是一位数),而且已经知道第i+1项数字为k的方案数,怎么递推?
例如,现在的a[i]是3,要改成数字6,只有2种可能,把第i+1项的3做第一种操作,把第i+1项的2做第二种操作,所以我们就看第i+1改成数字x的方案数,那第i+1项又是如此,很显然是一个计数dp。
代码如下,时间复杂度为O(n),常数为10倍,妥妥够了。
注意:这道题,计数dp的数组不要开成int,哪怕取模数字是在int范围,但还是会爆,试了n多次才发现的教训。
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2e5+7,M=1e9+7;
typedef long long ll;
int a[N];
ll f[N][10];//把第i位变成j的方案数
int c1(int x,int y){
return (x+y)%10;
}
int c2(int x,int y){
return (x*y)%10;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
if(n==1){
for(int i=0;i<10;i++){
if(a[1]!=i)cout<<0;
else cout<<1;
cout<<" ";
}
return 0;
}
else{
f[n-1][c1(a[n]%10,a[n-1]%10)]+=1;
f[n-1][c2(a[n]%10,a[n-1]%10)]+=1;
for(int i=n-2;i>=1;i--){
for(int j=0;j<10;j++){
//什么情况下可以使得第i项变成数字j?
//先考虑第一种操作
int d=a[i]%10;
f[i][j]=(f[i][j]+f[i+1][(j-d+10)%10])%M;
//再考虑第二种操作
for(int k=0;k<10;k++){ //为了变成i,需要乘哪个数
if((d*k)%10==j){
f[i][j]=(f[i][j]+f[i+1][k])%M;
}
}
}
}
}
for(int i=0;i<10;i++)cout<<f[1][i]<<" ";
}



京公网安备 11010502036488号