一个用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
There are multiple test cases, the first line is the number of test cases.
The first line of each test case 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.

Sample Input
2
4 7
17 5 -21 15
4 5
17 5 -21 15
Sample Output
Divisible
Not divisible
这道题目就是在给定的一堆数中,添加+ -号,判断一下结果是否能整除给定的K.
这道题首先给人的感觉是穷举/搜索,但是给的数在10000以内,穷举的话是2^n,最坏情况10000个数,如果不可整除,计算出来时间是相当长的.
而把指数级复杂度变为多项式级别的可以考虑动态规划.
这道题的动态规划信息非常隐蔽,因为如果直接把这些数的运算划分为子问题的话,这些子问题都将是不重复的.而动态规划是通过记住子问题的最优解来避免重复计算而提高效率的.
但仔细分析这道题,我们发现其实没有必要真正求出表达式的值,我们只要把每一步计算结果
的对k的余数记住就行了.这样每一步的计算结果就在0~K之间了.如果我们把这个计算过程表示为树形结构的话
a1
/ \
a1-a2 a1+a2
/ \ / \
a1-a2-a3 a1-a2+a3 a1+a2-a3 a1+a2+a3
...................................
这样第j层就会有2^j个计算的结果,而如果我们每次计算的时候都对k求模,这样这2^j个值
都在0~k-1之间,而如果一层中的两个节点的值一样,那么显然可以在计算第一个节点的值是否能被k整除后把这个结果保存下来,再遇到这样同一个值的可以直接查找到,事实上这样重复的节点计算是相当多的--(指数级别2^j的数在常量0~k范围).这样我们把一棵树的宽度约束在了1~k的范围了,而高度是N,所以复杂度变成了多项式级别O(k*N)了:
代码如下:
[code]
#include<iostream>
#include<string>
using namespace std;

int flag[10005][105];
int a[1005];
int n,K,N;
//@return 返回是否可以整除K,@parm level递归形成二叉数的层数,vaule为该层一个计算出节点的值
int f(int level,int value){
if(flag[level][value] != -1) return flag[level][value];//如果已经记录过了,就直接返回
int check,t1,t2;
t1 = (value + a[level]) % K;//进行+运算并对K取余数
t2 = (value - a[level]) % K;//进行-运算并对K取余数
t1 = t1 < 0 ? t1 + K : t1;//余数都改成正的
t2 = t2 < 0 ? t2 + K : t2;

if(level == N) check = (value % K == 0) ? 1 : 0;//如果第N层,递归出口,判断 value 是否可以整除K
else if(f(level + 1,t1) == 1) check = 1;//如果level + 1层计算结果可以整除K,则标记check为1,同时可以跳过另一个分支,因为0-a,0+a对最终判断否是否可以整除K的效果是一样的
else if( f(level + 1,t2) == 1 ) check = 1;
else check = 0;//左右分支均不可以整除 check=0;
return flag[level][value] = check;//返回并记录本层的check值
}

int main(){
int m;
int i,j;
string result;
cin >> n;
for(i = 0; i < n; i++){
cin >> N >> K;
for(j = 0; j < N; j++){
cin >> a[j];
if((a[j] %= K) < 0) a[j] += K;
}

for(j = 0; j <= N; j++)
for(m = 0; m <= K; m++)
flag[j][m] = -1;


result = f(1,a[0]) == 1 ? "Divisible" : "Not divisible" ;
cout << result << endl;
}
return 0;
}
[/code]
阅读更多

没有更多推荐了,返回首页