蓝桥杯备赛-数论

蓝桥杯备赛-数论

  1. 数论

    1. 204. 计数质数
      我的题解:

      思路: 埃氏筛,从2开始素数的倍数肯定是合数,所以这里从2开始每次判断nums[i]是否为0,若为0,则计数器+1,并且在i * i < n的基础上,将从i * i 到n - 1中是i的倍数的全置为1,代表这些数都是合数,最后返回count即可

      class Solution {
          public int countPrimes(int n) {
               int[] nums = new int[n];
              int count = 0;
              for (int i = 2; i < n ; i++) {
                  if(nums[i] == 0){
                      count++;
                      if((long)i * i < n){
                          for(int j = i * i; j < n; j += i){
                              nums[j] = 1;
                          }
                      }
                  }
              }
              return count;
          }
      }
      
    2. 质数距离

      给定两个整数 L 和 U,你需要在闭区间 [L,U][�,�] 内找到距离最接近的两个相邻质数 C1 和 C2(即 C2−C1是最小的),如果存在相同距离的其他相邻质数对,则输出第一对。

      同时,你还需要找到距离最远的两个相邻质数 D1 和 D2(即 D1−D2 是最大的),如果存在相同距离的其他相邻质数对,则输出第一对。

      输入格式

      每行输入两个整数 L 和 U,其中 L 和 U 的差值不会超过 106106。

      输出格式

      对于每个 L 和 U,输出一个结果,结果占一行。

      结果包括距离最近的相邻质数对和距离最远的相邻质数对。(具体格式参照样例)

      如果 L和 U 之间不存在质数对,则输出 There are no adjacent primes.

      数据范围

      1≤ L< U ≤ 2e31−1

      思路: 埃氏筛

      import java.util.Arrays;
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
             new Main().run();
          }
          int N = (int) (1e6 + 10);
          int[] primes = new int[N];
          int cnt;
          boolean[] st = new boolean[N];
          void getPrimes(int n){
              Arrays.fill(st, false);
              cnt = 0;
              for(int i = 2; i <= n; i++){
                  if(!st[i]){
                      primes[cnt++] = i;
                  }
                  for(int j = 0; primes[j] * i <= n; j++){
                      st[primes[j] * i] = true;
                      if(i % primes[j] == 0) break;
                  }
              }
          }
      
          void run(){
              long l ,r;
              Scanner sc = new Scanner(System.in);
              while(sc.hasNext()){
                  l = sc.nextLong();
                  r = sc.nextLong();
                  getPrimes(50000);
                  Arrays.fill(st, false);
                  for(int i = 0; i < cnt; i++){
                      int p = primes[i];
                      //把[l, r]中所有的倍数都筛掉
                      for(long j = Math.max((l + p - 1) / p * p, 2l * p); j <= r; j += p){
                          st[(int) (j - l)] = true;
                      }
                  }
      
                  cnt = 0;
                  for(int i = 0; i <= r - l; i++){
                      if(!st[i] && i + l > 1){
                          primes[cnt++] = (int) (i + l);
                      }
                  }
                  if(cnt < 2) System.out.println("There are no adjacent primes.");
                  else{
                      int minp = 0, maxp = 0;
                      for(int i = 0; i + 1 < cnt; i++){
                          int d = primes[i + 1] - primes[i];
                          if(d < primes[minp + 1] - primes[minp]) minp = i;
                          if(d > primes[maxp + 1] - primes[maxp]) maxp = i;
                      }
      
                      System.out.printf("%d,%d are closest, %d,%d are most distant.\n", primes[minp], primes[minp + 1], primes[maxp], primes[maxp + 1]);
                  }
              }
          }
      }
      
      
      #include<iostream>
      #include<cstring>
      #include<algorithm>
      using namespace std;
      const int N = 1e6 + 10;//定义常量用const
      int primes[N], cnt;
      bool st[N];
      
      /**
       * 埃氏筛
       * @return
       */
       void get_primes(int n){
          memset(st, 0, sizeof st);
          cnt = 0;
          for(int i = 2; i <= n; i++){
              //若当前数是质数
              if(!st[i]){
                  primes[cnt++] = i;//primes中当前位的值为i
              }
              //将该数所有的倍数都置为合数
              for(int j = 0; primes[j] * i <= n; j++){
                  st[primes[j] * i] = 1;
                  if(i % primes[j] == 0) break;
              }
          }
      
       }
      int main(){
           long long l, r;
      
          while(cin >> l >> r){
              get_primes(50000);
              memset(st, 0, sizeof st);
              for(int i = 0; i < cnt; i++){
                  int p = primes[i];
                  //把[l, r]中所有p的倍数筛掉
                  for(long long j = max((l + p - 1) / p * p, 2ll * p); j <= r; j += p){
                      //j - 偏移量
                      st[j - l] = true;
                  }
              }
              cnt = 0;
              for(int i = 0; i <= r - l; i++){
                  if(!st[i] && i + l > 1){
                      primes[cnt++] = i + l;
                  }
              }
              if(cnt < 2) puts("There are no adjacent primes.");
              else{
                  int minp = 0, maxp = 0;
                  for(int i = 0; i + 1 < cnt; i++){
                      int d = primes[i + 1] - primes[i];
                      if(d < primes[minp + 1] - primes[minp]) minp = i;
                      if(d > primes[maxp + 1] - primes[maxp]) maxp = i;
                  }
      
                  printf("%d,%d are closest, %d,%d are most distant.\n", primes[minp], primes[minp + 1], primes[maxp], primes[maxp + 1]);
              }
      
      
          }
          return 0;
      }
      
    3. 阶乘分解

      给定整数 N,试把阶乘 N!分解质因数,按照算术基本定理的形式输出分解结果中的 pi 和 ci 即可。

      输入格式

      一个整数 N。

      输出格式

      N! 分解质因数后的结果,共若干行,每行一对 pi,ci,表示含有 pcii 项。按照 pi 从小到大的顺序输出。

      数据范围

      3 ≤ N ≤ 10e6

      思路: 用埃氏筛求出范围内的质数,对每个质数进行判断看在n的范围内倍数有多少个

      **时间复杂度:**O(n)
      [ n / p ] + [ n / ( p ∗ p ) ] + [ n / ( p ∗ p ∗ p ) ] . . . . [n / p] + [n / (p * p)] + [n / (p * p * p)].... [n/p]+[n/(pp)]+[n/(ppp)]....

      #include<iostream>
      #include<cstring>
      #include<math.h>
      using namespace std;
      typedef long long LL;
      /**
       * 阶乘-分解质因数-
       * @return
       */
      const int N = 1e6 + 10;
      int primes[N], cnt;
      bool st[N];
      int ans[N];
      
      /**
       * 埃氏筛
       * @param n
       */
      void get_primes(int n){
          cnt = 0;
          memset(st, 0, sizeof st);
          for(int i = 2; i <= n; i++){
              if(!st[i]){
                  primes[cnt++] = i;
                  if((LL)i * i <= n){
                      for(int j = i * i; j <= n; j += i){
                          st[j] = 1;
                      }
                  }
              }
          }
      
      }
      /**
       * 1.找出小于或等于根号n的素数
       * 2.用n对按从小到大的顺序对小于或n的质数整除并累加,除完后 除数 * primes[i]
       * ans[primes[i]] = n / d + n / (d * d) + n / (d * d * d)....
       * @return
       */
      int main(){
          int n;
          cin >> n;
          get_primes(n);
          for(int i = 0; i < cnt; i++){
             LL d = primes[i];
             while(n >= d){
                 ans[primes[i]] += n / d;//加上可以整除的个数
                 d *= primes[i];
             }
          }
          for(int i = 0; i < cnt; i++){
              if(ans[primes[i]]){
                  printf("%d %d \n", primes[i], ans[primes[i]]);
              }
          }
          return 0;
      }
      
      import java.util.Scanner;
      
      public class Main {
          /**
           * 阶乘分解
           * @param args
           */
          public static void main(String[] args) {
              new Main().run();
          }
          int N = (int) (1e6 + 10), cnt = 0;
          int[] primes = new int[N];
          boolean[] st = new boolean[N];
          int[] ans = new int[N];
      
          /**
           * 埃氏筛
           * @param n
           */
          void getPrimes(int n){
              for(int i = 2; i <= n; i++){
                  if(!st[i]){
                      primes[cnt++] = i;
                      if((long)i * i <= n){
                          for(int j = i * i; j < n; j += i){
                              st[j] = true;
                          }
                      }
                  }
              }
          }
      
          void run(){
              Scanner sc = new Scanner(System.in);
              int n = sc.nextInt();
              getPrimes(N - 2);
      
              for(int i = 0; i < cnt; i++){
                  long d = primes[i];
                  while(n >= d){
                      ans[primes[i]] += n / d;
                      d *= primes[i];
                  }
              }
      
              for(int i = 0; i < cnt; i++){
                  if(ans[primes[i]] > 0){
                      System.out.println(primes[i] + " " + ans[primes[i]]);
                  }
              }
              sc.close();
          }
      }
      
    4. 反素数*

      对于任何正整数 x,其约数的个数记作 g(x),例如 g(1)=1、g(6)=4。

      如果某个正整数 x 满足:对于任意的小于 x 的正整数 i,都有 g(x)>g(i),则称 x 为反素数。

      例如,整数 1,2,4,6 等都是反素数。

      现在给定一个数 N,请求出不超过 N 的最大的反素数。

      输入格式

      一个正整数 N。

      输出格式

      一个整数,表示不超过 N 的最大反素数。

      数据范围

      1≤ N ≤ 2∗10e9

      #include<iostream>
      #include<cstring>
      #include<algorithm>
      using namespace std;
      typedef long long LL;
      int primes[9] = {2, 3, 5, 7, 11, 13, 17, 19, 21};
      int maxn, number, n;//分别表示约数个数、数本身和n
      
      void dfs(int u, int last, int p, int s){
        	//更新
          if(s > maxn || s == maxn && p < number){
              maxn = s;
              number = p;
          }
          //终止条件
          if(u == 9) return;
          //划分
          for(int i = 1; i <= last; i++){
              if((LL) p * primes[u] > n) break;
              p *= primes[u];
              dfs(u + 1, i, p, s * (i + 1));
          }
      }
      
      int main(){
          cin >> n;
          dfs(0, 30, 1, 1);
          cout << number << endl;
          return 0;
      }
      
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
      int[] primes = {2, 3, 5, 7, 11, 13, 17, 19, 23};
      int maxn, number, n;
          void dfs(int u, int last, int p, int s){
              if(s > maxn || s == maxn && p < number){
                  maxn = s;
                  number = p;
              }
              if(u == 9) return;
              for(int i = 1; i <= last; i++){
                  if((long) p * primes[u] > n) break;
                  p *= primes[u];
                  dfs(u + 1, i, p, s * (i + 1));
              }
          }
          void run(){
              Scanner sc = new Scanner(System.in);
              n = sc.nextInt();
              dfs(0, 30, 1, 1);
              System.out.println(number);
          }
      }
      
      
    5. 余数之和*

      思路:⌊k/i⌋,随着i的增大,值越小,该式为单调递减

      #include<iostream>
      using namespace std;
      typedef long long LL;
      LL n, k, ans, l, r;
      int main(){
          cin >> n >> k;
          ans = n * k;
          //分段算,区间范围是从1~n,没计算完一段,l就从r的下一个数开始重新计算
          for(l = 1; l <= n; l = r + 1){
              if(k / l == 0) break;//当k < l时说明这段后面的数是无效的跳出
              r = min(k / (k / l), n);//求上界
              ans -= (k / l) * (l + r) * (r - l + 1) / 2;//求三角形面积
          }
      
          cout << ans << endl;
          return 0;
      }
      
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
      
          void run(){
              long ans, n, k, l, r;
              Scanner sc = new Scanner(System.in);
              n = sc.nextInt();
              k = sc.nextInt();
              ans = n * k;
              for(l = 1; l <= n; l = r + 1){
                  if(k / l == 0) break;
                  r = Math.min(k / (k / l), n);
                  ans -= (k / l) * (l + r) * (r - l + 1) / 2;
              }
              System.out.println(ans);
              sc.close();
          }
      }
      
    6. 200. Hankson的趣味题

      #include<iostream>
      using namespace std;
      typedef long long LL;
      typedef pair<int, int> PII;
      const int N = 45000, M = 50;
      int primes[N], cnt;
      bool st[N];
      PII factor[M];//存储b1所有质因数及其个数
      int cntf;//因数计数器
      int divider[N], cntd;//b1所有约数、约数计数
      /**
       * 获取素数
       * @param n
       */
      void get_primes(int n){
          for(int i = 2; i <= n; i++){
              if(!st[i]) {
                  primes[cnt++] = i;
                  if((long long) i * i <= n){
                      for(int j = i * i; j <= n; j += i){
                          st[j] = 1;
                      }
                  }
              }
      
      
          }
      }
      //深度搜索-质因数拼凑所有约数
      void dfs(int u, int p){
          //终止条件,当层数大于因子的个数
          if(u > cntf){
              divider[cntd++] = p;//当前约数 = p
              return;
          }
          //划分
          for(int i = 0; i <= factor[u].second; i++){//i从0开始到当前质因数的个数
              //层数+1,
              dfs(u + 1, p);
              p *= factor[u].first;//乘当前质因数
          }
      }
      int gcd(int a, int b){
          return b == 0 ? a : gcd(b, a % b);
      }
      
      int lcm(int a, int b){
          return a * b / gcd(a, b);
      }
      /**
       * 题目描述:a1 = gcd(x, a0), b1 = lcm(x, b0), 给出a0, a1, b0, b1求满足要求的x个数
       * @return
       */
      int main(){
          get_primes(N);//获取范围内所有质数
          int n;
          scanf("%d", &n);
          while(n--){
              int a0, a1, b0, b1;
              scanf("%d%d%d%d", &a0, &a1, &b0, &b1);//快读
              int d = b1;
              cntf = 0;
              //将b1分解质因数并存入质因数中
              for(int i = 0; primes[i] <= d / primes[i]; i++){
                  int p = primes[i];
                  if(d % p == 0){
                      int s = 0;
                      while(d % p == 0) s++, d /= p;
                      factor[++cntf] = {p, s};
                  }
              }
              if(d > 1) factor[++cntf] = {d, 1};
              //用d1的所有质因数拼凑出所有的约数
              cntd = 0;
              dfs(1, 1);
              //枚举所有约数,并记录满足条件的个数
              int ans = 0;
              for(int i = 0; i < cntd; i++){
                  int x = divider[i];
                  if(gcd(x, a0) == a1 && (LL)x * b0 / gcd(x, b0) == b1) ans++;
              }
              printf("%d\n", ans);
          }
          return 0;
      }
      
      import java.io.*;
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.List;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = 45000, M = 50;
          int[] primes = new int[N];
          int cnt = 0, cntd, cntf;
          boolean[] st = new boolean[N];
          int[] divider = new int[N];
          Pair[] factor = new Pair[M];
          void getPrimes(int n){
              for(int i = 2; i < n; i++){
                  if(!st[i]) {
                      primes[cnt++] = i;
                      if((long)i * i < n){
                          for (int j = i * i; j < n; j += i) {
                              st[j] = true;
                          }
                      }
                  }
              }
          }
          void dfs(int u, int p){
              //终止条件
              if(u > cntf){
                  divider[cntd++] = p;
                  return;
              }
              //划分
              for(int i = 0; i <= factor[u].s; i++){
                  dfs(u + 1, p);
                  p *= factor[u].f;
              }
          }
      
          int gcd(int a, int b){
              return b == 0 ? a : gcd(b, a % b);
          }
      
          StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
          int nextInt(){
              try {
                  streamTokenizer.nextToken();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return (int) streamTokenizer.nval;
          }
      
          void run(){
              PrintWriter out = new PrintWriter(System.out);
              getPrimes(N);//获取范围内所有的质数
              int n = nextInt();
              while(n-- > 0){
                  int a0, a1, b0, b1;
                  a0 = nextInt();
                  a1 = nextInt();
                  b0 = nextInt();
                  b1 = nextInt();
                  int d = b1;
                  cntf = 0;
                  //将b1分解质因数并存入质因数中
                  for(int i = 0; primes[i] <= d / primes[i]; i++){
                      int p = primes[i];
                      if(d % p == 0){
                          int s = 0;
                          while(d % p == 0){
                              s++;
                              d /= p;
                          }
                          factor[++cntf] = new Pair(p, s);
                      }
                  }
      
                  if(d > 1) factor[++cntf] = new Pair(d, 1);
                  cntd = 0;
                  //用d1的所有质因数平凑出所有的约数
                  dfs(1, 1);
                  //枚举所有约数,并记录满足条件的个数
                  int ans = 0;
                  for(int i = 0; i < cntd; i++){
                      int x = divider[i];
                      if(gcd(x, a0) == a1 && (long) x * b0 / gcd(x, b0) == b1) ans++;
                  }
                  out.println(ans);
      
              }
              out.flush();
              out.close();
          }
      
          class Pair{
              int f, s;
      
              public Pair(int f, int s) {
                  this.f = f;
                  this.s = s;
              }
          }
      }
      
    7. 201. 可见的点

      题目描述:在一个平面直角坐标系的以(0, 0)为左下角、(N, N)为右上角的矩形中,除了(0,0)之外,每个坐标都插着一个钉子。求在原点向四周看去能看到多少个钉子,这里同一条线上的钉子只能看到第一个。

      数据规模: 1 <= N, C <= 1000

      **分析:**分析题目容易发现,除了(1, 0), (0, 1)和(1, 1)这三个钉子以外,一个钉子(x, y)能被看到,当且仅当 1 <= x, y <= N, x != y, 并且gcd(x, y) = 1.

      在2 <= x <= y <= N, x != y 中能看到的钉子关于过(0, 0)和(N, N)的直线对称。我们可以考虑其中的一半,即 1 <= x < y <= N,答案就是3 + 2 * (phi[i]) (2 <= i <= n)。

      题解:利用埃氏筛再结合欧拉函数的计算式,求完后再根据输出的要求进行求和合并输出

      #include<iostream>
      using namespace std;
      const int N = 1010;
      int phi[N];
      
      /**
       * 埃氏筛写法
       * @param n
       * @return
       */
      int euler(int n){
          for(int i = 2; i <= n; i++) phi[i] = i;
          for(int i = 2; i <= n; i++){
              if(phi[i] == i){
                  for(int j = i; j <= n; j += i){
                      phi[j] = phi[j] / i * (i - 1);
                  }
              }
          }
          for(int i = 2; i <= n; i++) cout << phi[i] << endl;
      }
      int t, n;
      int main(){
          cin >> t;
          euler(N);
          for(int i = 1; i<= t; i++){
              cin >> n;
              int ans = 0;
              for(int i = 2; i <= n; i++) ans += phi[i];//求和
              ans = 2 * ans + 3;//这里只算了一半还要加上前两个数1,2
              printf("%d %d %d\n", i, n, ans);
          }
          return 0;
      }
      
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = 1010;
          int[] phi = new int[N];
          void euler(int n){
              for(int i = 2; i <= n; i++) phi[i] = i;
              for(int i = 2; i <= n; i++){
                  if(phi[i] == i){
                      for(int j = i; j <= n; j += i){
                          phi[j] = phi[j] / i * (i - 1);
                      }
                  }
              }
          }
          void run(){
              Scanner sc = new Scanner(System.in);
              int t = sc.nextInt();
              euler(N - 1);
              for(int i = 1; i <= t; i++){
                  int ans = 0, n = sc.nextInt();
                  for(int j = 2; j <= n; j++) ans += phi[j];
                  ans = 2 * ans + 3;
                  System.out.println(i + " " + n + " " + ans);
              }
              sc.close();
          }
      }
      
    8. 89. a^b

      题目描述:给出a、b、p,求a^b对p取模

      数据规模: 0 <= a, b <= 1e9, 1 <= p <= 1e9

      **分析:**这里需要用到快速幂

      题解:使用快速幂可以在O(log2b)时间内求出

      #include<iostream>
      using namespace std;
      
      int a, b, p;
      /**
       * calculate a^b mod p
       * @param a
       * @param b
       * @param p
       * @return
       */
      int power(int a, int b, int p){
          long long ans = 1 % p;//特殊情况,这里当p为1时ans就为0
          while(b){
              if(b & 1) ans = ans * a % p;
              a = (long long) a * a % p;
              b >>= 1;
          }
          return ans;
      }
      int main(){
          cin >> a >> b >> p;
          cout << power(a, b, p) << endl;
          return 0;
      }
      
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
             new Main().run();
          }
      
          void run(){
              Scanner sc = new Scanner(System.in);
              int a = sc.nextInt(), b = sc.nextInt(), p = sc.nextInt();
              System.out.println(power(a, b, p));
          }
      
          int power(int a, int b, int p){
                long ans = 1 % p;
              while(b > 0){
                  if((b & 1) == 1) ans = ans * a % p;
                  a = a * a % p;
                  b >>= 1;
              }
              return (int)ans;
          }
      }
      
    9. 最短Hamilton路径

      题目描述:给出a、b、p,求a*b对p取模,这里a和b都是64位整数

      数据规模: 0 <= a, b, p <= 1e18

      **分析:**这里同样需要用到快速幂的思想,a * b = ck-1 * a * 2^k-1 + ck-2 * a * 2^k-2…,拆分后相加

      题解:使用快速幂可以在O(log2b)时间内求出

      #include<iostream>
      using namespace std;
      typedef long long LL;
      /**
       * 类似于快速幂的思想,将b拆分为2^0 + 2^1 + ... + 2^k
       * @param a
       * @param b
       * @param p
       * @return
       */
      LL mul(LL a, LL b, LL p){
          LL ans = 0;
          while(b){
              if(b & 1) ans = (ans + a) % p;
              a = a * 2 % p;
              b >>= 1;
          }
          return ans;
      }
      
      int main(){
          LL a, b, p;
          cin >> a >> b >> p;
          cout << mul(a, b, p) << endl;
      }
      
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
      
          /**
           * 64位整数乘法
           * @param a
           * @param b
           * @param p
           * @return
           */
          long mul(long a, long b, long p){
              long ans = 0;
              while(b > 0){
                  if((b & 1) == 1) ans = (ans + a) % p;
                  a = a * 2 % p;
                  b >>= 1;
              }
              return ans;
          }
          void run(){
              Scanner sc = new Scanner(System.in);
              long a = sc.nextLong(), b = sc.nextLong(), p = sc.nextLong();
              System.out.println(mul(a, b, p));
              sc.close();
          }
      
      }
      
    10. 最短Hamilton路径

    题目描述:给定n个点的的带权无向图,求从起点0~n-1的哈密顿路径的最小值

    数据规模: 1 <= n <= 20, 0 <= a[i][i][j][j]<= 1e7

    **分析:**本题是一道NP完全问题,没有办法在多项式时间内求解,因此要考虑优化才能通过评测,这里可以使用动态规划的思想结合状态压缩

    题解:dp(i)(j),这里i的对应二进制形式表示对应当前的状态,j表示当前停在哪个点上
    边界条件 : d p [ 1 ] [ 0 ] = 0 , 初始状态 边界条件:dp[1][0] = 0, 初始状态 边界条件:dp[1][0]=0,初始状态

    状态转移方程 : d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ i − 1 < < j ] [ k ] + w [ k ] [ j ] ) 状态转移方程:dp[i][j] = min(dp[i][j], dp[i - 1 << j][k] + w[k][j]) 状态转移方程:dp[i][j]=min(dp[i][j],dp[i1<<j][k]+w[k][j])

    时间复杂度:O(n*2^n)

    #include<iostream>
    #include<cstring>
    using namespace std;
    const int N = 20, M = 1 << 20;
    int dp[M][N], w[N][N], n;
    int main(){
        cin >> n;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                cin >> w[i][j];
            }
        }
        memset(dp, 0x3f, sizeof dp);//初始化
        //边界条件
        dp[1][0] = 0;
        for(int i = 0; i < 1 << n; i++){
            for(int j = 0; j < n; j++){
                if(i >> j & 1){
                    for(int k = 0; k < n; k++){
                        if(i ^ 1 << j >> k & 1) dp[i][j] = min(dp[i][j], dp[i ^ 1 << j][k] + w[k][j]);
                    }
                }
            }
        }
        cout << dp[(1 << n) - 1][n - 1] << endl;
        return 0;
    }
    
    import java.util.Arrays;
    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            new Main().run();
        }
        int N = 20, M = 1 << 20;
        int[][] w = new int[N][N], dp = new int[M][N];
        void run(){
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt();
            for(int i = 0; i < n; i++){
                for(int j = 0; j < n; j++) w[i][j] = sc.nextInt();
            }
            //边界条件
            for(int i = 0; i < M; i++){
                Arrays.fill(dp[i], 0x3f3f3f);
            }
            dp[1][0] = 0;//初始状态,停在位置1上
            //转移方程: dp[i][j] = min(dp[i][j], dp[i ^ 1 << j][k] + w[k][j])
            for(int i = 0; i < 1 << n; i++){
                for(int j = 0; j < n; j++){
                    if((i >> j & 1) == 1){//若i状态中包含j
                        for(int k = 0; k < n; k++){//若包含k状态的话
                            if((i - (1 << j) >> k & 1) == 1) dp[i][j] = Math.min(dp[i][j], dp[i - (1 << j)][k] + w[k][j]);
                        }
                    }
                }
            }
            sc.close();
            System.out.println(dp[(1 << n) - 1][n - 1]);
        }
    }
    
    1. 998. 起床困难综合症

      题目描述:在[0, m]之间选一个整数x0,经过给定的n次位运算,时结果ans最大

      数据规模: 2 <= n <= 1e5, 2 <= m <= 1e9

      **分析:**位运算的主要特点之一是在二进制表示下不进位。正因如此,在x0可以任意选择的情况下,参与位运算的各个位(bit)之间是独立无关的。换言之,对于任意的k(0 <= k < 30), “ans的第k位是几,只与x0的第k位是几有关,与其它位无关”。因此我们可以从高位到低位依次考虑x0的每一位填0还是填1

      题解:x0的第k位应该填1,需要满足以下两个条件

      1. 已经填好的更高位构成的数值加上1 << k后不超过m
      2. 用每个参数的第k位参与运算。若初值为1,则n次位运算后结果为1;若初值为0,则n次位运算后结果为0

      时间复杂度:O(n * logm)

      #include<iostream>
      using namespace std;
      const int N = 1e5 + 10, M = 1e9 + 10;
      pair<string, int> a[N];
      int n, m;
      /**
       * bit代表二进制第几位,now代表当前数
       * @param bit
       * @param now
       * @return
       */
      int calc(int bit, int now){
          for(int i = 0; i < n; i++){
              int x = a[i].second >> bit & 1;
              if(a[i].first == "OR") now |= x;
              if(a[i].first == "AND") now &= x;
              if(a[i].first == "XOR") now ^= x;
          }
          return now;
      }
      int main(){
          cin >> n >> m;
          for(int i = 0; i < n; i++){
              char str[5]; int x;
              scanf("%s%d", str, &x);
              a[i] = make_pair(str, x);
          }
          int val = 0, ans = 0;
          //从高位到低位枚举
          for(int bit = 29; bit >= 0; bit--){
              int res0 = calc(bit, 0);//看填0是否有变化
              int res1 = calc(bit ,1);//看填1是否有变化,若没有变化,加上该位后小于m,就可以填1
              //这里有四种情况分别是00、01、10、11
              //仅有01是满足可以填1的一个条件,其它情况运算过后该位都相反了,填0,后面运算还可能填1
              if(val + (1 << bit) <= m && res0 < res1)
                  val += 1 << bit, ans += res1 << bit;
              else
                  ans += res0 << bit;
          }
          cout << ans << endl;
          return 0;
      }
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.util.StringTokenizer;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = (int) (1e5 + 10), M = (int) (1e9 + 10);
          int n, m;
          Pair[] a = new Pair[N];
          int calc(int bit, int now){
              for(int i = 0; i < n; i++){
                  int x = a[i].s >> bit & 1;
                  if(a[i].f.equals("OR")) now |= x;
                  if(a[i].f.equals("AND")) now &= x;
                  if(a[i].f.equals("XOR")) now ^= x;
              }
              return now;
          }
          void run(){
              n = nextInt();
              m = nextInt();
              for(int i = 0; i < n; i++){
                  String str = next();
                  int x = nextInt();
                  a[i] = new Pair(str, x);
              }
              int val = 0, ans = 0;
              for(int bit = 29; bit >= 0; bit--){
                  int res0 = calc(bit, 0);
                  int res1 = calc(bit, 1);
                  if(val + (1 << bit) <= m && res0 < res1) {
                      val += 1 << bit;
                      ans += res1 << bit;
                  }else{
                      ans += res0 << bit;
                  }
              }
              System.out.println(ans);
          }
      
          StringTokenizer stringTokenizer;
          BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
          String next(){
              while(stringTokenizer == null || !stringTokenizer.hasMoreElements()){
                  try {
                      stringTokenizer = new StringTokenizer(bufferedReader.readLine());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
              return stringTokenizer.nextToken();
          }
      
          int nextInt(){
              return Integer.parseInt(next());
          }
      
          class Pair{
              String f;
              int s;
      
              public Pair(String f, int s) {
                  this.f = f;
                  this.s = s;
              }
          }
      }
      
    2. 92. 递归实现指数型枚举

      题目描述:在1~n这n个数之间任选多个,输出所有可能的方案

      数据规模: 1 <= n <= 15

      **分析:**这里很显然要用递归来做,选和不选是两种情况,这里选了该数之后需要回溯

      题解:递归实现

      时间复杂度:O(2^n)

      #include<iostream>
      #include<vector>
      using namespace std;
      vector<int> chosen;
      int n;
      void calc(int x){
          //边界条件
          if(x == n + 1){
              for(int i = 0; i < chosen.size(); i++)
                  printf("%d ", chosen[i]);
              puts("");
              return;
          }
          //选
          chosen.push_back(x);
          calc(x + 1);
          //回溯
          chosen.pop_back();
          calc(x + 1);
      }
      int main(){
          cin >> n;
          calc(1);
      }
      
      import java.util.*;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int n;
          List<Integer> chosen = new ArrayList<>();
          void calc(int x){
              if(x == n + 1){
                  for(int i = 0; i < chosen.size(); i++){
                      System.out.printf("%d ", chosen.get(i));
                  }
                  System.out.println();
                  return;
              }
              //选
              chosen.add(x);
              calc(x + 1);
              //回溯
              chosen.remove(chosen.size() - 1);
              calc(x + 1);
          }
          void run(){
              Scanner sc = new Scanner(System.in);
              n = sc.nextInt();
              calc(1);
          }
      }
      
    3. 题目描述:在1~n这n个数之间任选m个,输出所有可能的方案

      数据规模: 0 <= m <= n, 2n - m <= 25

      **分析:**这里很显然要用递归来做,选和不选是两种情况,这里选了该数之后需要回溯,除此之外这里还需要剪枝分别从当前已选的数目和剩余数的个数入手

      题解:递归实现/状态压缩

      时间复杂度:O(n!)

      #include<iostream>
      #include<vector>
      using namespace std;
      vector<int> chosen;
      int n, m;
      //递归实现组合型枚举
      void f(int x){
          //剪枝
          if(chosen.size() > m || chosen.size() + n - x + 1 < m) return;
          if(chosen.size() == m){
              for(int i = 0; i < chosen.size(); i++){
                  printf("%d ", chosen[i]);
              }
              puts("");
              return;
          }
          //选
          chosen.push_back(x);
          f(x + 1);
          //不选
          chosen.pop_back();
          f(x + 1);
      }
      int main(){
          cin >> n >> m;
          f(1);
          return 0;
      }
      
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int n, m;
          List<Integer> chosen = new ArrayList<>();
          void f(int x){
              //剪枝
              if(chosen.size() > m || chosen.size() + n - x + 1 < m) return;
              if(chosen.size() == m){
                  for(int i = 0; i < m; i++){
                      System.out.printf("%d ", chosen.get(i));
                  }
                  System.out.println();
                  return;
              }
              //选
              chosen.add(x);
              f(x + 1);
              //不选
              chosen.remove(chosen.size() - 1);
              f(x + 1);
          }
      
          void run(){
              Scanner sc = new Scanner(System.in);
              n = sc.nextInt();
              m = sc.nextInt();
              sc.close();
              f(1);
          }
      }
      
    4. 题目描述:在将1~n这n个数随机排成一行,输出所有可能的次序

      数据规模: 1 <= n <= 9

      **分析:**这里很显然要用递归来做,我们可以创建两个数组vis和chosen,分别表示是否选取了当前数与存第k个数

      题解:递归实现

      时间复杂度:O(n!)

      #include<iostream>
      using namespace std;
      const int N = 19;
      int vis[N], order[N],  n;
      void f(int k){
          //边界条件
          if(k == n){
              for(int i = 0; i < n; i++){
                  printf("%d ", order[i]);
              }
              puts("");
              return;
          }
          //划分
          for(int i = 1; i <= n; i++){
              if(vis[i]) continue;
              //选
              order[k] = i;
              vis[i] = 1;
              f(k + 1);
              vis[i] = 0;
          }
      }
      int main(){
          cin >> n;
          f(0);
          return 0;
      }
      
      import java.io.PrintWriter;
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = 20, n;
          int[] chosen = new int[N];
          boolean[] vis = new boolean[N];
          PrintWriter out = new PrintWriter(System.out);
          void f(int k){
              if(k == n){
                  for(int i = 0; i < k; i++){
                      out.printf("%d ", chosen[i]);
                  }
                  out.println();
              }
              for(int i = 1; i <= n; i++){
                  if(vis[i]) continue;
                  //选
                  chosen[k] = i;
                  vis[i] = true;
                  f(k + 1);
                  //回溯
                  vis[i] = false;
              }
          }
          void run(){
              Scanner sc = new Scanner(System.in);
              n = sc.nextInt();
              sc.close();
              f(0);
              out.flush();
          }
      }
      
    5. 202. 最幸运的数字

      题目描述:给定正整数L,请问至少多少个8连在一起是L的倍数

      数据规模: 1 <= L <= 2e9

      **分析:**朴素的枚举法肯定不可行,这里可以从数学的角度进行思考。x个8连在一起组成的整数可写作8(10^x - 1) / 9。题目是让我们求出一个最小的x满足L | 8(10^x - 1) / 9

      题解:本题需要结合快速幂、龟速乘、求试除法欧拉函数等基本算法来求解

      时间复杂度:O(logL)

      #include<iostream>
      using namespace std;
      typedef long long LL;
      
      //欧几里得算法
      int gcd(LL a, LL b){
          return b == 0 ? a : gcd(b, a % b);
      }
      
      //试除法求欧拉函数
      LL get_euler(LL c){
          LL ans = c;
          for(int i = 2; i <= c / i; i++){
              if(c % i == 0){
                  while(c % i == 0) c /= i;
                  ans = ans / i * (i - 1);
              }
          }
          if(c > 1) ans = ans / c * (c - 1);
          return ans;
      }
      
      //龟速乘
      LL qmul(LL a, LL b, LL c){
          LL ans = 0;
          while(b){
              if(b & 1) ans = (ans + a) % c;
              a = (a + a) % c;
              b >>= 1;
          }
          return ans;
      }
      
      //快速幂
      LL qmi(LL a, LL b, LL c){
          LL ans = 1 % c;
          while(b){
              if(b & 1) ans = qmul(ans, a, c);
              a = qmul(a, a, c);
              b >>= 1;
          }
          return ans;
      }
      
      int main(){
          int t = 1;
          LL l;
          while(cin >> l, l){
              int d = gcd(l, 8);
              LL c = 9 * l / d;
              LL phi = get_euler(c);
              LL ans = 1e18;
              if(c % 2 == 0 || c % 5 == 0) ans = 0;//判断c和10是否互质
              else{
                  for(LL i = 1; i <= phi / i; i++){
                      if(phi % i == 0){
                          if(qmi(10, i, c) == 1) ans = min(ans, i);
                          if(qmi(10, phi / i, c) == 1) ans = min(ans, phi / i);
                      }
                  }
              }
      
              printf("Case %d: %lld\n", t++ , ans);
          }
          return 0;
      }
      
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          long gcd(long a, long b){
              return b == 0 ? a : gcd(b, a % b);
          }
          long getEuler(long c){
              long ans = c;
              for(int i = 2; i <= c / i; i++){
                  if(c % i == 0){
                      while(c % i == 0) c /= i;
                      ans = ans / i * (i - 1);
                  }
              }
              if(c > 1) ans = ans / c * (c - 1);
              return ans;
          }
      
          long qmul(long a, long b, long c){
              long ans = 0;
              while(b > 0){
                  if((b & 1) == 1) ans = (ans + a) % c;
                  a = (a + a) % c;
                  b >>= 1;
              }
              return ans;
          }
      
          long qmi(long a, long b, long c){
              long ans = 1 % c;
              while(b > 0){
                  if((b & 1) == 1) ans = qmul(ans, a, c);
                  a = qmul(a, a, c);
                  b >>= 1;
              }
              return ans;
          }
      
          void run(){
              Scanner sc = new Scanner(System.in);
              int t = 1;
              while(sc.hasNext()){
                  long l = sc.nextLong();
                  while(l > 0){
                      long d = gcd(l, 8);
                      long c = 9 * l / d;
                      long phi = getEuler(c);
                      long ans = (long) 1e18;
                      if(c % 2 == 0 || c % 5 == 0) ans = 0;
                      else{
                          for(long i = 1; i <= phi / i; i++){
                              if(phi % i == 0){
                                  if(qmi(10, i, c) == 1) ans = Math.min(ans, i);
                                  if(qmi(10, phi / i, c) == 1) ans = Math.min(ans, phi / i);
                              }
                          }
                      }
                      System.out.printf("Case %d: %d\n", t++ , ans);
                      break;
                  }
      
              }
              sc.close();
          }
      }
      
      
    6. 95. 费解的开关

      题目描述:在一个5*5的矩阵中,点击任意一个位置,该位置以及它的上下左右四个相邻的位置数字都会发生变化

      数据规模: 1 <= n <= 500

      **分析:**朴素的枚举法不太好操作,因此我们换个思路。在这里我们可以固定第一行,然后根据第一行的情况其它行就可以唯一的确定下来

      题解:在这里我们可以这样思考我们固定第一行,然而第一行有2^5 = 32种取法,这里可以利用状态压缩来实现,将第一行对应位的1进行点击操作,随后我们根据1~4行对应位的情况对它的下一行进行操作。最后我们根据最后一行的情况选择性的对ans进行更新,并根据最终ans的值来输出

      时间复杂度:O(n)

      #include<iostream>
      #include<cstring>
      using namespace std;
      /**
       * 费解的开关
       * @return
       */
       const int M = 10;
       char a[M][M], backup[M][M];//c++中字符串可以直接当成字符数组用
       int n, dx[M] = {0, 0, 0, -1, 1}, dy[M] = {0, -1, 1, 0, 0};
       void turn(int y, int x){
          for(int i = 0; i < 5; i++){
              int ny = y + dy[i], nx = x + dx[i];
              if(ny >= 0 && ny < 5 && nx >= 0 && nx < 5){
                  a[ny][nx] ^= 1;//c++中字符型可以直接进行二进制运算
              }
          }
       }
      int main(){
          cin >> n;
          while(n--){
              for(int i = 0; i < 5; i++) cin >> a[i];
              int ans = 10;
               memcpy(backup, a, sizeof a);//备份
              for(int k = 0; k < 1 << 5; k++){
                 
                  int step = 0;
                  //第一行
                  for(int i = 0; i < 5; i++){//遍历每个位
                      if(k >> i & 1){
                          step++;
                          turn(0, i);
                      }
                  }
                  //我们看1到4行的情况
                  for(int i = 0; i < 4; i++){
                      for(int j = 0; j < 5; j++){
                          //若该位置为0,我们就将下一行对应位置翻转
                          if(a[i][j] == '0'){
                              step++;
                              turn(i + 1, j);
                          }
                      }
                  }
                  //看最后一行是否符合要求
                  bool flag = true;
                  for(int j = 0; j < 5; j++){
                      if(a[4][j] == '0'){
                          flag = false;
                          break;
                      }
                  }
                  if(flag) ans = min(ans, step);
                  //还原
                  memcpy(a, backup, sizeof a);
              }
              if(ans > 6) ans = -1;
              cout << ans << endl;
          }
          return 0;
      
      }
      
      import java.util.Arrays;
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = 5;
          char[][] arr = new char[N][N], back = new char[N][N];
          int[] dx = {0, 0, 0, -1, 1}, dy = {0, -1, 1, 0, 0};
          //异或操作,这里异或可以直接对字符对应的asc码异或,最后我们只需要判断对应字符是否匹配即可
          void turn(int x, int y){
              for(int i = 0; i < 5; i++){
                  int nx = x + dx[i], ny = y + dy[i];
                  if(nx >= 0 && nx < 5 && ny >= 0 && ny < 5){
                      arr[ny][nx] ^= 1;
                  }
              }
          }
          void run(){
              Scanner sc = new Scanner(System.in);
              int n = sc.nextInt();
              while(n-- > 0){
                  for(int i = 0; i < 5; i++){
                      arr[i] = sc.next().toCharArray();
                  }
      
                  for(int i = 0; i < 5; i++){
                      System.arraycopy(arr[i], 0, back[i], 0, 5);
                  }
                  int ans = 10;
                  for(int k = 0; k < 1 << 5; k++){
                      int step = 0;
                      //看第一行
                      for(int i = 0; i < 5; i++){
                          if((k >> i & 1) == 1){
                              step++;
                              turn(i, 0);
                          }
                      }
      
                      //遍历1~4行看有那些为0
                      for(int i = 0; i < 4; i++){
                          for(int j = 0; j < 5; j++){
                              if(arr[i][j] == '0'){
                                  step++;
                                  turn(j, i + 1);
                              }
                          }
                      }
      
                      boolean flag = true;
                      for(int j = 0; j < 5; j++){
                          if(arr[4][j] == '0'){
                              flag = false;
                              break;
                          }
                      }
                      if(flag) ans = Math.min(ans, step);
                      for(int i = 0; i < 5; i++){
                          System.arraycopy(back[i], 0, arr[i], 0, 5);
                      }
                  }
                  if(ans > 6) ans = -1;
                  System.out.println(ans);
              }
              sc.close();
          }
      }
      
    7. 96. 奇怪的汉诺塔

      题目描述:解出n个盘子的4座汉诺塔问题最少需要多少步?

      数据规模: 1 <= n <= 12

      **分析:**首先考虑n个盘子3座汉诺塔的问题,设d[n]表示求解该n盘3塔问题的最少步数,显然可以得到
      d [ 1 ] = 1 , d [ n ] = 2 ∗ d [ n − 1 ] + 1 d[1] = 1, d[n] = 2 * d[n - 1] + 1 d[1]=1,d[n]=2d[n1]+1
      意思是将n-1个盘子移到b中然后将一个盘子移到c中,最后将b中的n-1个盘子移到c中

      在本题中,设f[i]表示求解i盘4塔问题的最少步数,则f[i] = min(f[i], 2 * f[j] + d[i - j]) (1 <= i < n)

      上式含义是先把j个盘子在4塔模式下移动到b中,然后把n-j个盘子在3塔模式下移动到d中,最后将j个盘子在四塔模式下移动到d中

      题解:本题显然要用动态规划来求解

      时间复杂度:O(n^2)

      #include<iostream>
      #include<cstring>
      using namespace std;
      const int N = 20;
      int d[N], f[N], n;
      int main(){
          d[1] = 1;
          for(int i = 2; i <= 12; i++){
              d[i] = d[i - 1] * 2 + 1;
          }
          memset(f, 0x3f, sizeof f);
          f[1] = 1;
          for(int i = 2; i <= 12; i++){
              for(int j = 1; j < i; j++){
                  f[i] = min(f[i], 2 * f[j] + d[i - j]);
              }
          }
          for(int i = 1; i <= 12; i++)
              cout << f[i] << endl;
          return 0;
      }
      
      import java.util.Arrays;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = 20, n;
          int[] d = new int[N], f = new int[N];
          void run(){
              d[1] = 1;
              for(int i = 2; i <= 12; i++){
                  d[i] = d[i - 1] * 2 + 1;
              }
              Arrays.fill(f, 0x3f3f);
              f[1] = 1;
              for(int i = 2; i <= 12; i++){
                  for(int j = 1; j < i; j++){
                      f[i] = Math.min(f[i], 2 * f[j] + d[i - j]);
                  }
              }
      
              for(int i = 1; i <= 12; i++){
                  System.out.println(f[i]);
              }
          }
      }
      
    8. 97. 约数之和

      题目描述:求A^B的所有约数之和mod 9901

      数据规模: 1 <= A, B <= 5e7

      **分析:**暴力枚举肯定不可行,这里要将式子进行化简。A = p1^c1 * p2 ^ c2 * p3^c3…pn ^ cn

      A^B = p1^bc1 p2^Bc2 … pn^bcn

      题解:见分析

      时间复杂度:O(B)

      #include<iostream>
      #include<unordered_map>
      using namespace std;
      typedef long long LL;
      const int mod = 9901;
      int A, B;
      //保存质因子以及出现的次数
      unordered_map<int, int> primes;
      //试除法质因子分解
      void divide(int n){
          for(int i = 2; i <= n / i; i++){
              while(n % i == 0){
                  primes[i]++;
                  n /= i;
              }
          }
          //若最后一个数大于1,则该数为质数
          if(n > 1) primes[n]++;
      }
      
      //快速幂
      int qmi(int a, int b){
          int ans = 1;
          while(b){
              if(b & 1) ans = (LL) ans * a % mod;
              a = (LL) a * a % mod;
              b >>= 1;
          }
          return ans;
      }
      
      //p0 + p1 + .... + pk - 1
      int sum(int p, int k){
          //边界
          if(k == 1) return 1;
          //若k为偶数
          if(k % 2 == 0){
              return (LL)(qmi(p, k / 2) + 1) * sum(p, k / 2) % mod;
          }
          //若为奇数
          return (qmi(p, k - 1) + sum(p, k - 1)) % mod;
      }
      
      int main(){
          cin >> A >> B;
          //分解质因子
          divide(A);
      
          int ans = 1;
          for(auto it : primes){
              int p = it.first, k = it.second * B;
              ans = (LL) ans * sum(p, k + 1) % mod;
          }
          //特判若A为0则ans=0
          if(!A) ans = 0;
          cout << ans << endl;
          return 0;
      }
      
      import java.util.HashMap;
      import java.util.Map;
      import java.util.Scanner;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          Map<Integer, Integer> primes = new HashMap<>();
          int mod = 9901;
          int A, B;
          //试除法,质因子分解
          void divide(int n){
              for(int i = 2; i <= n / i; i++){
                  while(n % i == 0){
                      primes.put(i, primes.getOrDefault(i, 0) + 1);
                      n /= i;
                  }
              }
              if(n > 1) primes.put(n, primes.getOrDefault(n, 0) + 1);
          }
      
          //快速幂
          int qmi(int a, int b){
              int ans = 1;
              while(b > 0){
                  if((b & 1) == 1) ans = (int) ((long) ans * a % mod);
                  a = (int) ((long) a * a % mod);
                  b >>= 1;
              }
              return ans;
          }
      
          int sum(int p, int k){
              if(k == 1) return 1;
              if(k % 2 == 0){
                  return (int) ((long)(qmi(p, k / 2) + 1) * sum(p, k / 2) % mod);
              }
              return (qmi(p, k - 1) + sum(p, k - 1)) % mod;
          }
      
          void run(){
              Scanner sc = new Scanner(System.in);
              A = sc.nextInt();
              B = sc.nextInt();
              //分解质因子
              divide(A);
              int ans = 1;
              for(Map.Entry<Integer, Integer> entry : primes.entrySet()){
                  int p = entry.getKey(), k = entry.getValue() * B;
                  ans = (int) ((long) ans * sum(p, k + 1) % mod);
              }
              //特判
              if(A == 0) ans = 0;
              System.out.println(ans);
              sc.close();
          }
      }
      
    9. 98. 分形之城

      题目描述:城市扩建问题,这里第n个等级是由第n-1个等级变换而来,目标输入n组数据并给出编号,求这一组编号的距离x

      数据规模: 1 <= N <= 31, 1 <= A, B <= 4^N, 1 <= n <= 1000

      **分析:**我们可以从等级n开始向下递归,直到等级为0只有一个城市为止,然后反向根据当前城市等级和长度进行处理。这里显而易见, 第n个等级的len = 2^n-1, 第n-1等级所包含的城市cnt = 2^2n-2。这里我们让每个城市的编号-1,然后m对cnt取商就可以知道目标属于哪一块,有0、1、2、3四种取值,分别对应,四个位置。

      题解:这里求出了目标的位置就很容易的可以求出两点的距离

      时间复杂度:O(n*N)

      #include<iostream>
      #include<cmath>
      using namespace std;
      typedef long long LL;
      typedef pair<LL, LL> PLL;
      
      PLL calc(LL n, LL m){
          //边界条件
          if(n == 0) return {0, 0};
          //cnt, n-1层的个数, len是当前层的单位长度
          LL cnt = 1LL << 2 * n - 2, len = 1LL << n - 1;
          //递归求取位置
          auto pos = calc(n - 1, m % cnt);
          LL x = pos.first, y = pos.second;
          //看在哪一块
          int z = m / cnt;
          if(z == 0) return {y, x};
          if(z == 1) return {x, y + len};
          if(z == 2) return {x + len, y + len};
          return {2 * len - y - 1, len - x - 1};
      }
      int main(){
          int n;
          cin >> n;
          while(n--){
              LL N, A, B;
              cin >> N >> A >> B;
              auto a = calc(N, A - 1), b = calc(N, B - 1);//从0开始编号
              double x = a.first - b.first, y = a.second - b.second;
              printf("%.0lf\n", sqrt(x * x + y * y) * 10);
          }
          return 0;
      }
      
      import java.io.*;
      
      import java.util.StringTokenizer;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          class Pair{
              long f, s;
      
              public Pair(long f, long s) {
                  this.f = f;
                  this.s = s;
              }
          }
          Pair calc(long n, long m){
              if(n == 0) return new Pair(0, 0);
              long cnt = 1l << 2 * n - 2, len = 1l << n - 1;
              Pair pos = calc(n - 1, m % cnt);
              long x = pos.f, y = pos.s;
              long z = m / cnt;
              //这里变换默认是逆时针,且对应的是矩阵
              if(z == 0) return new Pair(y, x);
              if(z == 1) return new Pair(x, y + len);
              if(z == 2) return new Pair(x + len, y + len);
              return new Pair(2 * len - y - 1, len - x - 1);
          }
          BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
          StringTokenizer stringTokenizer;
          String next(){
              while(stringTokenizer == null || !stringTokenizer.hasMoreElements()){
                  try {
                      stringTokenizer = new StringTokenizer(bufferedReader.readLine());
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
              return stringTokenizer.nextToken();
          }
          long nextLong(){
              return Long.parseLong(next());
          }
          void run(){
              PrintWriter out = new PrintWriter(System.out);
              long n = nextLong();
              while(n-- > 0){
                  long N = nextLong(), A = nextLong(), B = nextLong();
                  Pair a = calc(N, A - 1), b = calc(N, B - 1);
                  double x = a.f - b.f, y = a.s - b.s;
                  out.printf("%.0f\n", Math.hypot(x, y) * 10);
              }
              out.flush();
              out.close();
          }
      }
      
    10. 99. 激光炸弹

      题目描述:地图上有N个目标,用整数(x, y)表示在地图上的位置,这里目标的位置可能相同,每个目标都有一个价值w,现在有一个炸弹能炸R*R的区域且必须与坐标轴平行,求一颗炸弹最多能炸掉地图上总价值最多的目标

      数据规模: 0 <= R <= 1e9, 0 <= N <= 10000, 0 <= x, y <= 5000, 0 <= w <= 1000

      **分析:**我们可以基于贪心的思想,用二维前缀和将区域的价值求出来,再用长度R扫描整个区域,并对ans进行更新求取该面积内的价值,这里ans代表总区域的最大价值。

      **TIPS:**这里R的规模可能很大,因此当它大于5001时,就取5001即可

      题解:见分析

      时间复杂度:O(5000 * 5000)

      #include<iostream>
      using namespace std;
      const int N = 5100;
      typedef long long LL;
      int sums[N][N], xmax, ymax;
      int main(){
          int n, k;
          cin >> n >> k;
          k = min(k, 5001);
          xmax = ymax = k;
          while(n--){
              int x, y, w;
              cin >> x >> y >> w;
              sums[++y][++x] += w;
              xmax = max(x, xmax),  ymax = max(ymax, y);//更新边界
          }
      
          int ans = 0;
          for(int i = 1; i <= ymax; i++){
              for(int j = 1; j <= xmax; j++){
                  sums[i][j] += sums[i - 1][j] + sums[i][j - 1] - sums[i - 1][j - 1];
              }
          }
      
          for(int i = k; i <= ymax; i++){
              for(int j = k; j <= xmax; j++){
                  //这里i-k,j-k减了两次
                  ans = max(ans, sums[i][j] - sums[i - k][j] - sums[i][j - k] + sums[i - k][j - k]);
              }
          }
          cout << ans << endl;
          return 0;
      }
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.io.StreamTokenizer;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
          int nextInt(){
              try {
                  streamTokenizer.nextToken();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return (int) streamTokenizer.nval;
          }
      
          int N = 5100;
          int[][] sums = new int[N][N];
          int xmax, ymax;
          void run(){
              int n = nextInt(), k = nextInt();
              k = Math.min(k, 5001);
              ymax = xmax = k;
              while(n-- > 0){
                  int x = nextInt(), y = nextInt(), w = nextInt();
                  x++;
                  y++;
                  sums[y][x] += w;
                  xmax = Math.max(xmax, x);
                  ymax = Math.max(ymax, y);
              }
      
              //计算前缀和
              for(int i = 1; i <= ymax; i++){
                  for(int j = 1; j <= xmax; j++){
                      sums[i][j] += sums[i - 1][j] + sums[i][j - 1] - sums[i - 1][j - 1];
                  }
              }
              //计算最大值
              int ans = 0;
              for(int i = k; i <= ymax; i++){
                  for(int j = k; j <= xmax; j++){
                      ans = Math.max(sums[i][j] - sums[i - k][j] - sums[i][j - k] + sums[i - k][j - k], ans);
                  }
              }
              System.out.println(ans);
          }
      }
      
      
    11. 100. 增减序列

      题目描述:给定一个长度为n的数列a1,a2,…an,每次可以选择一个区间[l, r]时下标在这个区间内都+1或-1

      数据规模: 0 <= n <= 1e5, 0 <= ai < 2147483648

      **分析:**这里我们可以基于差分的思想去对[l, r]内的数+1,或-1,能够在O(1)的时间内完成。这里l和r有以下几种取法

      1. 2 <= i <= j <= n应该在保证b[i]和b[j]两数符号相反
      2. i = 1, 2 <= j <= n
      3. 2 <= i <= n, j = n + 1

      题解:设从2到n中正数的和为pos,负数的和为neg

      这里最少操作次数为min(pos, neg) + |pos - neg|,解释:前面对应第一种操作,后面对应第2、3种操作

      最后序列的可能为|pos - neg| + 1,这里全选后缀n+1时为一种情况,其他的情况为|pos - neg|种,因此总的可能为|pos - neg| + 1

      时间复杂度:O(n)

      #include<iostream>
      using namespace std;
      const int N = 1e5 + 10;
      long long b[N];
      int main(){
          int n;
          cin >> n;
          for(int i = 1; i <= n; i++) cin >> b[i];
          for(int i = n; i > 1; i--) b[i] -= b[i - 1];
      
          long long pos = 0, neg = 0;
          for(int i = 2; i <= n; i++){
              if(b[i] > 0) pos += b[i];
              else neg -= b[i];
          }
      
          printf("%lld\n%lld", min(neg, pos) + abs(pos - neg), abs(pos - neg) + 1);
          return 0;
      }
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.io.StreamTokenizer;
      
      public class Main {
          public static void main(String[] args) {
              new Main().run();
          }
          int N = (int) (1e5 + 10);
          int[] b = new int[N];
          StreamTokenizer streamTokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
          int nextInt(){
              try {
                  streamTokenizer.nextToken();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return (int) streamTokenizer.nval;
          }
          void run(){
              int n = nextInt();
              for(int i = 1; i <= n; i++){
                  b[i] = nextInt();
              }
              for(int i = n; i > 1; i--){
                  b[i] -= b[i - 1];
              }
              long pos = 0, neg = 0;
              for(int i = 2; i <= n; i++){
                  if(b[i] > 0) pos += b[i];
                  else neg -= b[i];
              }
              System.out.println(Math.min(pos, neg) + Math.abs(pos - neg));
              System.out.println(Math.abs(pos - neg) + 1);
          }
      }
      
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值