[牛客]被3整除的子序列
题目:
给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除,答案对1e9+7取模
解析:首先对其子问题进行解决,前i个元素所构成的序列有几个子序列mod 3的余数分别是0,1,2,那么推广到i+1,考虑第i+1个元素
1.若单取s[i+1],则dp[i+1][s[i+1]%3]++;
2.不取s[i+1],则余数与前面的子序列相同
3.取前面的子序列以及s[i+1]
下面给出代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+7;
ll dp[550][3];//表示前i个数中模3余0,1,2的子序列数
int main(){
string s;
cin>>s;
dp[0][(s[0]-'0')%3]=1;
for(int i=1;i<s.size();i++){
dp[i][(s[i]-'0')%3]++;//单取
for(int j=0;j<=2;j++){
dp[i][j]+=dp[i-1][j]+dp[i-1][((j-(s[i]-'0'))%3+3)%3]//保证是正数;
}
}
cout<<dp[s.size()-1][0]%mod;
return 0;
}
P1005 [NOIP2007 提高组] 矩阵取数游戏
这里用了__int128 因为不想打高精
首先注意到每一行是相互独立的,那么就分别处理每一行,最后求和
然后找到状态方程dp[i][j]=max(2*dp[i+1][j]+dp[i][i],2*dp[i][j-1]+dp[j][j])
dp[i][j]表示取区间[i,j]所能得到的最大值,因为只能取两边,所以最后一次取的数一定是最边上的两个中的一个,那么就得到了状态方程
注意这里左端点要从n-1开始遍历,因为这样才可以得到前面的状态
最后的dp[1][m]记得×2
#include<bits/stdc++.h>
#define ll __int128
using namespace std;
inline ll read(){
char ch=getchar();int s=0,w=1;
while(ch<48||ch>57){
if(ch=='-')w=-1;ch=getchar();
}
while(ch>=48&&ch<=57){
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
}
return s*w;
}
inline void write(ll x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
int n,m;
ll dp[90][90],a[90],ans;
int main(){
cin>>n>>m;
while(n--){
for(int i=1;i<=m;i++)dp[i][i]=read();
for(int i=m-1;i>=1;i--){
for(int j=i+1;j<=m;j++){
dp[i][j]=max(2*dp[i+1][j]+dp[i][i],2*dp[i][j-1]+dp[j][j]);
}
}
ans+=2*dp[1][m];
}
write(ans);
return 0;
}
[洛谷]P3951 [NOIP2017 提高组] 小凯的疑惑 / [蓝桥杯 2013 省] 买不到的数目
这里插入一道非dp的题目,主要是为了下面一道题目的理解
题目:
小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小 凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在小凯无法准确支付的商品。
ans=a*b-a-b //证明在下面那道题里,懒得贴了
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read(){
char ch=getchar();ll s=0,w=1;
while(ch<48||ch>57){
if(ch=='-')w=-1;ch=getchar();
}
while(ch>=48&&ch<=57){
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
}
return s*w;
}
inline void write(ll x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
int main(){
ll a,b;
a=read(),b=read();
write(a*b-a-b);
}
[洛谷]P1052 [NOIP2005 提高组] 过河
题目:
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,⋯,L(其中L 是桥的长度)。坐标为 0 的点表示桥的起点,坐标为 L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T)。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度 L,青蛙跳跃的距离范围 S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
输入格式
第一行有 1 个正整数L(1≤L≤109),表示独木桥的长度。
第二行有 3个正整数 S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离及桥上石子的个数,其中1≤S≤T≤10,1≤M≤100。
第三行有 M个不同的正整数分别表示这 M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
分析:首先注意到L的取值范围,所以想到压缩路径(我是蒟蒻,偷看了题解)
1.对于S=T的情况,直接分析石头的位置即可
2.对于T>S,考虑最短的两种步长S,S+1:
下证明存在非负整数x,y使k>=0,S*x+(S+1)*y=-S+k
(也就是对于距离L>=L1=-S的点都可以到达,那么就可以把距离大于L1的两个石头距离缩短为L1-1,不放心可以压缩的路径大一点)
设k=pS+q,p,q均为非负整数,且q<=S-1
-S+k= -S+pS+q=q(S+1)-qS+ -S+pS= q(S+1)+S(S-1+p-q)
那么x=q,y=p+(S-1)-q满足题设(这时两者均非负)
压缩路径的正确性个人理解:
要证明压缩路径的可行性,就是要证明第二个石头后面的棕色部分在压缩前后保持不变,而粉色那段(无论多长)永远由标注数字的绿色那段决定,也就是后面的棕色部分也由绿色那段决定,所以说压缩前后是保持不变的。
#include<bits/stdc++.h>
using namespace std;
inline int read(){
char ch=getchar();int s=0,w=1;
while(ch<48||ch>57){
if(ch=='-')w=-1;ch=getchar();
}
while(ch>=48&&ch<=57){
s=(s<<1)+(s<<3)+ch-48;ch=getchar();
}
return s*w;
}
inline void write(int x){
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
int store[110],dp[10000],sto[10000];//记录石头的位置
bool flag[10000]={1};//记录i位置能否跳到
int main(){
int L,S,T,M;
L=read(),S=read(),T=read(),M=read();
for(int i=0;i<M;i++)
store[i]=read();
//构造压缩后的桥
if(S==T){
int cnt=0;
for(int i=0;i<M;i++)
if(store[i]%S==0)++cnt;
write(cnt);
}
else {
sort(store,store+M);
int x=71,pos=0;//pos记录石头插入的位置
pos+=min(x,store[0]),sto[pos]=1;
for(int i=1;i<M;i++){
pos+=min(x,store[i]-store[i-1]),sto[pos]=1;
}
pos+=min(x,L-store[M-1]);
for(int i=S;i<=pos+T;i++){
bool check=false;//表示dp[i]没有赋初值
for(int j=S;j<=T;j++){
if(flag[i-j]){
flag[i]=1;
if(check)dp[i]=min(dp[i],dp[i-j]+sto[i]);
else dp[i]=dp[i-j]+sto[i],check=true;
}
}
}
int i=0,minn;
while(!flag[pos+i])++i;//找到可以给dp赋初值的位置
minn=dp[pos+i];
while(i<=T){
if(flag[pos+i])
minn=min(minn,dp[pos+i]),i++;
}
write(minn);
}
}