/*A Number Puzzle
Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1095 Accepted Submission(s): 331
Problem Description
Lele 最近上课的时候都很无聊,所以他发明了一个数字游戏来打发时间。
这个游戏是这样的,首先,他拿出几张纸片,分别写上0到9之间的任意数字(可重复写某个数字),然后,他叫同学随便写两个数字X和K。
Lele要做的事情就是重新拼这些纸牌,组成数字 T ,并且 T + X 是 K 的正整数倍。
有时候,当纸片很多的时候,Lele经常不能在一节课之内拼出来,但是他又想知道答案,所以,他想请你帮忙写一个程序来计算答案。
Input
本题目包含多组测试数据,请处理到文件结束。
每组数据第一行包含两个整数 N和M(0<N<9,0<M<2000),分别代表纸片的数目和询问的数目。
第二行包含N个整数分别代表纸片上写的数字,每个数字可能取0~9。
接下来有M行询问,每个询问给出两个整数X和K(0<=x<10^9,0<K<100)。
注意:在拼纸片的时候,每张纸片都必须用上,且T首位不能为0
Output
对于每次询问,如果能够用这些纸片拼出符合答案的T,就输出结果T。如果有多个结果,就输出符合要求的最小的T。
如果不能拼出,就输出"None"。
Sample Input
4 3
1 2 3 4
5 7
33 6
12 8
Sample Output
1234
None
1324
Author
linle
*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int num[11], n, m, vis[11], cnt, a, b;
int ok, f[70000], ans, dp[101][101];
int cmp(const void *a, const void*b)
{
return *(int *)a - *(int *)b;
}
void dfs()
{
if(cnt == n)//如果所有纸片都用上了
{
f[ok++] = ans;//保存进数组
return ;
}
int k = -1;// 初始化 表示k无意义
if(ans == 0)//如果答案为0 k设为0防止第一位ans第一位出现0
k = 0;
for(int i = 0; i < n; ++i)
{
if(! vis[i] && num[i] != k)//如果当前位可用过并且 不是前一轮所用的数字 (相当于去重)
{
ans = ans*10 + num[i];//更新当前值
vis[i] = 1;//标记为不可用
cnt++;//纸片数加1
dfs();//继续寻找下一位
vis[i] = 0;//回溯当前位标记为可用
cnt--;//纸片数减1
k = num[i];//标记为当前轮已用数字
ans /= 10;//返回原值
}
}
}
int main()
{
int i, j, k;
while( scanf("%d%d", &n, &m) != EOF)
{
for(i = 0; i < n; ++i)
scanf("%d", &num[i]);//保存输入的数
qsort(num, n, sizeof(num[0]), cmp);//从小到大排序
ok = 0;//记录组合数 的 个数
memset(vis, 0, sizeof(vis));//标记是否用过
cnt = 0;// 记录纸片的张数
ans = 0;//组合数
dfs();//预处理所有排列数
int len = ok;
//预处理所有取余后的答案
memset(dp, -1, sizeof(dp));//dp【i】【j】表示为 某个数对i取余为j的最小数
for(i = 0; i < len; ++i)
{
for(j = 1; j <= 100; ++j)
{
k = f[i] % j;//计算第i个组合数对j取余后的余数
if(dp[j][k] == -1 || dp[j][k] > f[i])//如果该dp第一次出现或者是保存的值比第i个数大
dp[j][k] = f[i];//更新为较小的值
}
}
for(i = 0; i < m; ++i)
{
scanf("%d%d", &a, &b);
if(b == 1)//如果取余对象为1,则余数必定为0
{
printf("%d\n", dp[1][0]);
continue;
}
a %= b;//先对b取余
a = b-a;//再取互补的数
if(a == b)//如果a == b 说明余数还是0
a = 0;
if(dp[b][a] != -1)//如果对b取余后余数为a的值存在
printf("%d\n", dp[b][a]);
else
printf("None\n");
}
}
return 0;
}
题意:题意是中文的,我就不翻译了。
思路:这里说明一下思路吧,常规的暴力很容易超时,这里主要要注意k的范围,也就是对k取余这个数,可以进行预处理,其次就是要理解(ans+x)% k == 0的反面意思。
如果单独把ans和x分别对k取余,取余之后要求他们的和为k, 所以相当于说ans对k取余后为x对k取余后的互补值。这里要注意的是如果互补值为k,相当于说ans要为k的整数倍。其次就是在找排列组合数的时候,由于会有重复的数,所以可以记录一下当前位的前一位的数来进行去重,当搜索到了尽头返回时,如果说返回到某一位要对下一位进行遍历,而下一位的值正好和当前位一样,则可以想象继续遍历下去的值和当前位已经走到尽头的值是一样的,进行去重后可以优化时间。当找到所有的排列组合数以后,就可以对所有排列组合数取余后的值进行保存,保存为dp【i】【j】,表示对i取余后余数为j的数,题目要求多个值得时候保存最小的,所以选最小的值即可。最后就是对m组输入进行输出了,只要查找dp数组即可输出答案。
难点:要灵活的应对取余的优化,其次要注意的就是对小数据的预处理来节省大输入的时间。