Hduoj1755【数学】

/*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数组即可输出答案。

难点:要灵活的应对取余的优化,其次要注意的就是对小数据的预处理来节省大输入的时间。

你可能感兴趣的:(Hduoj1755【数学】)