/*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数组即可输出答案。
难点:要灵活的应对取余的优化,其次要注意的就是对小数据的预处理来节省大输入的时间。