文章目录
题目描述
由于计划经济失败,津巴布韦称为世界上通胀率最高的国家。这里的物价即使在一天中也会持续上涨,所以必须实时更新物品价格。例如:1个鸡蛋的价格为35亿津巴布韦元,所以超市做了每位数字的活动标价牌。
钟旭在穆加贝超市打工,有一天遇到了一位比较麻烦的客人。这位客人要退回刚才买走的鸡蛋,但是他不仅丢失了发票,而且连购买鸡蛋的数量也记不清了。鸡蛋价格已经在此期间上涨了1次,所以广告牌上已经写上新的价格。辛亏钟旭还记得如下两件事情。
1)最近一次价格上涨的时候,钟旭只是交换了塑料板的顺序。也就是说,没有添加其他塑料板,也没有去掉过广告牌中的塑料板。
2)看到最近一次上涨的价格时,钟旭心里曾经想过,“哇,这些钱刚好能购买m个糖果”。所以,最后的鸡蛋价格是m的倍数。(因为糖果的价格已经上涨,所以不能计算出鸡蛋的价格了)。
样例
输入要求:
第一行输入测试用例的个数C(C<=50)。之后的C行里面每行输入两个自然数e和m(1<=e<=1014,2<=m<=20)。当前鸡蛋的价格不能以0开始,但是之前的价格可以以0开始。
输出要求:
每个测试用例在1行内输出可能的价格个数除以1 000 000 007的余数。
示例输入值:
4
321 3
123 3
422 2
12738173912 7
示例输出值
5
0
2
11033
示例输入输出值的说明:
第一个示例输入值:以前鸡蛋的价格可能是123元、132元、213元、231元、312元。
第二个示例输入值:无论怎样重新排列123元的数字,结果都会比123元大,故无解。
第三个示例输入值:224元和242元是可能的价格。
第四个示例输入值:鸡蛋简直太贵了。
简单分析这是一个包含重复元素的全排列问题由于数据量过庞大需要进行一定的优化于是正文开始了
项目总体设计
方案一:
1)void dfs(int u)
深度优先搜索生成全排列对象并根据额外条件进行剪枝。
方案二:
1)void permutation(char* s, int cur, int len)
递归生成全排列并剪枝;
2)bool check(char* s, int cur, int i)
检验单个字符是否出现过以避免生成结果的重复情况。
方案三:
1)void my_next_permutation(int l, int r)
用于根据字典序求出下一个全排列对象;
2)bool check()
用于检验是否已生成字典序最大的全排列,若返回值为true
则跳出循环;
3)void quick_sort(int q[], int l, int r)
将输入数据按位存储到数组arr
中后,按数值大小进行排序,便于后续生成全排列;
4)LL merge(int a[N], int cnt)
将生成的全排列对象合并为 long long
类型数据便于检验是否符合额外要求。
方案一:基于DFS的递归算法
思路图:
DFS递归思想
-
dfs
就是一条路走到头,当无法再往下走时就往上退一步,如图1所示,再看有没有路可以走,如果还没有路的话就再回退一步,重复这个步骤,直到找到可以走的道路。 -
递归的主要思想在于不断调用本身的函数,层层深入,直到遇到递归终止条件后层层回溯,其思想与
dfs
基本吻合,从而调用递归实现dfs
。
时间复杂度: O(n*n!)
空间复杂度: O(n)
主旨呈现:
1.用path
数组保存排列,当排列的长度为n时,实现一种排序,ans
进行一次自增操作。
2.用bool
数组st
表示数字是否用过,st[i]
返回true
时,表示i已用过,跳过本次,st[i]
返回false
时,i
没有被用过,填入path
数组进行回溯。
3.dfs(u)
表示的含义是:在path[u]
处填写数字,即进行第u
层的回溯。
具体代码:
1.1 数字类型的实现方法:
#pragma warning(disable : 4996)
#include <string.h>
#include <stdio.h>
typedef long long LL;
const int N = 20;
int m, n, cnt;
LL e, ans, res[50];
int num[N], path[N];
bool st[N];
LL merge(int a[N], int cnt)
{
LL sum = 0;
for (int i = 0; i < cnt; i++)
{
sum = 10 * sum + a[i];
}
return sum;
}
void myswap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) myswap(&q[i], &q[j]);
else break;
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
void dfs(int u)
{
if (u == cnt)
{
if (merge(path, cnt) % m == 0 && merge(path, cnt) < e) //满足字典序,并且整除m
{
ans++;
//printf("%lld\n", merge(path,cnt));
return;
}
}
for (int i = 0; i < cnt; i++)
if (!st[i])
{
path[u] = num[i];
st[i] = true;
dfs(u + 1);
st[i] = false;
while (i + 1 < cnt && num[i] == num[i + 1])
i++;
}
}
int main()
{
printf("请输入测试样例组数n(n <= 50):");
scanf("%d", &n);
if (n > 50)
{
printf("测试样例过多!\n");
return 0;
}
printf("开始输入样例:\n");
for (int i = 0; i < n; i++)
{
scanf("%lld%d", &e, &m);
memset(num, 0, sizeof num);
if (e == 0 || m < 2 || m > 20)
{
res[i] = -1;
continue;
}
LL tmp = e;
cnt = 0;
while (tmp)
{
num[cnt++] = tmp % 10;
tmp /= 10;
}
quick_sort(num, 0, cnt - 1);
ans = 0;
dfs(0);
res[i] = ans % 1000000007;
}
printf("*********************\n OUTPUT:\n*********************\n");
for (int i = 0; i < n; i++)
if (res[i] == -1)
printf("该组数据不合法!\n");
else
printf("%lld\n", res[i]);
return 0;
}
1.2. 字符串类型的实现方法:
#pragma warning(disable : 4996)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef long long LL;
const int N = 20;
char path[N], pre_str[N];
LL cnt;
int n, m;
void permutation(char* str, int k, int end)
{
char tmp;
if (k == end)
{
if (strcmp(path, pre_str) < 0 && atoll(path) % m == 0)
{
cnt++;
//printf("%s\n",path);
}
return;
}
for (int i = 0; i < end; i++)
{
while (str[i] == '#')
i++;
for (int j = 0; j < i; j++)
{
if (str[i] == '#' || str[j] == str[i])
{
i++;
j = 0;
}
if (i == end)
return;
}
path[k] = str[i];
tmp = str[i];
str[i] = '#';
permutation(str, k + 1, end);
str[i] = tmp;
}
}
int main()
{
char str[N];
scanf("%d", &n);
LL ans[50];
for (int i = 0; i < n; i++)
{
scanf("%s%d", str, &m);
strcpy(pre_str, str);
permutation(str, 0, strlen(str));
ans[i] = cnt % 1000000007;
cnt = 0;
}
for (int i = 0; i < n; i++)
printf("%lld\n", ans[i]);
return 0;
}
方案二:基于交换的递归算法(减治思想)
手写next_permutation
思路图:
基本思想:
如图2所示,求n
个元素的全排列可以先从n
个元素中选一个作为首元素,然后排列剩下元素的全排列,其中一个元素的全排列为它本身。然后分别另每个元素作为首元素进行递归即可。
例如 如下表述:
Perm({a, b, c} = {a}.Perm({b, c}) + {b}.Perm({a, c}) + {c}.Perm({a, b});
但是要注意考虑元素重复会导致生成的排列重复的情况,改进的方法是在每次选取元素作为首元素时,使用check
函数判断这个元素是否已经取过,从而避免重复情况。
通过递归的思路分析可以得知时间复杂度最低为O(n!)
,空间复杂度为O(n^2)
。
具体代码:
#pragma warning(disable : 4996)
#include <stdio.h>
#include <string.h>
typedef long long LL;
const int N = 20;
int arr[N], m, cnt;
LL e, ans, res[50];;
void myswap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
LL merge(int a[N], int cnt)
{
LL sum = 0;
for (int i = 0; i < cnt; i++)
{
sum = 10 * sum + a[i];
}
return sum;
}
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) myswap(&q[i], &q[j]);
else break;
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
void my_next_permutation(int l, int r)
{
int tmp;
for (int i = r - 1; i >= l; i--)
if (arr[i] < arr[i + 1])
{
tmp = i;
break;
}
for (int i = r; i > tmp; i--)
if (arr[i] > arr[tmp])
{
myswap(&arr[i], &arr[tmp]);
break;
}
quick_sort(arr, tmp + 1, r);
}
bool check() //降序序列则返回true
{
for (int i = 0; i < cnt; i++)
for (int j = i; j < cnt; j++)
if (arr[i] < arr[j])
return false;
return true;
}
int main()
{
printf("请输入测试样例组数n(n <= 50):");
int n;
scanf("%d", &n);
if (n > 50)
{
printf("测试样例过多!\n");
return 0;
}
printf("开始输入样例:\n");
for (int i = 0; i < n; i++)
{
scanf("%lld%d", &e, &m);
memset(arr, 0, sizeof arr);
if (e == 0 || m < 2 || m > 20)
{
res[i] = -1;
continue;
}
LL tmp = e;
cnt = 0;
while (tmp)
{
arr[cnt++] = tmp % 10;
tmp /= 10;
}
quick_sort(arr, 0, cnt - 1);
ans = 0;
while (!check())
{
if (merge(arr, cnt) < e && merge(arr, cnt) % m == 0)
ans++;
my_next_permutation(0, cnt - 1);
/*for (int i = 0; i < cnt; i++)
printf("%d ", arr[i]);
puts("");*/
}
res[i] = ans % 1000000007;
}
printf("*********************\n OUTPUT:\n*********************\n");
for (int i = 0; i < n; i++)
if (res[i] == -1)
printf("该组数据不合法!\n");
else
printf("%d\n", res[i]);
return 0;
}
方案三:基于字典序的非递归实现
基本思路:
给定一个初始排列,通过字典序的转换规则不断得到它的下一个排列,如果初始排列为升序的有序排列,最终便可得到全排列。
每次生成全排列的时间复杂度为O(N)
。
具体实现:
- 从右到左扫描集合,找到第一个比右边相邻数字小的数字的序号
tmp
; - 在
tmp
右边的数字中从右到左找到第一个大于a[tmp]
的数字a[i]
(因为右侧的数字从右到左是递增的); - 对换
a[tmp]
和a[i]
; - 再将
a[tmp]
右侧的数字倒转得到排列,这就是排列a的下一个排列。
具体见可视化演示。
具体代码:
#pragma warning(disable : 4996)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef long long LL;
const int N = 20;
char pre_str[N];
LL cnt;
int n, m;
void myswap(char* x, char* y)
{
char tmp = *x;
*x = *y;
*y = tmp;
}
bool check(char* s, int cur, int i)
{
for (int k = cur; k < i; ++k)
if (s[i] == s[k])
return false;
return true;
}
void permutation(char* s, int cur, int len)
{
if (cur == len - 1)
{
//printf("%s\n", s);
if (atoll(s) % m == 0 && atoll(s) < atoll(pre_str))
cnt++;
}
for (int i = cur; i < len; ++i)
{
if (check(s, cur, i))
{
myswap(&s[i], &s[cur]);
permutation(s, cur + 1, len);
myswap(&s[i], &s[cur]);
}
}
}
int main()
{
printf("请输入测试样例组数n(n <= 50):");
char str[N];
scanf("%d", &n);
if (n > 50)
{
printf("测试样例过多!\n");
return 0;
}
LL ans[50];
printf("开始输入样例:\n");
for (int i = 0; i < n; i++)
{
cnt = 0;
scanf("%s%d", str, &m);
if (m < 2 || m >20)
{
ans[i] = -1;
continue;
}
for (int j = 0; str[j]; j++)
if (str[j] < '0' || str[j] > '9')
ans[i] = -1;
if (ans[i] == -1)
continue;
strcpy(pre_str, str);
permutation(str, 0, strlen(str));
ans[i] = cnt % 1000000007;
}
printf("*********************\n OUTPUT:\n*********************\n");
for (int i = 0; i < n; i++)
{
if (ans[i] == -1)
printf("该组数据不合法!\n");
else
printf("%lld\n", ans[i]);
}
return 0;
}
由于老师要求有了下面部分
基于easyx的可视化实现
#pragma warning(disable : 4996)
#include <stdio.h>
#include <stdlib.h>
#include <graphics.h>
#include <conio.h>
#include <Windows.h>
#include <math.h>
const int N = 10;
int r1[] = { 830,340,930,380 };
int r2[] = { 830,390,930,430 };
int r3[] = { 30,340,145,380 };
int r4[] = { 30,390,145,430 };
void lineArrow(int x1, int y1, int x2, int y2) //绘制指针
{
line(x1, y1, x2, y2);
double distance = sqrt((y1 - y2) * (y1 - y2) + (x1 - x2) * (x1 - x2));
double tmpx = double(x1 + (x2 - x1) * (1 - (12 * sqrt(3) / 2) / distance));
double tmpy = double(y1 + (y2 - y1) * (1 - (12 * sqrt(3) / 2) / distance));
if (y1 == y2)
{
line(x2, y2, int(tmpx), int(tmpy + 6));
line(x2, y2, int(tmpx), int(tmpy - 6));
}
else
{
double k = (double(x2) - double(x1)) / (double(y1) - double(y2));
double increX = 6 / sqrt(k * k + 1);
double increY = 6 * k / sqrt(k * k + 1);
line(x2, y2, int(tmpx + increX), int(tmpy + increY));
line(x2, y2, int(tmpx - increX), int(tmpy - increY));
}
}
void myswap(int* x, int* y)
{
int k = *x;
*x = *y;
*y = k;
}
bool check(int *a,int n) //降序序列则返回true
{
for (int i = 0; i < n; i++)
for (int j = i; j < n; j++)
if (a[i] < a[j])
return false;
return true;
}
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) myswap(&q[i], &q[j]);
else break;
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
void next_per(int* a, int n) {
setfillcolor(WHITE);
fillrectangle(480 - 50 * n, 130, 480 + 50 * n, 230);
int x0 = 480 - 50 * n;
for (int i = 0; i <= n; i++)
line(x0 + i * 100, 130, x0 + i * 100, 230);
for (int i = 0; i < n; i++)
{
char s[10];
itoa(a[i], s, 10);
outtextxy(520 - 50 * n + i * 100, 160, s);
}
if (check(a, n))
{
setfillcolor(RED);
solidroundrect(360, 250, 600, 330, 20, 20);
settextstyle(80, 0, _T("Consolas"));//设置字号、字体
outtextxy(368, 250, "FINISH");
Sleep(1000);
exit(0);
}
lineArrow(430 + 50 * n, 310, 430 + 50 * n, 260);
Sleep(500);
int x = 430 - 50 * n;
int tmp;
int i;
for (i = n - 1; i >= 0; i--) {
if (a[i] < a[i + 1]) {
tmp = i;
break;
}
solidrectangle(x + 50 + 100 * i, 231, x + 50 + 100 * (i + 1), 330);
lineArrow(x + 100 * i, 310, x + 100 * i, 260);
Sleep(500);
}
lineArrow(430 + 50 * n, 310, 430 + 50 * n, 260);
for (int i = n; i > tmp; i--)
{
if (a[i] > a[tmp]) {
char s1[10], s2[10];
itoa(a[i], s1, 10), itoa(a[tmp], s2, 10);;
solidrectangle(481 - 50 * n + 100 * tmp, 131, 479 - 50 * n + 100 * (tmp + 1), 229);
outtextxy(520 - 50 * n + tmp * 100, 160, s1);
solidrectangle(481 - 50 * n + 100 * i, 131, 479 - 50 * n + 100 * (i + 1), 229);
outtextxy(520 - 50 * n + i * 100, 160, s2);
myswap(&a[tmp], &a[i]);
break;
}
solidrectangle(x + 50 + 100 * i, 231, x + 50 + 100 * (i + 1), 320);
lineArrow(x + 100 * i, 310, x + 100 * i, 260);
Sleep(500);
}
Sleep(500);
quick_sort(a, tmp + 1, n - 1);
int nn = tmp + n;
for (int i = tmp + 1; i < n; i++)
{
char s[10];
itoa(a[i], s, 10);
solidrectangle(481 - 50 * n + 100 * (i), 131, 479 - 50 * n + 100 * (i + 1), 229);
outtextxy(520 - 50 * n + (i) * 100, 160, s);
Sleep(500);
}
setfillcolor(WHITE);
solidrectangle(480 - 50 * n, 231, 480 + 50 * n, 339);
}
void show(int* a, int n) {
int k = 0;
if (!k) {
initgraph(960, 480);
setbkcolor(WHITE);
cleardevice();
setlinecolor(BLACK);
settextcolor(BLACK);
setbkmode(TRANSPARENT);
settextstyle(40, 20, _T("Consolas"));
k++;
}
setfillcolor(RGB(128, 128, 135)); //灰色
solidroundrect(r1[0], r1[1], r1[2], r1[3], 20, 20);
outtextxy(r1[0] + 10, r1[1], "next");
solidroundrect(r2[0], r2[1], r2[2], r2[3], 20, 20);
outtextxy(r2[0] + 10, r2[1], "quit");
solidroundrect(r3[0], r3[1], r3[2], r3[3], 20, 20);
outtextxy(r3[0] + 10, r3[1], "start");
solidroundrect(r4[0], r4[1], r4[2], r4[3], 20, 20);
outtextxy(r4[0] + 10, r4[1], "pause");
setfillcolor(WHITE);
fillrectangle(480 - 50 * n, 130, 480 + 50 * n, 230);
int x0 = 480 - 50 * n;
for (int i = 0; i <= n; i++)
line(x0 + i * 100, 130, x0 + i * 100, 230);
for (int i = 0; i < n; i++)
{
char s[10];
itoa(a[i], s, 10);
outtextxy(520 - 50 * n + i * 100, 160, s);
}
//Sleep(5000);
MOUSEMSG m;
while (1) {
m = GetMouseMsg();
if (m.x > r1[0] && m.x <r1[2] && m.y >r1[1] && m.y < r1[3]) {
if (m.uMsg == WM_LBUTTONDOWN)
{
next_per(a, n);
for (int i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n*****************\n");
}
}
if (m.x > r2[0] && m.x <r2[2] && m.y >r2[1] && m.y < r2[3]) {
if (m.uMsg == WM_LBUTTONDOWN)
break;
}
if (m.x > r3[0] && m.x <r3[2] && m.y >r3[1] && m.y < r3[3]) {
if (m.uMsg == WM_LBUTTONDOWN)
{
while (1) {
next_per(a, n);
Sleep(1000);
for (int i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n*****************\n");
if (_kbhit()) {
char userKey = _getch();
if (userKey == 0x20)
break;
}
}
}
}
if (m.x > r4[0] && m.x <r4[2] && m.y >r4[1] && m.y < r4[3]) {
if (m.uMsg == WM_LBUTTONDOWN)
continue;
}
}
}
int main() {
printf("请输入进行全排列的数据个数 n(n < 9):");
int n, a[N];
scanf("%d", &n);
printf("请输入进行全排列的n个数据:");
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
int m = 3;
printf("生成的全排列如下:\n");
show(a, n);
return 0;
}
部分测试数据
示例输入值:
4
321 3
123 3
422 2
12738173912 7
示例输出值
5
0
2
11033
input:
13
195365421597 19
569523654123 13
25658 15
249523614 7
123695265412 37
13265245 2
25954563214 3
265425 1
235 2
1 1
666 3
986 0
321 3
output:
158858
557004
0
3220
该组数据不合法!
212
0
该组数据不合法!
0
该组数据不合法!
该组数据不合法!
该组数据不合法!
5
10
579 30
376 1
321 3
abx 3
5782 0
0 7
537 a
0 0
abc acs
1 3