原题链接
进制规定了数字在数位上逢几进一。
X X X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!
例如说某种 X X X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X X X 进制数 321 321 321 转换为十进制数为 65 65 65。
现在有两个 X X X 进制表示的整数 A A A 和 B B B,但是其具体每一数位的进制还不确定,只知道 A A A 和 B B B 是同一进制规则,且每一数位最高为 N N N 进制,最低为二进制。
请你算出 A − B A−B A−B 的结果最小可能是多少。
请注意,你需要保证 A A A 和 B B B 在 X X X 进制下都是合法的,即每一数位上的数字要小于其进制。
第一行一个正整数 N N N,含义如题面所述。
第二行一个正整数 M a Ma Ma,表示 X X X 进制数 A A A 的位数。
第三行 M a M_a Ma 个用空格分开的整数,表示 X X X 进制数 A A A 按从高位到低位顺序各个数位上的数字在十进制下的表示。
第四行一个正整数 M b M_b Mb,表示 X X X 进制数 B B B 的位数。
第五行 M b M_b Mb 个用空格分开的整数,表示 X X X 进制数 B B B 按从高位到低位顺序各个数位上的数字在十进制下的表示。
请注意,输入中的所有数字都是十进制的。
输出一行一个整数,表示 X X X 进制数 A − B A−B A−B 的结果的最小可能值转换为十进制后再模 1000000007 1000000007 1000000007 的结果。
对于 30 % 30\% 30% 的数据, N ≤ 10 ; M a , M b ≤ 8 N≤10;M_a,M_b≤8 N≤10;Ma,Mb≤8,
对于 100 % 100\% 100% 的数据, 2 ≤ N ≤ 1000 ; 1 ≤ M a , M b ≤ 100000 ; A ≥ B 2≤N≤1000;1≤M_a,M_b≤100000;A≥B 2≤N≤1000;1≤Ma,Mb≤100000;A≥B。
11
3
10 4 0
3
1 2 0
94
当进制为:最低位 2 2 2 进制,第二数位 5 5 5 进制,第三数位 11 11 11 进制时,减法得到的差最小。
此时 A A A 在十进制下是 108 108 108, B B B 在十进制下是 14 14 14,差值是 94 94 94。
由题可知: 对于 X X X 进制数 321 321 321,进制分别为 8 10 2 8~10~2 8 10 2,转化为十进制数为 65 65 65。
算式: 3 ⋅ 10 ⋅ 2 + 2 ⋅ 2 + 1 = 65 3·10·2~+~2·2~+~1~=~65 3⋅10⋅2 + 2⋅2 + 1 = 65
假设, A A A 和 B B B 都是 n n n 位数,分别是 a n a n − 1 ⋅ ⋅ ⋅ a 1 a_na_{n-1}···a_1 anan−1⋅⋅⋅a1 和 b n b n − 1 ⋅ ⋅ ⋅ b 1 b_nb_{n-1}···b_1 bnbn−1⋅⋅⋅b1,对应的进制数 X X X 分别是 p n p n − 1 ⋅ ⋅ ⋅ p 1 p_np_{n-1}···p_1 pnpn−1⋅⋅⋅p1
由此可知:
A A A 的十进制应为: a n ⋅ ( p n − 1 ⋅ ⋅ ⋅ p 1 ) + a n − 1 ⋅ ( p n − 2 ⋅ ⋅ ⋅ p 1 ) + ⋅ ⋅ ⋅ + a 1 a_n·(p_{n-1}···p_1)~+~a_{n-1}·(p_{n-2}···p_1)~+···+~a_1 an⋅(pn−1⋅⋅⋅p1) + an−1⋅(pn−2⋅⋅⋅p1) +⋅⋅⋅+ a1,
B B B 的十进制应为: b n ⋅ ( p n − 1 ⋅ ⋅ ⋅ p 1 ) + b n − 1 ⋅ ( p n − 2 ⋅ ⋅ ⋅ p 1 ) + ⋅ ⋅ ⋅ + b 1 b_n·(p_{n-1}···p_1)~+~b_{n-1}·(p_{n-2}···p_1)~+···+~b_1 bn⋅(pn−1⋅⋅⋅p1) + bn−1⋅(pn−2⋅⋅⋅p1) +⋅⋅⋅+ b1,
所以, A − B = ( a n − b n ) ⋅ ( p n − 1 ⋅ ⋅ ⋅ p 1 ) + ( a n − 1 − b n − 1 ) ⋅ ( p n − 2 ⋅ ⋅ ⋅ p 1 ) + ⋅ ⋅ ⋅ + ( a 1 − b 1 ) A - B = (a_n-b_n)·(p_{n-1}···p_1)~+~(a_{n-1}-b_{n-1})·(p_{n-2}···p_1)~+···+~(a_1 - b_1) A−B=(an−bn)⋅(pn−1⋅⋅⋅p1) + (an−1−bn−1)⋅(pn−2⋅⋅⋅p1) +⋅⋅⋅+ (a1−b1)
判断当 a n ≥ b n a_n~≥~b_n an ≥ bn 时, 对应的进制数 x n x_n xn 都尽可能的取小(最小为 2 2 2 ),就可以得出 A − B A−B A−B 的最小值。
不过如果 a n < b n a_n~<~b_n an < bn 时, x n x_n xn是否依然是尽可能取小呢,答案是肯定的。
如果 a n < b n a_n~<~b_n an < bn, 我们让 x n − 1 x_{n-1} xn−1 变大来使得最终值变小,假设在原 x n − 1 x_{n-1} xn−1 的基础上增加 x x x 那么在此位的值将减小 ( b n − a n ) ⋅ ( x ⋅ x n − 2 ⋅ ⋅ ⋅ x 1 ) (b_n-a_n)·(x·x_{n-2}···x_1) (bn−an)⋅(x⋅xn−2⋅⋅⋅x1),
注意到,题目在最后数据范围中给出 A ≥ B A ≥ B A≥B 条件,说明如果存在 a n < b n a_n < b_n an<bn 的情况,那么在更高位一定存在 a m > b m a_m > b_m am>bm,相应的, 因为比 n n n 更高位的每个数都要乘上这个 x n − 1 x_{n-1} xn−1, 所以, 想要使更高位的增值最小的情况是 m = n + 1 m = n + 1 m=n+1,增值为 ( a n + 1 − b n + 1 ) ⋅ ( x n ⋅ x ⋅ x n − 2 ⋅ ⋅ ⋅ x 1 ) (a_{n+1}-b_{n+1})·(x_n·x·x_{n-2}···x_1) (an+1−bn+1)⋅(xn⋅x⋅xn−2⋅⋅⋅x1)
将两次变化值相加得: [ ( a n + 1 − b n + 1 ) ⋅ x n − ( b n − a n ) ] ⋅ ( x ⋅ x n − 2 ⋅ ⋅ ⋅ x 1 ) [~(a_{n+1}-b_{n+1})·x_n-(b_n-a_n)~]·(x·x_{n-2}···x_1) [ (an+1−bn+1)⋅xn−(bn−an) ]⋅(x⋅xn−2⋅⋅⋅x1)
显然, ( x ⋅ x n − 2 ⋅ ⋅ ⋅ x 1 ) > 0 (x·x_{n-2}···x_1)~>~0 (x⋅xn−2⋅⋅⋅x1) > 0,只需要判断 ( a n + 1 − b n + 1 ) ⋅ x n − ( b n − a n ) (a_{n+1}-b_{n+1})·x_n-(b_n-a_n) (an+1−bn+1)⋅xn−(bn−an) 的正负。
因为, a n + 1 > b n + 1 a_{n+1}~>~b_{n+1} an+1 > bn+1 并且都为正整数,所以, ( a n + 1 − b n + 1 ) ≥ 1 (a_{n+1}-b_{n+1})~≥~1 (an+1−bn+1) ≥ 1
又因为, n n n 进制一定大于 n n n 进制每一位的数,所以, x n > b n > b n − a n x_n>b_n>b_n-a_n xn>bn>bn−an
所以, ( a n + 1 − b n + 1 ) ⋅ x n − ( b n − a n ) > 0 (a_{n+1}-b_{n+1})·x_n-(b_n-a_n)~>~0 (an+1−bn+1)⋅xn−(bn−an) > 0
由此可知,最终值一定变大,故当 a n < b n a_n~<~b_n an < bn 时, x n x_n xn依然是要尽可能取小
#include
using namespace std;
typedef long long ll;
#define mod 1000000007
#define N 100010
int n, ma, mb;
int a[N], b[N];
ll ans = 0, xn = 1;
int main() {
cin >> n >> ma;
for (int i = 1; i <= ma; i++) cin >> a[i];
cin >> mb;
for (int i = 1; i <= mb; i++) cin >> b[i];
int i = ma, j = mb;
while (i > 0) {
ans += (a[i] - b[j]) * xn;
ans %= mod;
ll x = max(a[i], b[j]) + 1;
xn *= max(x, 2ll);
xn %= mod;
i--;
if (j) j--;
}
cout << ans;
return 0;
}
原题链接
给定一个 N × M N×M N×M 的矩阵 A A A,请你统计有多少个子矩阵 (最小 1 × 1 1×1 1×1,最大 N × M N×M N×M) 满足子矩阵中所有数的和不超过给定的整数 K K K?
第一行包含三个整数 N , M N,M N,M 和 K K K。
之后 N N N 行每行包含 M M M 个整数,代表矩阵 A A A。
一个整数代表答案。
对于 30 % 30\% 30% 的数据, N , M ≤ 20 N,M≤20 N,M≤20,
对于 70 % 70\% 70% 的数据, N , M ≤ 100 N,M≤100 N,M≤100,
对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 500 ; 0 ≤ A i j ≤ 1000 ; 1 ≤ K ≤ 2.5 × 108 1≤N,M≤500;0≤A_{ij}≤1000;1≤K≤2.5×108 1≤N,M≤500;0≤Aij≤1000;1≤K≤2.5×108。
3 4 10
1 2 3 4
5 6 7 8
9 10 11 12
19
满足条件的子矩阵一共有 19 19 19,包含:
使用前缀和进行暴力枚举,枚举每个点为起点,和枚举每个点为终点。时间复杂度是 O ( n 4 ) O(n^4) O(n4)。
对于 70 % 70\% 70% 的数据, N , M ≤ 100 N,M≤100 N,M≤100,大概为 1 0 8 10^8 108,可以通过;
对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 500 1≤N,M≤500 1≤N,M≤500,大概为 1 0 10 10^{10} 1010,部分会超时;
暴力大概可以获得 70 70 70% 的分数。
#include
using namespace std;
long long a[502][502];
int main()
{
int n, m, k;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
a[i][j] += a[i-1][j];
}
}
long long ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
long long s = a[i][j], row = i, col = j, temp = a[i-1][j];
while (s - temp <= k && row <= n)
{
while (s - temp <= k && col <= m)
{
ans++;
col++;
s += a[row][col];
temp += a[i-1][col];
}
col = j;
row++;
temp = a[i-1][j];
s = a[row][j];
}
}
}
cout << ans;
return 0;
}
从枚举每个点转换为枚举每一列,以每一列为起点和终点,使用双指针滑动窗口思维;
用 l e f t left left 表示左边界,从 1 1 1 开始, r i g h t right right 表示右边界,也从 1 1 1 开始,子矩阵和为 s s s;
当 s ≤ k s≤k s≤k 时,子矩阵的个数为 r i g h t − l e f t + 1 right - left + 1 right−left+1 个矩阵;
当 s > k s>k s>k 时, l e f t left left 就向右移动,一直移动到满足 s ≤ k s≤k s≤k 的位置,当 l e f t left left 走到 m m m 点时循环结束。
对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 500 1≤N,M≤500 1≤N,M≤500,大概为 1 0 8 10^8 108,可以全部通过。
#include
using namespace std;
long long a[502][502];
int main()
{
int n, m, k;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
a[i][j] += a[i-1][j];
}
}
long long ans = 0;
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
long long left = 1, right = 1, s = 0;
while (right <= m)
{
s += a[j][right] - a[i-1][right];
while (s > k)
{
s -= a[j][left] - a[i-1][left];
left++;
}
ans += right++ - left + 1;
}
}
}
cout << ans;
return 0;
}
原题链接
小明最近迷上了积木画,有这么两种类型的积木,分别为 I I I 型(大小为 2 2 2 个单位面积)和 L L L 型(大小为 3 3 3 个单位面积):
同时,小明有一块面积大小为 2 × N 2×N 2×N 的画布,画布由 2 × N 2×N 2×N 个 1 × 1 1×1 1×1 区域构成。
小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?
积木可以任意旋转,且画布的方向固定。
输入一个整数 N N N,表示画布大小。
输出一个整数表示答案。
由于答案可能很大,所以输出其对 1000000007 1000000007 1000000007 取模后的值。
1 ≤ N ≤ 1 0 7 1≤N≤10^7 1≤N≤107。
3
5
要摆出 n n n 列方格,可以在 n − 1 n - 1 n−1 ~ n n n 列方格后加一个竖着的 I I I 型积木,也可以在 n − 2 n - 2 n−2 ~ n n n 列方格后加两个横着的 I I I 型积木,也可以在 n − 3 n - 3 n−3 ~ n n n 列方格上后加两个 L L L 型积木的两种摆法, 也可以在 n − 4 n - 4 n−4 ~ n n n 列方格上两边加两个 L L L 型积木中间加一个横着的 I I I 型的两种摆法,也可以在 n − 5 n - 5 n−5 ~ n n n 列方格上两边加两个 L L L 型积木中间加两个个横着的 I I I 型的两种摆法,如图:
以此类推,可得 1 1 1 ~ n n n 列方格的摆法 d p [ n ] dp[n] dp[n] 的方程为:
d p [ n ] = d p [ n − 1 ] + d p [ n − 2 ] + d p [ n − 3 ] ⋅ 2 + d p [ n − 4 ] ⋅ 2 + d p [ n − 5 ] ⋅ 2 + ⋅ ⋅ ⋅ + d p [ 1 ] ⋅ 2 dp[n] = dp[n - 1] + dp[n - 2] + dp[n - 3] · 2 + dp[n - 4] · 2 + dp[n - 5] · 2 + ··· + dp[1] · 2 dp[n]=dp[n−1]+dp[n−2]+dp[n−3]⋅2+dp[n−4]⋅2+dp[n−5]⋅2+⋅⋅⋅+dp[1]⋅2
d p [ n − 1 ] = d p [ n − 2 ] + d p [ n − 3 ] + d p [ n − 4 ] ⋅ 2 + d p [ n − 4 ] ⋅ 2 + d p [ n − 5 ] ⋅ 2 + ⋅ ⋅ ⋅ + d p [ 1 ] ⋅ 2 dp[n - 1] = dp[n - 2] + dp[n - 3] + dp[n - 4] · 2 + dp[n - 4] · 2 + dp[n - 5] · 2 + ··· + dp[1] · 2 dp[n−1]=dp[n−2]+dp[n−3]+dp[n−4]⋅2+dp[n−4]⋅2+dp[n−5]⋅2+⋅⋅⋅+dp[1]⋅2
两式相减得: d p [ n ] = d p [ n − 1 ] ⋅ 2 + d p [ n − 3 ] dp[n] = dp[n-1] · 2 + dp[n-3] dp[n]=dp[n−1]⋅2+dp[n−3]
#include
using namespace std;
typedef long long ll;
#define N 10000010
#define mod 1000000007
int main()
{
ll *a, n;
a = new ll[N];
cin >> n;
a[1] = 1;
a[2] = 2;
a[3] = 5;
for (int i = 4; i <= n; i++)
a[i] = (a[i-1]*2 + a[i-3])%mod;
cout << a[n];
return 0;
}
原题链接
话说大诗人李白,一生好饮。
幸好他从不开车。
一天,他提着酒壶,从家里出来,酒壶中有酒 2 斗。
他边走边唱:
无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。
这一路上,他一共遇到店 N N N 次,遇到花 M M M 次。
已知最后一次遇到的是花,他正好把酒喝光了。
请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?
注意:壶里没酒 ( 0 0 0 斗) 时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。
第一行包含两个整数 N N N 和 M M M。
输出一个整数表示答案。由于答案可能很大,输出模 1000000007 1000000007 1000000007 的结果。
对于 40 % 40\% 40% 的评测用例: 1 ≤ N , M ≤ 10 1≤N,M≤10 1≤N,M≤10。
对于 100 % 100\% 100% 的评测用例: 1 ≤ N , M ≤ 100 1≤N,M≤100 1≤N,M≤100。
5 10
14
如果我们用 0 0 0 代表遇到花, 1 1 1 代表遇到店, 14 14 14 种顺序如下:
010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100
本题最易想到的就是深搜,枚举所有,用几种规则进行剪枝,排除所有不合法的情况。
用 k k k 表示酒的量;
已知:最后一次遇到的必须是花,并且没酒时遇花是不合法的;
当 n = 0 n=0 n=0 时,只有 k = m k=m k=m的时候有解,否则无解;
当 k = 0 k=0 k=0 时,只有 m = n = 0 m=n=0 m=n=0 的时候有解,否则无解;
当 k ≠ 0 k≠0 k=0 且 n ≥ m n≥m n≥m ,或者 k > m k~>~m k > m 时,无解;
单纯的深搜时间复杂度为 O ( 2 n ) O(2^n) O(2n),当 n ≥ 30 n≥30 n≥30 时就容易超时,只能拿部分分。
#include
using namespace std;
typedef long long ll;
#define mod 1000000007
ll dfs(int n, int m, int k) {
if (n == 0) return (k == m);
if (k == 0) return (n == 0 && m == 0);
if (n >= m || k > m) return 0;
ll ans = dfs(n, m-1, k-1) + dfs(n-1, m, k*2);
return ans % mod;
}
int main() {
ll ans;
int n, m;
cin >> n >> m;
ans = dfs(n, m, 2);
cout << ans;
return 0;
}
使用 d p [ n ] [ m ] [ k ] dp[n][m][k] dp[n][m][k] 表示遇到 n n n 次店, m m m 次花,剩下 k k k 斗酒,记录此时可能种数;
因为 n , m ≤ 100 n,m ≤ 100 n,m≤100,所以当 k > 100 k > 100 k>100 时,不合法,故给 d p dp dp 空间为 107 ⋅ 107 ⋅ 107 107·107·107 107⋅107⋅107。
#include
using namespace std;
typedef long long ll;
#define mod 1000000007
#define N 107
ll dp[N][N][N];
ll dfs(int n, int m, int k) {
if (n == 0) return (k == m);
if (k == 0) return (n == 0 && m == 0);
if (n >= m || k > m) return 0;
if (dp[n][m][k]) return dp[n][m][k];
ll ans = dfs(n, m-1, k-1) + dfs(n-1, m, k*2);
dp[n][m][k] = ans;
return ans % mod;
}
int main() {
ll ans;
int n, m;
cin >> n >> m;
ans = dfs(n, m, 2);
cout << ans;
return 0;
}
原题链接
这天,小明在砍竹子,他面前有 n n n 棵竹子排成一排,一开始第 i i i 棵竹子的高度为 h i h_i hi。
他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。
魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为 H H H,那么使用一次魔法可以把这一段竹子的高度都变为 ⌊ ⌊ H 2 ⌋ + 1 ⌋ ⌊\sqrt{⌊\frac{H}{2}⌋+1}⌋ ⌊⌊2H⌋+1⌋,其中 ⌊ x ⌋ ⌊x⌋ ⌊x⌋ 表示对 x x x 向下取整。
小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为 1 1 1。
第一行为一个正整数 n n n,表示竹子的棵数。
第二行共 n n n 个空格分开的正整数 h i h_i hi,表示每棵竹子的高度。
一个整数表示答案。
对于 20 % 20\% 20% 的数据,保证 1 ≤ n ≤ 1000 , 1 ≤ h i ≤ 1 0 6 1≤n≤1000,1≤h_i≤10^6 1≤n≤1000,1≤hi≤106。
对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ h i ≤ 1 0 18 1≤n≤2×10^5,1≤h_i≤10^{18} 1≤n≤2×105,1≤hi≤1018。
6
2 1 4 2 6 7
5
其中一种方案:
2 1 4 2 6 7
→ 2 1 4 2 6 2
→ 2 1 4 2 2 2
→ 2 1 1 2 2 2
→ 1 1 1 2 2 2
→ 1 1 1 1 1 1
共需要 5 5 5 步完成。
从第 1 1 1 颗竹子一直砍到第 n n n 颗,并记录每颗竹子被砍过程中所出现的所有情况,直到高度为 1 1 1,并记录次数;
当第 i i i 颗竹子被砍的过程中高度 x x x,存在于第 i − 1 i-1 i−1 颗竹子被砍的过程中,则不计数。
注意: 高度最大值为 1 0 18 10^{18} 1018,开方的时候使用 s q r t l ( ) sqrtl() sqrtl() 函数。
#include
using namespace std;
typedef long long ll;
#define N 200007
ll ans;
vector vec[N];
ll doIt(ll x) {
// 数据值过大使用 sqrtl;
return sqrtl(x / 2 + 1);
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
// 记录第 i 颗竹子高度;
ll x;
cin >> x;
// 砍第 i 颗竹子;
while (x > 1) {
// 判断第 i 颗竹子砍的过程中,是否与第 i-1 颗存在公共值,默认为 false;
bool flag = false;
for (auto it : vec[i-1])
// 第 i-1 颗竹子被砍的过程中存在 x 时,flag = true;
if (it == x)
flag = true;
// 不存在公共值次数加一;
if (!flag) ans++;
// 记录第 i 颗被砍过程中存在的高度;
vec[i].push_back(x);
// 砍竹子;
x = doIt(x);
}
}
cout << ans;
return 0;
}