偶然发现了 Atcoder 的一场动态规划专项赛,视若珍宝。
这套题可以很好的练习思维。
欢迎大家去洛谷练习,因为有翻译,有题解,当然也推荐 Vjudge。
本文写作时间跨度大,码风可能不同,见谅。
A.Frog 1
f i f_i fi 表示跳到 i i i 需要的费用。
这道题显然有两种选项,直接按顺序 DP 即可。
f
i
=
min
(
f
i
−
1
+
∣
h
i
−
h
i
−
1
∣
,
f
i
−
2
+
∣
h
i
−
h
i
−
2
∣
)
f_i=\min(f_{i-1}+|h_i-h_{i-1}|,f_{i-2}+|h_i-h_{i-2}|)
fi=min(fi−1+∣hi−hi−1∣,fi−2+∣hi−hi−2∣)
#include<bits/stdc++.h>
using namespace std;
long long n,h[100005],f[100005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>h[i];
}
memset(f,127,sizeof(f));
f[1]=0;
for(int i=2;i<=n;i++)
{
f[i]=min(f[i-1]+abs(h[i]-h[i-1]),f[i-2]+abs(h[i]-h[i-2]));
}
cout<<f[n]<<endl;
return 0;
}
B.Frog 2
f i f_i fi 表示跳到 i i i 需要的费用。
这道题一次可以跳
k
k
k 格了,但是也是纸老虎,再加一层循环即可。
f
i
=
min
j
=
min
(
i
−
k
,
1
)
i
−
1
(
f
j
+
∣
h
i
−
h
j
∣
)
;
f_i=\min_{j=\min(i-k,1)}^{i-1}(f_{j}+|h_i-h_{j}|);
fi=j=min(i−k,1)mini−1(fj+∣hi−hj∣);
#include<bits/stdc++.h>
using namespace std;
long long n,k,h[100005],f[100005];
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>h[i];
}
memset(f,127,sizeof(f));
f[1]=0;
for(int i=2;i<=n;i++)
{
for(int j=i-1;j>=1&&j+k>=i;j--)
{
f[i]=min(f[i],f[j]+abs(h[i]-h[j]));
}
}
cout<<f[n]<<endl;
return 0;
}
C.Vacation
f i , j f_{i,j} fi,j 表示第 i i i 天做活动 j j j 的最大幸福值。
对于每个状态,显然有两种选项,直接按顺序 DP 即可。
f
i
,
j
=
max
(
f
i
−
1
,
(
j
+
1
)
m
o
d
3
,
f
i
−
1
,
(
j
+
2
)
m
o
d
3
)
+
a
i
,
j
f_{i,j}=\max(f_{i-1,(j+1)\bmod 3},f_{i-1,(j+2)\bmod3})+a_{i,j}
fi,j=max(fi−1,(j+1)mod3,fi−1,(j+2)mod3)+ai,j
那啥,公式里的一些东西和题目描述的不太一样,相信大家可以看懂。
#include<bits/stdc++.h>
using namespace std;
long long n,a[100005][3],f[100005][3];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i][0]>>a[i][1]>>a[i][2];
for(int i=1;i<=n;i++)
for(int j=0;j<=2;j++)
f[i][j]=max(f[i-1][(j+1)%3],f[i-1][(j+2)%3])+a[i][j];
cout<<max(f[n][0],max(f[n][1],f[n][2]))<<endl;
return 0;
}
D.Knapsack 1
一眼背包,还是01背包的板子。
f i , j f_{i,j} fi,j 为价值为 选前 i i i 个物品,重量为 j j j 时,价值最大是多少。
f i , j = max ( f i − 1 , j − w i + v i , f i − 1 , j ) f_{i,j}=\max(f_{i-1,j-w_i}+v_i,f_{i-1,j}) fi,j=max(fi−1,j−wi+vi,fi−1,j)
答案是从大到小找到的第一个 i i i 使得 f i < W f_i<W fi<W 。
显然可以压缩成一维,不过要倒着来,避免已经计算的部分影响当前计算。
#include<bits/stdc++.h>
using namespace std;
long long n,t,w,v,f[100005];
int main(){
cin>>t>>n;
for(int i=1;i<=t;i++)
{
cin>>w>>v;
for(int j=n;j>=w;j--)
f[j]=max(f[j],f[j-w]+v);
}
cout<<f[n];
}
E.Knapsack 2
一眼背包,还是01背包的板子,不过略有变化。
受数据影响,我们需要变形。
f i , j f_{i,j} fi,j 为价值为 选前 i i i 个物品,价值为 j j j 时,重量最小是多少。
f i , j = min ( f i − 1 , j − v i + w i , f i − 1 , j ) f_{i,j}=\min(f_{i-1,j-v_i}+w_i,f_{i-1,j}) fi,j=min(fi−1,j−vi+wi,fi−1,j)
答案是从大到小找到的第一个 i i i 使得 f i < W f_i<W fi<W 。
显然可以压缩成一维,不过要倒着来,避免已经计算的部分影响当前计算。
#include<bits/stdc++.h>
using namespace std;
long long n,t,w,v,f[100005];
int main(){
cin>>t>>n;
memset(f,127,sizeof(f));
f[0]=0;
for(int i=1;i<=t;i++)
{
cin>>w>>v;
for(int j=100000;j>=v;j--)
f[j]=min(f[j],f[j-v]+w);
}
for(int j=100000;j>=0;j--)
{
if(f[j]<=n)
{
cout<<j<<endl;
return 0;
}
}
}
F.LCS
这道题显然就是最长公共子序列的板题。
f i , j f_{i,j} fi,j 表示 字符串1 的前 i i i 个字符与 字符串2 的前 j j j 个字符的最长公共子序列的长度。
f i , j = { f i − 1 , j − 1 + 1 ( a i = b j ) max ( f i − 1 , j , f i , j − 1 ) ( a i ≠ b j ) f_{i,j}= \begin{cases} f_{i-1,j-1}+1(a_i=b_j)\\ \max(f_{i-1,j},f_{i,j-1})(a_i \not= b_j) \end{cases} fi,j={fi−1,j−1+1(ai=bj)max(fi−1,j,fi,j−1)(ai=bj)
不过需要记录路径,还是很简单的。
我们记录每个状态有哪个状态转移而来,然后倒推答案。
#include <bits/stdc++.h>
using namespace std;
long long f[3005][3005],s[3005][3005][2],n,m;
char a[3005],b[3005];
void dg(long long x,long long y)
{
if(x==0&&y==0)return;
dg(s[x][y][0],s[x][y][1]);
if(s[x][y][0]==x-1&&s[x][y][1]==y-1)
{
cout<<a[x];
}
}
int main() {
cin>>(a+1)>>(b+1);
n=strlen(a+1);
m=strlen(b+1);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i]==b[j])
{
f[i][j]=f[i-1][j-1]+1;
s[i][j][0]=i-1,s[i][j][1]=j-1;
}
else
{
if(f[i][j-1]>f[i-1][j])
{
f[i][j]=f[i][j-1];
s[i][j][0]=i,s[i][j][1]=j-1;
}
else
{
f[i][j]=f[i-1][j];
s[i][j][0]=i-1,s[i][j][1]=j;
}
}
}
}
dg(n,m);
}
G.Longest Path
f i f_i fi 表示从点 i i i 开始走,能走的最远距离。
f i = max ( 0 , max ( i , j ) ∈ E ( f j + 1 ) ) f_i=\max(0,\max_{(i,j)\in E}(f_j+1)) fi=max(0,(i,j)∈Emax(fj+1))
那正常来说,我们要找出度为零的点,然后倒退求解,整体使用拓扑排序,有些小麻烦。
如果用记忆化搜索,就方便多了。
#include<bits/stdc++.h>
using namespace std;
long long n,m,x,y,f[100005],ans;
vector<long long>v[100005];
long long dfs(long long x)
{
if(f[x]==0)
{
for(auto i:v[x])
{
f[x]=max(dfs(i)+1,f[x]);
}
}
return f[x];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y;
v[x].push_back(y);
}
for(int i=1;i<=n;i++)
{
ans=max(dfs(i),ans);
}
cout<<ans<<endl;
}
H.Grid 1
这题像休息的驿站,反而简单一点,赶紧做了去看下一题吧!
f i , j f_{i,j} fi,j 表示从 ( 1 , 1 ) (1,1) (1,1) 走到 ( i , j ) (i,j) (i,j) 的方案数。
f i , j = { 0 ( c i , j = # ) f i − 1 , j + f i , j − 1 ( c i , j = . ) f_{i,j}= \begin{cases} 0(c_{i,j}=\#)\\ f_{i-1,j}+f_{i,j-1}(c_{i,j}=.) \end{cases} fi,j={0(ci,j=#)fi−1,j+fi,j−1(ci,j=.)
#include<bits/stdc++.h>
using namespace std;
long long n,m,f[1005][1005];
char c[1005][1005];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>c[i][j];
}
}
f[1][1]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(c[i][j]=='#')continue;
if(i==j&&i==1)continue;
f[i][j]=f[i-1][j]+f[i][j-1];
f[i][j]%= 1000000007;
}
}
cout<<f[n][m]<<endl;
}
I.Coin
这是一道概率动态规划,但是比较简单,跟一些比较复杂的期望动态规划比真的太简单了。
f
i
,
j
f_{i,j}
fi,j 表示前
i
i
i 个硬币正面
j
j
j 次的概率。
f
i
,
j
=
f
i
−
1
,
j
−
1
×
a
i
+
f
i
−
1
,
j
×
(
1
−
a
i
)
f_{i,j}=f_{i-1,j-1}\times a_i+f_{i-1,j}\times (1-a_i)
fi,j=fi−1,j−1×ai+fi−1,j×(1−ai)
最后将 f n , i ( n 2 + 1 ≤ i ≤ n ) f_{n,i}(\frac{n}{2}+1\leq i\leq n) fn,i(2n+1≤i≤n) 累加即可。
#include<bits/stdc++.h>
using namespace std;
long long n;
double a[10005],f[3000][3000],ans;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=i;j++)
{
f[i][j]=f[i-1][j-1]*a[i]+f[i-1][j]*(1-a[i]);
}
}
for(int i=(n+1)/2;i<=n;++i){
ans+=f[n][i];
}
printf("%.11lf",ans);
return 0;
}
J.Sushi
一道好玩的期望动态规划,只要想到状态,问题就简单了。
设 f i , j , k f_{i,j,k} fi,j,k 为还有 i i i 个剩1个的盘子,还有 j j j 个剩2个的盘子,还有 k k k 个剩3个的盘子时期望的操作次数。
f i , j , k = n i + j + k + i i + j + k f i − 1 , j , k + j i + j + k f i + 1 , j − 1 , k + k i + j + k f i , j + 1 , k − 1 f_{i,j,k}=\frac{n}{i+j+k}+\frac{i}{i+j+k}f_{i-1,j,k}+\frac{j}{i+j+k}f_{i+1,j-1,k}+\frac{k}{i+j+k}f_{i,j+1,k-1} fi,j,k=i+j+kn+i+j+kifi−1,j,k+i+j+kjfi+1,j−1,k+i+j+kkfi,j+1,k−1
#include<bits/stdc++.h>
using namespace std;
long long n,a,b,c,x;
double f[305][305][305];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
if(x==1)a++;
if(x==2)b++;
if(x==3)c++;
}
for(int k=0;k<=n;k++)
{
for(int j=0;j<=n;j++)
{
for(int i=0;i<=n;i++)
{
if(i==0&&j==0&&k==0)continue;
if(i!=0)f[i][j][k]+=i*1.00000/n*f[i-1][j][k];
if(j!=0)f[i][j][k]+=j*1.00000/n*f[i+1][j-1][k];
if(k!=0)f[i][j][k]+=k*1.00000/n*f[i][j+1][k-1];
f[i][j][k]++;
f[i][j][k]/=(i+j+k)*1.00000/n;
}
}
}
printf("%.10lf",f[a][b][c]);
}
K.Stones
对于每个状态,枚举可能选择的石头数量,如果有一个方案是可以保证先手必胜的,那就是先手必胜,否则后手必胜。
设
f
i
f_i
fi表示
i
i
i个石子时是否是先手必胜。
f
i
∣
=
f
i
−
a
j
(
a
j
≤
i
)
f_i|=f_{i-a_j}(a_j\leq i)
fi∣=fi−aj(aj≤i)
#include<bits/stdc++.h>
using namespace std;
bool f[100005];
long long n,k,a[100005];
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=k;i++)
{
for(int j=1;j<=n&&a[j]<=i;j++)
{
f[i]|=!f[i-a[j]];
}
}
if(f[k])
{
cout<<"First"<<endl;
}
else
{
cout<<"Second"<<endl;
}
}
L.Deque
跟上面那道很像!列出状态之后,套路就一致了,看看公式应该不难理解。
设
f
i
,
j
f_{i,j}
fi,j表示双端队列长度为
i
i
i,对应到原数组,左边界是
j
j
j的先后手之差。
f
i
,
j
=
max
(
−
f
i
−
1
,
j
+
a
j
+
i
−
1
,
−
f
i
−
1
,
j
+
1
+
a
j
)
;
f_{i,j}=\max(-f_{i-1,j}+a_{j+i-1},-f_{i-1,j+1}+a_j);
fi,j=max(−fi−1,j+aj+i−1,−fi−1,j+1+aj);
#include<bits/stdc++.h>
using namespace std;
long long n,a[3005],sum[3005],f[3005][3005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j+i-1<=n;j++)
{
f[i][j]=max(-f[i-1][j]+a[j+i-1],-f[i-1][j+1]+a[j]);
}
}
cout<<f[n][1]<<endl;
}
M. Candies
这道题是一道典型的前缀和优化DP。
设
f
i
,
j
f_{i,j}
fi,j表示前
i
i
i个人分了
j
j
j颗糖的方案数,那么容易得到:
f
i
,
j
=
∑
k
=
0
a
i
f
i
−
1
,
j
−
k
f_{i,j}=\sum_{k=0}^{a_i}f_{i-1,j-k}
fi,j=k=0∑aifi−1,j−k
时间复杂度
O
(
n
k
2
)
O(nk^2)
O(nk2),显然时超。
如何优化?注意到和式内式连续的一段区间和,我们可以用前缀和
O
(
1
)
O(1)
O(1)求出。
于是本题时间复杂度变为
O
(
n
k
)
O(nk)
O(nk)
注意前缀和要提防数组越界,为此,前缀和数组下标全部往前挪一位。
#include<bits/stdc++.h>
using namespace std;
long long n,k,a[105],f[105][100005],sum[105][100005];
const long long mod=1e9+7;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
f[0][0]=1;
sum[0][1]=1;
for(int i=1;i<=k+1;i++)sum[0][i]=1;
for(int i=1;i<=n;i++)
{
for(long long j=0;j<=k;j++)
{
f[i][j]+=(sum[i-1][j+1]-sum[i-1][max(0ll,j-a[i])]+mod)%mod;
f[i][j]%=mod;
sum[i][j+1]=sum[i][j]+f[i][j];
sum[i][j+1]%=mod;
}
}
cout<<f[n][k]<<endl;
}
N.Slimes
这不就是石子合并吗?是时候祭出远古黑历史了——This。
我们设
f
i
,
j
f_{i,j}
fi,j 表示区间
[
i
,
j
]
[i,j]
[i,j] 合并所得的最小代价,不难想到枚举一个中点
k
k
k,表示合并区间
[
i
,
k
]
[i,k]
[i,k] 和
[
k
+
1
,
j
]
[k+1,j]
[k+1,j] 组成的区间 。
f
i
,
j
=
min
k
=
i
j
−
1
(
f
i
,
k
+
f
k
+
1
,
j
)
+
∑
k
=
i
j
a
i
f_{i,j}=\min_{k=i}^{j-1}(f_{i,k}+f_{k+1,j})+\sum_{k=i}^{j}a_i
fi,j=k=iminj−1(fi,k+fk+1,j)+k=i∑jai
前面的最小值表示将石子合并成两堆的最小代价,后面的区间和是本次合并的代价,无论如何,这次操作的代价永远都是它。
后面那个东西可以用前缀和优化。
还可以用四边形不等式优化,不过这里就不赘述了。
#include<bits/stdc++.h>
using namespace std;
long long dp[505][505],sum[505],n,a[505],mn=1E9;
int main()
{
memset(dp,127,sizeof(dp));
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i][i]=0;
}
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+a[i];
}
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
int j=i+k-1;
if(j>n)continue;
for(int l=i;l<j;l++)
{
dp[i][j]=min(dp[i][j],dp[i][l]+dp[l+1][j]+sum[j]-sum[i-1]);
}
}
}
cout<<dp[1][n]<<endl;
}
O.Matching
这是一道状态压缩 DP。
我们设 f i , j f_{i,j} fi,j 表示左边部分的前 i i i 个点匹配右边集合为 j j j 的点的方案数。
则可得方程:
f
i
,
j
=
∑
k
∈
j
,
a
i
,
k
=
1
f
i
−
1
,
j
−
k
f_{i,j}=\sum_{k\in j,a_{i,k}=1} f_{i-1,j-k}
fi,j=k∈j,ai,k=1∑fi−1,j−k
也就是我们枚举
i
i
i 选择的点
k
k
k ,这种选法的贡献就是前
i
−
1
i-1
i−1 个点选择右边
j
j
j 集合除掉
k
k
k 的点的方案数。
注意到这里出现了集合的概念,显然实现时难以表示,我们可以转换为一个二进制数。
这个二进制数可以看作是一个布尔数组,第 i i i 位表示右边第 i i i 的点是否被选中。
这样集合除去的操作可以用二进制中的异或完成。
但是这样还是会超时,注意到方程中的 i i i 等于 j j j 的二进制中的 1 1 1 的个数,所以直接枚举状态,找出对应的 i i i 即可。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL N=(1<<21);
const LL mod=1e9+7;
LL n,a[22][22],f[22][N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
}
}
f[0][0]=1;
for(int j=1;j<N;j++)
{
LL cnt=0;
for(int i=1;i<=n;i++)cnt+=(j>>(i-1))&1;
for(int i=1;i<=n;i++)
{
if((j>>(i-1))&1==0)continue;
if(a[cnt][i])
{
f[cnt][j]=(f[cnt][j]+f[cnt-1][j^(1<<(i-1))])%mod;
}
}
}
cout<<f[n][(1<<n)-1];
}
P.Independent Set
这道题显然是树形 DP。
我们设 f i , 0 f_{i,0} fi,0 表示在 i i i 的子树中染色, i i i 为白色的方案数。
我们设 f i , 1 f_{i,1} fi,1 表示在 i i i 的子树中染色, i i i 为黑色的方案数。
考虑到题目给的限制,易得:
f
i
,
0
=
∏
j
∈
i
s
o
n
(
f
i
,
0
+
f
i
,
1
)
f
i
,
1
=
∏
j
∈
i
s
o
n
f
i
,
0
f_{i,0}=\prod_{j\in ison}(f_{i,0}+f_{i,1})\\ f_{i,1}=\prod_{j\in ison}f_{i,0}\\
fi,0=j∈ison∏(fi,0+fi,1)fi,1=j∈ison∏fi,0
#include<bits/stdc++.h>
using namespace std;
vector<long long>v[100005];
long long n,x,y,f[100005][2];
const long long mod=1e9+7;
void dfs(long long x,long long fa)
{
if(f[x][1]&&f[x][0])return;
f[x][1]=f[x][0]=1;
for(auto i:v[x])
{
if(i==fa)continue;
dfs(i,x);
f[x][0]*=f[i][1]+f[i][0];
f[x][1]*=f[i][0];
f[x][0]%=mod;
f[x][1]%=mod;
}
}
int main()
{
cin>>n;
for(int i=1;i<=n-1;i++)
{
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
dfs(1,0);
cout<<(f[1][0]+f[1][1])%mod<<endl;
}
Q.Flowers
我们设 f i f_i fi 表示以 i i i 为结尾的不下降子序列的最大权值。
得方程:
f
i
=
max
j
<
i
,
h
j
≤
h
i
(
f
j
)
+
a
i
f_i=\max_{j<i,h_j \leq h_i}(f_j) +a_i
fi=j<i,hj≤himax(fj)+ai
这个方程显然是个二维偏序问题,我们用一个树状数组维护一个前缀最大值即可。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL N=5e5+5;
LL n,a[N],b[N],t[N],f[N],mx;
LL lowbit(LL x)
{
return x&-x;
}
LL query(LL x)
{
LL ans=0;
while(x)
{
ans=max(ans,t[x]);
x-=lowbit(x);
}
return ans;
}
void update(LL x,LL y)
{
while(x<=n)
{
t[x]=max(t[x],y);
x+=lowbit(x);
}
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&b[i]);
}
for(int i=1;i<=n;i++)
{
f[i]=query(a[i])+b[i];
update(a[i],f[i]);
mx=max(f[i],mx);
}
printf("%lld",mx);
}
R.Walk
我们考虑暴力地用 Floyd 来求解方案数,设 f i , j , k f_{i,j,k} fi,j,k 为长度为 k k k, i → j i\to j i→j 的路径数。
得:
f
i
,
j
,
k
=
∑
0
<
f
i
,
l
,
k
−
1
,
0
<
f
l
,
j
,
k
−
1
f
i
,
l
,
k
−
1
×
f
l
,
j
,
k
−
1
f_{i,j,k}=\sum_{0<f_{i,l,k-1},0<f_{l,j,k-1}}f_{i,l,k-1}\times f_{l,j,k-1}
fi,j,k=0<fi,l,k−1,0<fl,j,k−1∑fi,l,k−1×fl,j,k−1
显然条件可要可不要,因为等于
0
0
0 时贡献也为
0
0
0,也就是:
f
i
,
j
,
k
=
∑
l
=
1
n
f
i
,
l
,
k
−
1
×
f
l
,
j
,
k
−
1
f_{i,j,k}=\sum_{l=1}^nf_{i,l,k-1}\times f_{l,j,k-1}
fi,j,k=l=1∑nfi,l,k−1×fl,j,k−1
这个式子中,
k
k
k 步的方案都由
k
−
1
k-1
k−1 步得到,我们考虑滚动数组舍去
k
k
k。
f
i
,
j
=
∑
k
=
1
n
f
i
,
k
×
f
k
,
j
f_{i,j}=\sum_{k=1}^nf_{i,k}\times f_{k,j}
fi,j=k=1∑nfi,k×fk,j
这个式子非常眼熟,这不是矩阵乘法吗?原来我们可以直接来一个矩阵快速幂来求出答案。
我们先处理出邻接矩阵,然后求出其 K K K 次方即可,这就是我们要的 f i , j , k f_{i,j,k} fi,j,k,加起来就是答案。
#include<bits/stdc++.h>
using namespace std;
long long n,k,sum;
const long long mod=1e9+7;
struct Matrix
{
long long n,m,a[105][105];
void Input(long long x)
{
n=x,m=x;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
}
} a;
Matrix operator*(const Matrix &a,const Matrix &b)
{
Matrix c;
c.n=a.n;
c.m=b.m;
for(int i=1;i<=c.n;i++)
{
for(int j=1;j<=c.m;j++)
{
c.a[i][j]=0;
}
}
for(int i=1;i<=a.n;i++)
{
for(int j=1;j<=b.m;j++)
{
for(int k=1;k<=a.m;k++)
{
c.a[i][j]+=a.a[i][k]*b.a[k][j];
c.a[i][j]%=mod;
}
}
}
return c;
}
Matrix ksm(Matrix x,long long y)
{
Matrix ans=x;
y--;
while(y)
{
if(y&1)
{
ans=ans*x;
}
x=x*x;
y>>=1;
}
return ans;
}
int main()
{
cin>>n>>k;
a.Input(n);
a=ksm(a,k);
for(int i=1;i<=a.n;i++)
{
for(int j=1;j<=a.m;j++)
{
sum+=a.a[i][j];
sum%=mod;
}
}
cout<<sum<<endl;
}
S.Digit Sum
数位 DP 基础题。
我们设 f i , j , 0 f_{i,j,0} fi,j,0 表示在数字 K K K 从左往右第 i i i 位往后,余数为 j j j ,不需要考虑小于 K K K 这一条件的方案数。
我们设 f i , j , 1 f_{i,j,1} fi,j,1 表示在数字 K K K 从左往右第 i i i 位往后,余数为 j j j ,前面与 K K K 都一样,需要考虑小于 K K K 这一条件的方案数。
两个的不同在于选择的范围不同,没有限制则可以选择 [ 0 , 9 ] [0,9] [0,9] 的所有数字,有限制则不能超过 K K K。
我们设 K K K 从左往右第 i i i 位为 K i K_i Ki。
得:
f
i
,
j
,
0
=
∑
k
=
0
9
f
i
+
1
,
j
−
k
,
0
f
i
,
j
,
1
=
∑
k
=
0
K
i
−
1
(
f
i
+
1
,
j
−
k
,
0
)
+
f
i
+
1
,
j
−
K
i
,
1
f_{i,j,0}=\sum_{k=0}^9f_{i+1,j-k,0}\\ f_{i,j,1}=\sum_{k=0}^{K_i-1}(f_{i+1,j-k,0})+f_{i+1,j-K_i,1}
fi,j,0=k=0∑9fi+1,j−k,0fi,j,1=k=0∑Ki−1(fi+1,j−k,0)+fi+1,j−Ki,1
注意
j
−
k
j-k
j−k 其实是要取模的,但我没写。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL mod=1e9+7;
char c[10005];
LL n,k,f[10005][105][2];
int main()
{
cin>>(c+1)>>k;
n=strlen(c+1);
f[n+1][0][0]=f[n+1][0][1]=1;
for(int i=n;i>=1;i--)
{
for(int j=0;j<=k-1;j++)
{
for(int l=0;l<=9;l++)
{
LL t=((j-l)%k+k)%k;
f[i][j][0]=(f[i][j][0]+f[i+1][t][0])%mod;
if(l<c[i]-'0')f[i][j][1]=(f[i][j][1]+f[i+1][t][0])%mod;
if(c[i]-'0'==l)f[i][j][1]=(f[i][j][1]+f[i+1][t][1])%mod;
}
}
}
cout<<(f[1][0][1]-1+mod)%mod<<endl;
}