ZOJ 2042 Divisibility (DP)
Consider an arbitrary sequence of integers. One can place + or - operators between integers in the sequence, thus deriving different arithmetical expressions that evaluate to different values. Let us, for example, take the sequence: 17, 5, -21, 15. There are eight possible expressions:
17 + 5 + -21 + 15 = 16
17 + 5 + -21 - 15 = -14
17 + 5 - -21 + 15 = 58
17 + 5 - -21 - 15 = 28
17 - 5 + -21 + 15 = 6
17 - 5 + -21 - 15 = -24
17 - 5 - -21 + 15 = 48
17 - 5 - -21 - 15 = 18
We call the sequence of integers divisible by K if + or - operators can be placed between integers in the sequence in such way that resulting value is divisible by K. In the above example, the sequence is divisible by 7 (17+5+-21-15=-14) but is not divisible
by 5.
You are to write a program that will determine divisibility of sequence of integers.
Input
The first line of the input contains two integers, N and K (1 <= N <= 10000, 2 <= K <= 100) separated by a space.
The second line contains a sequence of N integers separated by spaces. Each integer is not greater than 10000 by it's absolute value.
Output
Write to the output file the word "Divisible" if given sequence of integers is divisible by K or "Not divisible" if it's not.
This problem contains multiple test cases!
The first line of a multiple input is an integer N, then a blank line followed by N input blocks. Each input block is in the format indicated in the problem description. There is a blank line between input blocks.
The output format consists of N output blocks. There is a blank line between output blocks.
Sample Input
2
4 7
17 5 -21 15
4 5
17 5 -21 15
Sample Output
Divisible
Not divisible
【问题分析】http://blog.csdn.net/tsaid/article/details/7840487(可怜原作者不晓得输出的恶心换行套路,找错绝对很头疼)
看到题目第一个反映就是枚举中间添的运算符,算出值在MOD K如果有一个值MOD K=0则输出“Divisible”。
时间复杂度是O(2N-1)。
但是题目给出的数据量很大,这样做效率太低了。
因为题目涉及MOD运算,要想简化问题就需要知道一些基本的MOD运算性质:
A*B mod C=(A mod C*B mod C) mod C
(A+B) mod C=(A mod C+B mod C) mod C
有了这个性质,我们就可以把累加后求余转化成求余后累加(我们把减法看作加负数以后分析只说加法)再求余。这样我们的读入数据就控制在了1-K到K-1的范围内了。
我们要判断的就是
所有结果的累加和 MOD K 是否为0。简记为:
(A+B)mod K=0 or (A+B) mod K<>0
如果我们按数的个数划分阶段,前N-1个数的运算结果 MOD K看做A,第N个数看作B就OK了。
于是我们想到了这样的状态:opt[i,j]表示前i个数是否可以得到余数为J的结果。
那么状态转移方程就是
opt[i,(j-a[i] mod k )modk]=opt[i-1,j] (opt[i-1,j]=true);
opt[i,(j+a[i] mod k) modk]=opt[i-1,j] (opt[i-1,j]=true);
如果opt[n,0]=true就输出‘Divisible’
我一开始想用滚动数组来解决(尽管没有这个必要)。
a[1][(x%k+k)%k]=true; for(int i=2;i<=n;i++) { cin>>x;//=_S(); for(int j=0;j<k;j++) { if(a[(i-1)%2][j]) { a[i%2][((j+x)%k+k)%k]=true; a[i%2][((j-x)%k+k)%k]=true; } } } if(a[n%2][0]==true) printf("Divisible\n"); else printf("Not divisible\n"); if(T) printf("\n");
然后一直wa,后来发现滚动数组必须每次刷新,很尴尬,反而使得效率低下。如下:
memset(a[1],false,sizeof(a[1])); x=_S(); a[1][(x%k+k)%k]=true; for(int i=2;i<=n;i++) { memset(a[i%2],false,sizeof(a[i%2])); x=_S(); for(int j=0;j<k;j++) { if(a[(i-1)%2][j]) { a[i%2][((j+x)%k+k)%k]=true; a[i%2][((j-x)%k+k)%k]=true; } } } if(a[n%2][0]) printf("Divisible\n"); else printf("Not divisible\n"); if(T) printf("\n");
就是memset这个可恶的东西,然后改了一下:
a[1][(x%k+k)%k]=1; for(int i=2;i<=n;i++) { x=_S(); for(int j=0;j<k;j++) { if(a[(i-1)%2][j]==i-1)//用唯一的数字标记,不需要更新 { a[i%2][((j+x)%k+k)%k]=i; a[i%2][((j-x)%k+k)%k]=i; } } } if(a[n%2][0]==n) printf("Divisible\n"); else printf("Not divisible\n"); if(T) printf("\n");
但是搞来搞去,好像效率还是不怎么样,不过数字代替memset的思路是挺好的。
建议赛场上还是开a[10001][101]的数组,免得乱搞麻烦。
完整代码:100ms,还算可以了
#include<iostream> #include<cstdio> #include<cstring> #include<memory.h> #include<cmath> using namespace std; int a[2][101]; int _S() { char c=getchar(); while(c<'0'||c>'9') c=getchar(); int s=0; while(c>='0'&&c<='9'){ s=s*10+c-'0'; c=getchar(); } return s; } int main() { int T,k,x,n; T=_S(); while(T--) { n=_S(); k=_S(); memset(a,0,sizeof(a)); x=_S(); a[1][x%k]=1; for(int i=2;i<=n;i++) { x=_S(); for(int j=0;j<k;j++) { if(a[(i-1)%2][j]==i-1) { a[i%2][(j+x)%k]=i; a[i%2][((j-x)%k+k)%k]=i; } } } if(a[n%2][0]==n) printf("Divisible\n"); else printf("Not divisible\n"); if(T) printf("\n"); } return 0; }