挑战程序竞赛系列(41):4.1中国剩余定理
2017-08-30 16:31
543 查看
挑战程序竞赛系列(41):4.1中国剩余定理
详细代码可以fork下Github上leetcode项目,不定期更新。练习题如下:
POJ 1006: Biorhythms
POJ 2891: Strange Way to Express Integers
POJ 3708: Recurrent Function
POJ 1006: Biorhythms
今天学习了下中国剩余定理,很多参考资料啊,推荐博文:http://blog.csdn.net/acdreamers/article/details/8050018
其他的可看可不看,简单来说,中国剩余定理是用来求解同余方程组的,比如
x≡1mod5x≡2mod6x≡3mod7
中国剩余定理指出,如果给出的每个方程模数两两互素,就能通过一种方法计算出来,具体方法可以参考《初等数论及其应用》。
在这里给出书上的证明:
该证明还是很好理解的,主要还是通过两两互素的性质构造一个联立解,最后证明联立解符合每个方程即可,给人一种凑答案的过程,此解法如何想到令人深思。
具体做法如下:
好了,有了这些知识,第一题能解了,无非把上述思想用代码实现一遍。
代码如下:
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.StringTokenizer; public class Main{ String INPUT = "./data/judge/201708/P1006.txt"; public static void main(String[] args) throws IOException { new Main().run(); } void solve() { int t = 0; while (true){ int p = ni(); int e = ni(); int i = ni(); int d = ni(); if (p == -1 && e == -1 && i == -1 && d == -1) break; int[] a = new int[3]; int[] m = new int[3]; a[0] = p; a[1] = e; a[2] = i; m[0] = 23; m[1] = 28; m[2] = 33; int ans = CRT(a, m, 3); if (ans <= d){ ans += 21252; } out.println("Case " + (++t) + ": the next triple peak occurs in " + (ans - d) + " days."); } } class Pair{ int d; int x; int y; public Pair(int d, int x, int y){ this.d = d; this.x = x; this.y = y; } } public Pair extgcd(int a, int b){ if (b == 0){ return new Pair(a, 1, 0); } else{ Pair p = extgcd(b, a % b); Pair ans = new Pair(0, 0, 0); ans.d = p.d; ans.x = p.y; ans.y = p.x - (a / b) * p.y; return ans; } } public int mod_inverse(int a, int m){ Pair p = extgcd(a, m); if (p.d != 1) return -1; return (p.x % m + m) % m; } public int CRT(int[] a, int[] m, int n){ int M = 1; int ans = 0; for (int i = 0; i < n; ++i){ M *= m[i]; } for (int i = 0; i < n; ++i){ int inv = mod_inverse(M / m[i], m[i]); ans += a[i] * M / m[i] * inv; ans %= M; } return ans; } FastScanner in; PrintWriter out; void run() throws IOException { boolean oj; try { oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode"); } catch (Exception e) { oj = System.getProperty("ONLINE_JUDGE") != null; } InputStream is = oj ? System.in : new FileInputStream(new File(INPUT)); in = new FastScanner(is); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); if (!oj){ System.out.println("[" + (System.currentTimeMillis() - s) + "ms]"); } } public boolean more(){ return in.hasNext(); } public int ni(){ return in.nextInt(); } public long nl(){ return in.nextLong(); } public double nd(){ return in.nextDouble(); } public String ns(){ return in.nextString(); } public char nc(){ return in.nextChar(); } class FastScanner { BufferedReader br; StringTokenizer st; boolean hasNext; public FastScanner(InputStream is) throws IOException { br = new BufferedReader(new InputStreamReader(is)); hasNext = true; } public String nextToken() { while (st == null || !st.hasMoreTokens()) { try { st = new StringTokenizer(br.readLine()); } catch (Exception e) { hasNext = false; return "##"; } } return st.nextToken(); } String next = null; public boolean hasNext(){ next = nextToken(); return hasNext; } public int nextInt() { if (next == null){ hasNext(); } String more = next; next = null; return Integer.parseInt(more); } public long nextLong() { if (next == null){ hasNext(); } String more = next; next = null; return Long.parseLong(more); } public double nextDouble() { if (next == null){ hasNext(); } String more = next; next = null; return Double.parseDouble(more); } public String nextString(){ if (next == null){ hasNext(); } String more = next; next = null; return more; } public char nextChar(){ if (next == null){ hasNext(); } String more = next; next = null; return more.charAt(0); } } }
POJ 2891: Strange Way to Express Integers
嘿,中国剩余定理表明同余方程组两两互素的情况下可解,那么遇到模数不互素的情况该如何?参考博文:http://www.cnblogs.com/MashiroSky/p/5918158.html
此篇讲的还是比较详细的,尤其这文字解释令人一下豁然开朗,我就不重复劳动力了。
但个人总觉得吧,这和中国剩余定理没啥关系了吧,难道这种解法也是孙子发明的?
大致思路是对所有同余方程两两合并,在合并的过程中可以求出新的一个特解和它的一个解集,解集用新的同余方程表示(这想法相当漂亮),那么该解集就能慢慢适应所有同余方程了。
最终在解集中找寻一个最小的即可,上述两篇博文有C++的具体实现,Java版本的就自己补上吧。
代码如下:
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.StringTokenizer; public class Main{ String INPUT = "./data/judge/201708/P2891.txt"; public static void main(String[] args) throws IOException { new Main().run(); } void solve() { while (more()){ int n = ni(); long[] a = new long ; long[] m = new long ; for (int i = 0; i < n; ++i){ m[i] = ni(); a[i] = ni(); } out.println(CRT(a, m, n)); } } class GCD{ long d; long x; long y; public GCD(long d, long x, long y){ this.d = d; this.x = x; this.y = y; } } public long gcd(long a, long b){ if (b == 0) return a; else return gcd(b, a % b); } public GCD extgcd(long a, long b){ if (b == 0) return new GCD(a, 1, 0); else{ GCD g = extgcd(b, a % b); GCD ans = new GCD(0, 0, 0); ans.d = g.d; ans.x = g.y; ans.y = g.x - (a / b) * g.y; return ans; } } public long mod_inverse(long a, long m){ GCD g = extgcd(a, m); if (g.d != 1) return -1; return (g.x % m + m) % m; } class LL{ long val; public LL(){} public LL(long val){ this.val = val; } @Override public String toString() { return val + ""; } } public boolean merge(long a1, long m1, long a2, long m2, LL a3, LL m3){ GCD g = extgcd(m1, m2); long c = a2 - a1; long d = g.d; if (c % d != 0) return false; m1 /= d; m2 /= d; c /= d; long inv = mod_inverse(m1, m2); c *= inv; c %= m2; c *= m1 * d; c += a1; m3.val = m1 * m2 * d; a3.val = c; return true; } public long CRT(long[] a, long[] m, int n){ long a1 = a[0]; long m1 = m[0]; for (int i = 1; i < n; ++i){ long a2 = a[i]; long m2 = m[i]; LL a3 = new LL(); LL m3 = new LL(); if (!merge(a1, m1, a2, m2, a3, m3)) return -1; a1 = a3.val; m1 = m3.val; } return (a1 % m1 + m1) % m1; } FastScanner in; PrintWriter out; void run() throws IOException { boolean oj; try { oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode"); } catch (Exception e) { oj = System.getProperty("ONLINE_JUDGE") != null; } InputStream is = oj ? System.in : new FileInputStream(new File(INPUT)); in = new FastScanner(is); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); if (!oj){ System.out.println("[" + (System.currentTimeMillis() - s) + "ms]"); } } public boolean more(){ return in.hasNext(); } public int ni(){ return in.nextInt(); } public long nl(){ return in.nextLong(); } public double nd(){ return in.nextDouble(); } public String ns(){ return in.nextString(); } public char nc(){ return in.nextChar(); } class FastScanner { BufferedReader br; StringTokenizer st; boolean hasNext; public FastScanner(InputStream is) throws IOException { br = new BufferedReader(new InputStreamReader(is)); hasNext = true; } public String nextToken() { while (st == null || !st.hasMoreTokens()) { try { st = new StringTokenizer(br.readLine()); } catch (Exception e) { hasNext = false; return "##"; } } return st.nextToken(); } String next = null; public boolean hasNext(){ next = nextToken(); return hasNext; } public int nextInt() { if (next == null){ hasNext(); } String more = next; next = null; return Integer.parseInt(more); } public long nextLong() { if (next == null){ hasNext(); } String more = next; next = null; return Long.parseLong(more); } public double nextDouble() { if (next == null){ hasNext(); } String more = next; next = null; return Double.parseDouble(more); } public String nextString(){ if (next == null){ hasNext(); } String more = next; next = null; return more; } public char nextChar(){ if (next == null){ hasNext(); } String more = next; next = null; return more.charAt(0); } } }
POJ 3708: Recurrent Function
此题对数学的要求较高,还是有一定难度的,用到的知识点也较多,简单说说我的心路历程吧。就拿式子:
f(d×n+j)=d×f(n)+bj
你能想到什么?它实际上是一种m转d进制,这是思维上的突破,想到这点后续都简单了。
我们可以把递推式化为最终的表达式如下:
f(m)=ap1⋅dn−1+∑i=2n(bpi⋅dn−i),ap1ϵ[1,d),(bpiϵ[0,d),iϵ[2,n)]
如果递推式难以理解的话,你可以想象10进制转d进制的一个过程,因为递推式其实模拟的就是这个过程,并且在转换的过程当中,余数一一对应到了bj上了。
当然你也可以参考《具体数学》P16,它也介绍了一些,此处推荐博文:
http://www.hankcs.com/program/algorithm/poj-3708-recurrent-function.html
但代码量相对冗长,比较难懂。
好了,m转成d进制,对应的值f(m)=(ap1bp2...bpn)d
所以第一步判断m和k转到d进制后,位数是否相等,不相等则无解。
接着,我们要考虑m中每一位的变化了, 因为经过一次f映射,实际上是从set中找一个数置换,而set中的值是有限个数,且在[1, d)之间,那么必然在置换过程中会存在循环的情况,那么自然的就考虑m中的每一位经过多少次能将mi置换到ki上去。
这样就可以列出一系列的同余方程了。
此处可以参考博文:
http://blog.csdn.net/adjky/article/details/60583393
思路很清楚,代码很精简。
代码如下:
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Arrays; import java.util.StringTokenizer; public class Main{ String INPUT = "./data/judge/201708/P3708.txt"; public static void main(String[] args) throws IOException { new Main().run(); } int[] A; int[] B; void solve() { while (true){ int N = ni(); if (N == -1) break; A = new int ; B = new int ; for (int i = 1; i < N; ++i){ A[i] = ni(); } for (int i = 0; i < N; ++i){ B[i] = ni(); } String m = ns(); String k = ns(); int[] nm = new int[m.length() + 16]; for (int i = m.length() - 1; i >= 0; --i) nm[i] = m.charAt(i) - '0'; int[] nk = new int[k.length() + 16]; for (int i = k.length() - 1; i >= 0; --i) nk[i] = k.charAt(i) - '0'; LEN_M = tran(nm, m.length(), M, N); LEN_K = tran(nk, k.length(), K, N); //radix(m, k, N); if (LEN_M != LEN_K) out.println("NO"); else{ getRC(LEN_M); boolean valid = true; for (int i = 0; i < LEN_M; ++i){ if (C[i] == -1){ valid = false; break; } } if (!valid) out.println("NO"); else{ long ans = CRT(C, R, LEN_M); if (ans == -1) out.println("NO"); else{ out.println(ans); } } } } } /********************欧几里德两两合并***************************/ class GCD{ long d; long x; long y; public GCD(long d, long x, long y){ this.d = d; this.x = x; this.y = y; } } public long gcd(long a, long b){ if (b == 0) return a; else return gcd(b, a % b); } public GCD extgcd(long a, long b){ if (b == 0) return new GCD(a, 1, 0); else{ GCD g = extgcd(b, a % b); GCD ans = new GCD(0, 0, 0); ans.d = g.d; ans.x = g.y; ans.y = g.x - (a / b) * g.y; return ans; } } public long mod_inverse(long a, long m){ GCD g = extgcd(a, m); if (g.d != 1) return -1; return (g.x % m + m) % m; } class LL{ long val; public LL(){} public LL(long val){ this.val = val; } @Override public String toString() { return val + ""; } } public boolean merge(long a1, long m1, long a2, long m2, LL a3, LL m3){ GCD g = extgcd(m1, m2); long c = a2 - a1; long d = g.d; if (c % d != 0) return false; m1 /= d; m2 /= d; c /= d; long inv = mod_inverse(m1, m2); c *= inv; c %= m2; c *= m1 * d; c += a1; m3.val = m1 * m2 * d; a3.val = c; return true; } public long CRT(int[] a, int[] m, int n){ long a1 = a[0]; long m1 = m[0]; for (int i = 1; i < n; ++i){ long a2 = a[i]; long m2 = m[i]; LL a3 = new LL(); LL m3 = new LL(); if (!merge(a1, m1, a2, m2, a3, m3)) return -1; a1 = a3.val; m1 = m3.val; } return (a1 % m1 + m1) % m1; } /****************大数进制转换****************/ int MAX_D = 500 + 16; int[] M = new int[MAX_D]; int[] K = new int[MAX_D]; int LEN_M; int LEN_K; public int tran(int ten[], int len, int ans[], int d){ int cnt = 0; int tcnt = 0; while(tcnt < len){ for(int i = tcnt; i < len; ++i){ ten[i + 1] += (ten[i] % d) * 10; ten[i] /= d; }/**模拟除法,余数 * 10存在ten[len]中*/ ans[cnt++] = ten[len] / 10; ten[len] = 0; while(ten[tcnt] == 0 && tcnt < len) ++tcnt; } return cnt; } /***************************计算每一位的模数****************************/ int[] R = new int[MAX_D]; int[] C = new int[MAX_D]; // 获得模数 public void getRC(int len){ Arrays.fill(C, -1); int id = len - 1; R[id] = 1; int cnt = 0; if (M[id] == K[id]) C[id] = 0; for (int i = A[M[id]]; i != M[id]; i = A[i]){ ++R[id]; ++cnt; if (i == K[id]) C[id] = cnt; } for (int i = 0; i < id; ++i){ if (K[i] == M[i]) C[i] = 0; R[i] = 1; cnt = 0; for (int j = B[M[i]]; j != M[i]; j = B[j]){ ++R[i]; ++cnt; if (K[i] == j) C[i] = cnt; } } } FastScanner in; PrintWriter out; void run() throws IOException { boolean oj; try { oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode"); } catch (Exception e) { oj = System.getProperty("ONLINE_JUDGE") != null; } InputStream is = oj ? System.in : new FileInputStream(new File(INPUT)); in = new FastScanner(is); out = new PrintWriter(System.out); long s = System.currentTimeMillis(); solve(); out.flush(); if (!oj){ System.out.println("[" + (System.currentTimeMillis() - s) + "ms]"); } } public boolean more(){ return in.hasNext(); } public int ni(){ return in.nextInt(); } public long nl(){ return in.nextLong(); } public double nd(){ return in.nextDouble(); } public String ns(){ return in.nextString(); } public char nc(){ return in.nextChar(); } class FastScanner { BufferedReader br; StringTokenizer st; boolean hasNext; public FastScanner(InputStream is) throws IOException { br = new BufferedReader(new InputStreamReader(is)); hasNext = true; } public String nextToken() { while (st == null || !st.hasMoreTokens()) { try { st = new StringTokenizer(br.readLine()); } catch (Exception e) { hasNext = false; return "##"; } } return st.nextToken(); } String next = null; public boolean hasNext(){ next = nextToken(); return hasNext; } public int nextInt() { if (next == null){ hasNext(); } String more = next; next = null; return Integer.parseInt(more); } public long nextLong() { if (next == null){ hasNext(); } String more = next; next = null; return Long.parseLong(more); } public double nextDouble() { if (next == null){ hasNext(); } String more = next; next = null; return Double.parseDouble(more); } public String nextString(){ if (next == null){ hasNext(); } String more = next; next = null; return more; } public char nextChar(){ if (next == null){ hasNext(); } String more = next; next = null; return more.charAt(0); } } }
注意m和k是大数,用字符串去取,且需要用大数高精度转d进制的做法,才能AC。
相关文章推荐
- 挑战程序竞赛系列(46):4.1Polya 计数定理(2)
- 挑战程序竞赛系列(45):4.1Polya 计数定理(1)
- 挑战程序竞赛系列(47):4.1 计数 二项式取模
- 挑战程序竞赛系列(44):4.1计数 欧拉函数
- 挑战程序竞赛系列(43):4.1矩阵 高斯消元
- 挑战程序竞赛系列(53):4.4 栈
- 挑战程序竞赛系列(11):2.5最短路径
- 挑战程序竞赛系列(87):3.6平面扫描(1)
- 挑战程序竞赛系列(76):4.3强连通分量分解(3)
- 挑战程序竞赛系列(60):4.6树上的分治法(3)
- 挑战程序竞赛系列(64):4.7字符串上的动态规划(2)
- 挑战程序竞赛系列(24):3.5最大流与最小割
- 挑战程序竞赛系列(20):3.2尺取法
- 挑战程序竞赛系列(69):4.7后缀数组(1)
- 挑战程序竞赛系列(73):4.7高度数组(3)
- 挑战程序竞赛系列(34):3.2坐标离散化
- 挑战程序竞赛系列(78):4.3 2-SAT(2)
- 挑战程序竞赛系列(26):3.5二分图匹配(1)
- 挑战程序竞赛系列(49):4.2 推理与动态规划算法(2)
- 挑战程序竞赛系列(85):3.6极限情况(2)