topic
Link: bajdcc/ACM
describe
在所有的N位数中,有多少个数中有偶数个数字3?结果模12345。(1<=N<=10000)
sample input
2
Sample output
73
Method 1: Exhaustive
Evaluation: The easiest and least efficient method.
Defect: When N is very large, the i used for traversal cannot be placed in long long, gg. But first, you have to be patient until long long overflows. It doesn't take time, it's too slow.
#include <iostream> using namespace std; #define LL long long #define NUM 3 int main() { LL m,n,i,j,t,count; cin>>n; for (i=0,m=1;i<n;i++) m*=10; // Find the upper bound of N digits for (i=m/10,count=0;i<m;i++) { // From 10..000 ~ 99..999 for (j=0,t=i;t;t/=10) // take each bit if (t%10==NUM) j++; // count if NUM j plus one if (j%2==0) { count++; // even NUM count count plus one count%=12345; } } cout<<count; return 0; }
Method 2: Recursion
The exhaustive method has an inherent flaw: the traversal range of i is limited, which can be avoided unless high precision is used.
After further thinking, change the question to "How many numbers have an even number of 4", and the result is recorded as N4. Then I think N4 should be the same as N3, symmetry. Proof: Corresponding to the number of even number 3 in each number, I can find the corresponding number, as long as the 3 and 4 in the original number are adjusted down, for example, 133242, the adjustment becomes 144232, which is eu. Of course, thinking of this conclusion, we can only prove that N1~N9 are equal at present. As it should be, if we know the sum of N1~N9, then we can get the result by averaging. However, if you are still at a loss, think about it with recursion.
If there is currently a number 6XXXXX, how many eligible numbers starting with 6 are there? Well, ignore 6 and get f(6XXXXX)=f(XXXXX), because 6 doesn't need to be included at all, oh! We found an important conclusion: some subproblems are duplicates! So the reason the brainless exhaustive method is so slow is that it computes repeated subproblems. Well, now to find out which sub-problems are duplicates.
Assume the functions f(n) and g(n), where n is the number of digits, f represents the total number of even 3s, and g represents the total number of odd 3s. Starting from a single digit, 0 is not counted, f(1)=8, g(1)=1, just check whether there is 3 or not.
Now it is an N-digit XY. Think about it, if Y has an odd number of 3s and X has an odd number of 3s, then the f function is eutectic; if Y has an even number of 3s and X has an even number of 3s, then the f function is euclidean. If Y has an odd number of 3s and X has an even number of 3s, then the g function is Euclidean; if Y has an even number of 3s and X has an odd number of 3s, then the g function is Euclidean. Finally, we set X as the highest digit and Y as the last N-1 digits for recursion. In this case, X cannot be 0, which determines f(1)=8 instead of 9. In the end, 0 still needs to be considered, but it is used as the last n-1 bit, which is reflected in the multiplier 9 on the right side of the following derivation.
I have some ideas, and now write the derivation of f and g. Boundary: f(1)=8, g(1)=1. If the nth bit is 3, then add g(n-1); if the nth bit is not 3, then add 9*f(n-1), because there are 9 possibilities if it is not 3, multiplication principle.
Sort it out:
- f(1)=8,g(1)=1
- f(n)=g(n-1)+9*f(n-1)
- g(n)=f(n-1)+9*g(n-1)
Write code:
#include <iostream> using namespace std; int g(int n); int f(int n) { return n==1?8:(g(n-1)+9*f(n-1))%12345; } int g(int n) { return n==1?1:(f(n-1)+9*g(n-1))%12345; } int main() { int n; cin>>n; cout<<f(n); return 0; }
It runs significantly faster.
Method 3: Dynamic Programming
The second method still needs to be improved. The f and g functions have repeated recursive calls, of course, it can be done by memoization . Since there is a recursive formula here, the state transition equation is ready to come out, and it has been written in the second method.
#include <iostream> using namespace std; int f[10002][2]; //f[][0]=even 3, f[][1]=odd 3 int main() { int n; cin>>n; f[1][0]=8,f[1][1]=1; for (int i=2;i<=n;i++) { f[i][0]=(9*f[i-1][0]+f[i-1][1])%12345; f[i][1]=(f[i-1][0]+9*f[i-1][1])%12345; } cout<<f[n][0]; return 0; }
Method 4: Play the table method
slightly.
Method 5: Formula method
I never thought it could be done with a formula! The Fibonacci sequence also has a general term formula, but how to ask for it? (of course refer to the book)
Write code:
#include <iostream> using namespace std; #define MOD 12345 // Fast exponentiation modulo int fast( int a, int N, int mod) { long long r = 1, aa=a; while (N) { //To take the binary bits of N, multiply one by the corresponding exponentiation and combine remainder if (N & 1) r = (r * aa) % mod; N >>= 1; aa = (aa * aa) % mod; } return (int)r; } // Fast exponentiation modulo (base 2) int fast2( int N, int mod) { static long long a=(1LL<<62)%mod; int s=N%62,t=N/62; // 2^N=2^s*a^t int r = (1LL<<s) % mod; if (t>0) { r *= fast(a,t,mod);// 2^s*a^t % mod r %= mod; } return (int)r; } int main() { int n; cin>>n; //化简: // an=1/2*{7*2^(3n-3)+9*2^(n-1)*5^(n-1)} // an=2^(n-2)*{9*5^(n-1)+7*2^(2n-2)} int a=fast2(n-2,MOD); int b=a<<1; int ans=a*(9*fast(5,n-1,MOD)+7*((b*b)%MOD)); ans% = MOD; cout<<ans<<endl; return 0; }
It can be seen that for optimization, the code does not look very beautiful. If the title does not require precise values, then using floating point numbers and pow I think it should be able to make the speed a little faster.
In comparison, the dynamic programming method is actually the most concise and efficient .
Summarize
A topic, multiple methods, in fact, in essence, doing it with the thinking of a computer is naturally DP, and doing it with the thinking of a mathematician is to deduce the formula of the general term. However, there are powers in the general term formula, making it inherently inefficient for a computer to do it.
From the perspective of multi-threading optimization, the essence of the DP method is a layer-by-layer recursive calculation. The latter depends on the former. The calculation is not independent and cannot be decomposed into small tasks. The fastest is O(n). The essence of the formula method is exponentiation, and exponentiation also has dependencies, and the sub-problems are the same, so there is no need to divide. The exhaustive method can guarantee the independence of the subtasks, but the amount of calculation is still large, if and only if there is no other good method.
Formula derivation is complex and time-consuming, so dynamic programming is excellent.