买不到的数目
题目大意
小明开了一家糖果店。
他别出心裁:把水果糖包成4颗一包和7颗一包的两种。糖果不能拆包卖。
小朋友来买糖的时候,他就用这两种包装来组合。
当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。
你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。
大于17的任何数字都可以用4和7组合出来。
本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。
输入格式
两个正整数 n,m,表示每种包装中糖的颗数。
输出格式
一个正整数,表示最大不能买到的糖数。
数据范围
2≤n,m≤1000 保证数据一定有解。
输入样例
4 7
输出样例
17
解题思路
两个正整数n , m互质,它们最大的不能凑出来的正整数是(n-1)(m-1)-1
#include <cstdio>
using namespace std;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
printf("%d\n",(n-1)*(m-1)-1);
return 0;
}
蚂蚁感冒
题目大意
长 100 厘米的细长直杆子上有 n 只蚂蚁。它们的头有的朝左,有的朝右。 每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。这些蚂蚁中,有 1 只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
输入格式
第一行输入一个整数 n, 表示蚂蚁的总数。
接着的一行是 n 个用空格分开的整数 X
i
_i
i, X
i
_i
i的绝对值表示蚂蚁离开杆子左边端点的距离。
正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。其中,第一个数据代表的蚂蚁感冒了。
输出格式
输出1个整数,表示最后感冒蚂蚁的数目。
数据范围
1<n<50,0<|X
i
_i
i|<100
输入样例
5
-10 8 -20 12 25
输出样例
3
解题思路
第一个蚂蚁向右走的情况:
1.右边向左走,必然被感染。
2.右边向右走,必然不会被感染
3.左边向左走,必然不会被感染
4.左边向右走:
(1)右边存在向左走,则必然被感染。
(2)右边不存在向左走,则必然不会被感染
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n,l=0,r=0,x,t;
scanf("%d%d",&n,&x);
for(int i=1;i<n;++i){
scanf("%d",&t);
if(abs(t)<abs(x)&&t>0) l++; //左边位置向右走
if(abs(t)>abs(x)&&t<0) r++; //右边位置向左走
}
if((!l&&x<0)||(!r&&x>0)) printf("1\n"); //未感染
else printf("%d\n",l+r+1);
return 0;
}
饮料换购
题目大意
乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。
请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。
输入格式
输入一个整数 n,表示初始买入的饮料数量。
输出格式
输出一个整数,表示一共能够喝到的饮料数量。
数据范围:0<n<10000
输入样例
100
输出样例
149
解题思路
三个瓶盖换一瓶饮料,如果瓶盖数 < 3的时候就不能换饮料,不断更新瓶盖数量,最后输出结果。
#include <iostream>
using namespace std;
int main()
{
int n,cnt=0;
cin>>n;
cnt=n;
while(n>=3)
{
int t=n%3;
cnt+=n/3;
n/=3;
n+=t;
}
cout<<cnt<<endl;
return 0;
}
01背包问题
题目大意
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 v
i
_i
i,价值是 w
i
_i
i。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行两个整数 N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 v
i
_i
i,w
i
_i
i,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围:0<N,V≤1000,0<v
i
_i
i,w
i
_i
i≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
二维的做法:
for(int i=1;i<=n;++i)
for(int j=v;j>=0;--j)
if(j>=V[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-V[i]]+W[i]);
else dp[i][j]=dp[i-1][j];
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int dp[maxn][maxn]; //表示体积为 v的背包能装价值为w的物品
int V[maxn],W[maxn];
int main()
{
int n,v;
scanf("%d%d",&n,&v);
for(int i=1;i<=n;++i) scanf("%d%d",&V[i],&W[i]);
for(int i=1;i<=n;++i)
for(int j=v;j>=0;--j)
if(j>=V[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-V[i]]+W[i]);
else dp[i][j]=dp[i-1][j];
printf("%d\n",dp[n][v]);
return 0;
}
一维的做法:
for(int i=1;i<=n;++i)
for(int j=v;j>=V[i];--j)
if(dp[j-V[i]]+W[i]>dp[j]) dp[j]=dp[j-V[i]]+W[i];
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int dp[maxn];
int V[maxn],W[maxn];
int main()
{
int n,v;
scanf("%d%d",&n,&v);
for(int i=1;i<=n;++i) scanf("%d%d",&V[i],&W[i]);
for(int i=1;i<=n;++i)
for(int j=v;j>=V[i];--j)
if(dp[j-V[i]]+W[i]>dp[j]) dp[j]=dp[j-V[i]]+W[i];
printf("%d\n",dp[v]);
return 0;
}
摘花生
题目大意
Hello Kitty想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。Hello Kitty只能向东或向南走,不能向西或向北走。问Hello Kitty最多能够摘到多少颗花生。
输入格式
第一行是一个整数T,代表一共有多少组数据。
接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
数据范围 1≤T≤100,1≤R,C≤100,0≤M≤1000
输入样例
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例
8
16
解题思路
dp[i][j]:表示第 i 行第 j 列的花生数量。
状态转移方程: dp[i][j]+=max(dp[i-1][j],dp[i][j-1])
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1e2+7;
ll dp[maxn][maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int r,c;
scanf("%d%d",&r,&c);
for(int i=1;i<=r;++i)
for(int j=1;j<=c;++j) scanf("%lld",&dp[i][j]);
for(int i=1;i<=r;++i)
for(int j=1;j<=c;++j)
dp[i][j]+=max(dp[i-1][j],dp[i][j-1]);
printf("%d\n",dp[r][c]);
}
return 0;
}
最长上升子序列
题目大意
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数N。
第二行包含N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,10
−
9
^{-9}
−9 ≤数列中的数≤10
9
^9
9
输入样例
7
3 1 2 1 8 5 6
输出样例
4
时间复杂度O(n 2 ^2 2)做法:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int dp[maxn],a[maxn];
int vis[maxn],ok,t[maxn];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
int ans=0;dp[1]=1;
for(int i=2;i<=n;++i)
{
dp[i]=1;
for(int j=1;j<i;++j){
if(a[j]<a[i]&&dp[i]<dp[j]+1){
dp[i]=dp[j]+1;
vis[i]=j; //记录状态
}
// ans=max(ans,dp[i]);
if(ans<dp[i]) ok=i,ans=dp[i]; //记录最优解
}
}
/***
输出最优解的序列
for(int i=ans;i>0;--i){
t[i]=a[ok];
ok=vis[ok];
}
for(int i=1;i<=ans;++i) printf("%d ",t[i]);puts("");
***/
printf("%d\n",ans);
return 0;
}
二分做法:
时间复杂度O(nlog
2
_2
2n)
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e3+7;
int a[maxn],res[maxn];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;++i) scanf("%d",&a[i]);
int cnt=0;
for(int i=0;i<n;++i)
{
int l=0,r=cnt;
while(l<r)
{
int mid=(l+r+1)>>1;
if(res[mid]<a[i]) l=mid;
else r=mid-1;
}
cnt=max(cnt,l+1);
res[l+1]=a[i]; //保存最长的严格单调的最长上升子序列
}
// for(int i=1;i<=cnt;++i) printf("%d ",res[i]);puts("");
printf("%d\n",cnt);
return 0;
}
地宫取宝
题目大意
X 国王有一个地宫宝库,是 n×m个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。地宫的入口在左上角,出口在右下角。小明被带到地宫的入口,国王要求他只能向右或向下行走。走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。当小明走到出口时,如果他手中的宝贝恰好是 k件,则这些宝贝就可以送给小明。请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。
输入格式
第一行 3 个整数,n,m,k,含义见题目描述。
接下来 n 行,每行有 m 个整数 C
i
_i
i
用来描述宝库矩阵每个格子的宝贝价值。
输出格式
输出一个整数,表示正好取 k 个宝贝的行动方案数。
该数字可能很大,输出它对 1000000007 取模的结果。
数据范围:1≤n,m≤50,1≤k≤12,0≤C
i
_i
i≤12
输入样例
2 3 2
1 2 3
2 1 5
输出样例
14
解题思路
#include <cstdio>
#define rep(i,a,b) for(auto i=(a);i<=(b);++i)
using namespace std;
const int mod=1e9+7;
int dp[55][55][15][15],a[55][55];
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
rep(i,1,n)
rep(j,1,m) scanf("%d",&a[i][j]),a[i][j]++;
dp[1][1][0][0]=dp[1][1][1][a[1][1]]=1;
//f[i][j][x][y]表示:在(i, j)这个点,拿了x 个物品,这些物品中价值最大的是y
rep(i,1,n)
rep(j,1,m)
rep(x,0,k)
rep(y,0,13){ 遍历的是物品价值为y的值
//不拿物品
dp[i][j][x][y]=(dp[i][j][x][y]+dp[i-1][j][x][y])%mod;
dp[i][j][x][y]=(dp[i][j][x][y]+dp[i][j-1][x][y])%mod;
//可以拿
if(x&&y==a[i][j]){ //如果这次拿了的话,这次拿的物品一定是最大的
rep(z,0,y-1){
/***
取了当前这个点的物品,那么就要把“上一步”可能在的两个点中,手上的所有物
品最大值比当前这个位置的物品最大值小的累加起来,因为求的是总方案数嘛。
***/
dp[i][j][x][y]=(dp[i][j][x][y]+dp[i-1][j][x-1][z])%mod;
dp[i][j][x][y]=(dp[i][j][x][y]+dp[i][j-1][x-1][z])%mod;
}
}
}
int res=0;
rep(i,1,13) res=(res+dp[n][m][k][i])%mod;
printf("%d\n",res);
return 0;
}
波动数列
题目大意
观察这个数列:1 3 0 2 -1 1 -2 …
这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数。
栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?
输入格式
共一行,包含四个整数 n,s,a,b,含义如前面所述。
输出格式
共一行,包含一个整数,表示满足条件的方案数。
由于这个数很大,请输出方案数除以 100000007 的余数。
数据范围:1≤n≤1000,−10
9
^9
9≤s≤10
9
^9
9,1≤a,b≤10
6
^6
6
输入样例
4 10 2 3
输出样例
2
样例解释:两个满足条件的数列分别是2 4 1 3和7 4 1 -2。
解题思路
设第一个数为
x
x
x,则第二个数为
x
+
d
1
x+d_1
x+d1,第三个数为
x
x
x+
d
1
d_1
d1+
d
2
d_2
d2…。
这里的
d
1
d1
d1,
d
2
d2
d2表示
a
a
a或者
−
b
{−b}
−b,所以这个数列为:
x
,
x
x, x
x,x+
d
1
d_1
d1,
x
x
x+
d
1
d_1
d1+
d
2
d_2
d2,
x
x
x+
d
1
d_1
d1+
d
2
d_2
d2+
d
3
d_3
d3, …,
x
x
x+
d
1
d_1
d1+
d
2
d_2
d2+…+
d
n
−
1
d_{n−1}
dn−1,又因为数列之和为
s
s
s,所以转化成:
n
∗
x
+
(
n
−
1
)
∗
n∗x+(n−1)∗
n∗x+(n−1)∗
d
1
d_1
d1 +
(
n
−
2
)
∗
(n−2)∗
(n−2)∗
d
2
d_2
d2+
(
n
−
3
)
∗
(n−3)∗
(n−3)∗
d
3
d_3
d3+…+
d
n
−
1
d_{n−1}
dn−1=
s
s
s
再在一步转化:
(
s
−
[
(
n
−
1
)
∗
(s−[(n−1)∗
(s−[(n−1)∗
d
1
d_1
d1+
(
n
−
2
)
(n−2)
(n−2)∗
d
2
d_2
d2+
(
n
−
3
)
∗
(n−3)∗
(n−3)∗
d
3
d_3
d3+…+
d
n
−
1
d_{n−1}
dn−1])
n
n
n=
x
x
x
因为
x
x
x 是任意整数,所以又转化成:
s
s
s 与
(
n
−
1
)
∗
(n−1)∗
(n−1)∗
d
1
d_1
d1+
(
n
−
2
)
∗
(n−2)∗
(n−2)∗
d
2
d_2
d2+
(
n
−
3
)
∗
(n−3)∗
(n−3)∗
d
3
d_3
d3+…+
d
n
−
1
d_{n−1}
dn−1 模
x
x
x 的余数相同
dp[i][j]:所有只考虑前 i 项,且当前的总和除以 n 的余数是 j 的方案的集合。
状态转移:dp[i][j]=dp[i-1][(j-ia)%n]+dp[i-1][(j+ib)%n];
#include <cstdio>
using namespace std;
typedef long long ll;
const ll maxn=1e3+7,mod=1e8+7;
ll dp[maxn][maxn];
ll work(int a,int b)
{
return (a%b+b)%b;
}
int main()
{
int n,s,a,b;
scanf("%d%d%d%d",&n,&s,&a,&b);
dp[0][0]=1;
for(int i=1;i<n;++i)
for(int j=0;j<n;++j){
ll ta=work(j-(n-i)*a,n);
ll tb=work(j+(n-i)*b,n);
dp[i][j]=(dp[i-1][ta]+dp[i-1][tb])%mod;
}
ll t=work(s,n);
printf("%lld\n",dp[n-1][t]);
return 0;
}