Practice II



前言

智商游戏打不过 QAQ
被大佬们虐死了 QAQQQQ


The most distant state - UVA - 10085

题意
给定一个八数码,求从此八数码到最远状态的操作步骤和该状态的样子
思路
顺便复习一下康托展开(其实我忘了,只是康托展开部分照着模板打)
耗时900ms

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  #include <queue>
  #include <iomanip>
  #include <cmath>
  using namespace std;
  const int N = 5e6 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  bool used[N];
  int  pre[N];
  char  preop[N];
  int  dpt[N];
  const int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
  const int M = 9;
  queue<int> que;

  int cantor(int a[M]){
      int sum = 0;
      for(int i = 0; i < M; i++){
          int cnt = 0;
          for(int j = i + 1; j < M; j++){
              if(a[j] < a[i])     cnt++;
          }
          sum += cnt*fac[M - i - 1];
      }
      return sum;
  }

  void inv_cantor(int num, int a[]){
      bool used[M + 1] = {0};
      for(int i = 0; i < M; i++){
          int cnt = num/fac[M - i - 1];
          num %= fac[M - i - 1];

          int j;
          for(j = 0; j < M; j++){
              if(!used[j]){
                  if(cnt == 0)    break;
                  cnt--;
              }
          }
          a[i] = j;
          used[j] = true;
      }
  }

  int bfs(int s){
      int maxdpt = -1;
      int ansidx;
      while(!que.empty())     que.pop();
      memset(used, false, sizeof(used));
      dpt[s] = 0;
      pre[s] = -1;
      used[s] = true;
      que.push(s);

      while(!que.empty()){
          int u = que.front();
          que.pop();
          int a[M];
          inv_cantor(u, a);

          if(maxdpt < dpt[u]){
              ansidx = u;
              maxdpt = dpt[u];
          }

          int poszero;
          for(int i = 0; i < M; i++){
              if(a[i] == 0){
                  poszero = i;
                  break;
              }
          }

          if(poszero > 2){
              swap(a[poszero - 3], a[poszero]);
              int v = cantor(a);
              if(used[v] == false){
                  dpt[v] = dpt[u] + 1;
                  pre[v] = u;
                  preop[v] = 'U';
                  que.push(v);
                  used[v] = true;
              }
              swap(a[poszero - 3], a[poszero]);
          }
          if(poszero%3 != 0){
              swap(a[poszero - 1], a[poszero]);
              int v = cantor(a);
              if(used[v] == false){
                  dpt[v] = dpt[u] + 1;
                  pre[v] = u;
                  preop[v] = 'L';
                  que.push(v);
                  used[v] = true;
              }
              swap(a[poszero - 1], a[poszero]);
          }
          if(poszero%3 != 2){
              swap(a[poszero + 1], a[poszero]);
              int v = cantor(a);
              if(used[v] == false){
                  dpt[v] = dpt[u] + 1;
                  pre[v] = u;
                  preop[v] = 'R';
                  que.push(v);
                  used[v] = true;
              }
              swap(a[poszero + 1], a[poszero]);
          }

          if(poszero <= 5){
              swap(a[poszero + 3], a[poszero]);
              int v = cantor(a);
              if(used[v] == false){
                  dpt[v] = dpt[u] + 1;
                  pre[v] = u;
                  preop[v] = 'D';
                  que.push(v);
                  used[v] = true;
              }
              swap(a[poszero + 3], a[poszero]);
          }
      }
      return ansidx;
  }

  int main(){
      int t, csn = 1;
      scanf("%d", &t);
      while(t--){
          int a[M];
          for(int i = 0; i < M; i++)      scanf("%d", &a[i]);

          int u = cantor(a);
          int ansidx = bfs(cantor(a));
          inv_cantor(ansidx, a);
          //printf("%d %d\n", u, ansidx);

          string s;
          while(pre[ansidx] != -1){
              s.push_back(preop[ansidx]);
              ansidx = pre[ansidx];
          }
          reverse(s.begin(), s.end());

          printf("Puzzle #%d\n", csn++);
          for(int i = 0; i < M; i++){
              printf("%d", a[i]);
              if(i%3 == 2)    putchar('\n');
              else            putchar(' ');
          }
          printf("%s\n\n", s.c_str());
      }
  }


Maximum Subsequence - CodeForces - 888E

题意
给定一个序列和整数m,求其中选择某些数字,它们的和模m的最大值(可以不选)
思路
由于n给定的最大范围是35,而根据 (a%m + b%m)%m = (a + b)%m,可知区间具有合并性,因此用meet-in-the-middle的思维解
把区间分成两半,前半段跑dfs,将结果存入set中
后半段跑dfs的结果,记为sum(已模m),在set中不断寻找最接近 m-sum 或者 2*m-sum 的值,记为x,不断更新答案 ans = max(ans, (x + sum)%m)
最后就会TLE,主要是被set拖的,换成数组跑完第一个dfs后sort再unique再跑第二个dfs即可,(耗时62ms,没想到set这么拖时间 = =|| )

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  #include <queue>
  #include <vector>
  using namespace std;
  const int N = 3e5 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  int a[N];
  int b[N], bp;

  void dfs1(int i, int n, int mod, ll sum){
      if(i > n){
          b[bp++] = sum%mod;
          return;
      }
      dfs1(i + 1, n, mod, sum + a[i]);
      dfs1(i + 1, n, mod, sum);
  }

  void dfs2(int i, int n, int mod, ll sum, int& ans){
      if(i > n){
          sum = sum%mod;

          int pos = lower_bound(b, b + bp, mod - sum) - b;
          if(pos != 0){
              ans = max(ans, (int)(b[pos - 1] + sum));
          }

          pos = lower_bound(b, b + bp, 2*mod - sum) - b;
          if(pos != 0){
              ans = max(ans, (int)(b[pos - 1] + sum)%mod);
          }
          return;
      }
      dfs2(i + 1, n, mod, sum + a[i], ans);
      dfs2(i + 1, n, mod, sum, ans);
  }

  int main(){
      int n, mod;
      while(~scanf("%d%d", &n, &mod)){
          bp = 0;
          for(int i = 1; i <= n; i++)     scanf("%d", &a[i]);

          int ans = 0;
          dfs1(1, (n + 1)/2, mod, 0);
          sort(b, b + bp);
          bp = unique(b, b + bp) - b;
          dfs2((n + 1)/2 + 1, n, mod, 0, ans);
          printf("%d\n", ans);
      }
  }


Bubble Sort Graph | CodeForces - 340D

题意
在冒泡排序中,若给需要交换的两个数连上一条边,问冒泡排序结束后,独立集的最大大小是多少
思路
先考虑一下什么情况下两个数会连上一条边——当前面的数大于后面的数的情况下会,因此所有的逆序对都会连边
反过来考虑,则不连边的两个数必定不逆序,那么选出来的独立集也就是非降序的
那么,不就是LIS么,直接套模板

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  using namespace std;
  const int N = 1e5 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  int a[N];

  int main(){
      int n;
      while(~scanf("%d", &n)){
          int ans = 0;
          for(int i = 0; i < n; i++){
              int tmp;
              scanf("%d", &tmp);
              int pos = upper_bound(a, a + ans, tmp) - a;
              a[pos] = tmp;
              if(pos == ans)  ans++;
          }
          printf("%d\n", ans);
      }
  }


Soldier and Number Game - CodeForces - 546D

题意
给定一个数 n = a!/b!,每次操作中,选择一个数x满足n%x == 0,令 n = n/x,直到 n = 1 为止,求最大操作次数
思路
实际上 n = (b+1)(b+2)(…)(a),要使操作次数最大,那么除的数当然越小越好,因此选每个数除的次数就等于自身质因子的个数(按重数计)
用素数筛法先筛出素数,再用打表思想储存每个数的质因子个数,作前缀和,答案就是 sum[a] - sum[b]

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  #include <queue>
  #include <iomanip>
  #include <vector>
  using namespace std;
  const int N = 5e6 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  ll   sum[N];
  int  prime[N], pp;
  bool used[N];

  void init(){
      memset(used, 0, sizeof(used));
      fill(sum, sum + N, 1);
      pp = 0, sum[0] = sum[1] = 0;
      for(int i = 2; i < N; i++){
          if(sum[i] == 1){
              prime[pp++] = i;
              for(int j = 2; i*j < N; j++){
                  sum[i*j] = 0;
              }
          }
      }
      for(int i = 2; i < N; i++){
          for(int j = 0; prime[j] <= i && prime[j]*i < N && j < pp; j++){
              if(used[i*prime[j]])     continue;
              sum[i*prime[j]] += sum[i] + 1;
              used[i*prime[j]] = true;
          }
      }
      for(int i = 2; i < N; i++)      sum[i] += sum[i - 1];
  }

  int main(){
      init();

      int q;
      scanf("%d", &q);
      while(q--){
          int a, b;
          scanf("%d%d", &a, &b);
          printf("%lld\n", sum[a] - sum[b]);
      }
  }


Sagheer, the Hausmeister - CodeForces - 812B

题意
给定一张图,从左下角出发,每次只能遍历完每一行所有的1后,才能从最左端或最右端走到上一行,直到遍历完1为止,求最小移动次数
思路1 - DP
训练的时候,用DP结果WA了三发,结果用穷举过了,训练后才来补DP
首先预处理一下每一行从左右两端各自出发走到最后一个1所需要的步数
定义DP状态为 dp[i][j][k],在第i层从j(左端0/右端1)走到k(左端0/右端1)所需步数,另定义 a[i][j][k] 为本层从j到k所需步数,则
dp[i][0][1] = min(dp[i - 1][0][0], dp[i - 1][1][0]) + a[i][0][1]
dp[i][0][0] = min(dp[i - 1][0][0], dp[i - 1][1][0]) + a[i][0][0]
dp[i][1][1] = min(dp[i - 1][0][1], dp[i - 1][1][1]) + a[i][1][1]
dp[i][1][0] = min(dp[i - 1][0][1], dp[i - 1][1][1]) + a[i][1][0]
其中,a[i][0][1] = a[i][1][0] = 列数 - 1,而a[i][0][0] = 从左端走到最后一个1所需步数*2a[i][1][1]同理
另外,不全为0的最顶层需要最后特判一下,因为不需要走到完
本DP方程有个Bug,那就是只有1层不能用,也需要特判一下

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  #include <queue>
  #include <iomanip>
  using namespace std;
  const int N = 100 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  char G[N][N];
  int lpos[N], rpos[N];
  int dp[N][2][2];

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          m += 2;
          for(int i = n; i >= 1; i--){
              getchar();
              for(int j = 1; j <= m; j++){
                  G[i][j] = getchar();
              }
          }
          for(int i = 1; i <= n; i++){
              lpos[i] = 0;
              rpos[i] = 0;
              for(int j = 1; j <= m; j++){
                  if(G[i][j] == '1'){
                      lpos[i] = m - j;
                      break;
                  }
              }
              for(int j = m; j >= 1; j--){
                  if(G[i][j] == '1'){
                      rpos[i] = j - 1;
                      break;
                  }
              }
              //printf("lpos: %d  rpos: %d\n", lpos[i], rpos[i]);
          }

          for(int i = n; i >= 2; i--){
              bool flag = true;
              for(int j = 1; j <= m; j++){
                  if(G[i][j] == '1'){
                      flag = false;
                      break;
                  }
              }
              if(!flag)   break;
              n--;
          }

          for(int i = 1; i <= n - 1; i++){
              dp[i][0][1] = m - 1;
              dp[i][1][0] = m - 1;
              dp[i][0][0] = rpos[i] * 2;
              dp[i][1][1] = lpos[i] * 2;
              //printf("01:%d 10:%d 00:%d 11:%d\n", dp[i][0][1], dp[i][1][0], dp[i][0][0], dp[i][1][1] );
          }
          dp[1][1][0] = dp[1][1][1] = inf;

          if(n == 1){
              printf("%d\n", rpos[n]);
          }else{
              for(int i = 2; i <= n - 1; i++){
                  int lans = min(dp[i - 1][0][0], dp[i - 1][1][0]) + 1;
                  dp[i][0][1] += lans;
                  dp[i][0][0] += lans;

                  int rans = min(dp[i - 1][0][1], dp[i - 1][1][1]) + 1;
                  dp[i][1][1] += rans;
                  dp[i][1][0] += rans;
              }

              int lans = min(dp[n - 1][0][0], dp[n - 1][1][0]) + 1;
              int rans = min(dp[n - 1][0][1], dp[n - 1][1][1]) + 1;
              int ans = min(lans + rpos[n], rans + lpos[n]);
              printf("%d\n", ans);
          }
      }
  }

思路2 - 穷举
其实就DFS,每次穷举左右两端,最后取结果最小值

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  #include <queue>
  #include <iomanip>
  using namespace std;
  const int N = 100 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  char G[N][N];
  int lpos[N], rpos[N];

  void dfs(int nxt, int dpt, int sum, int n, int m, int& ans){
      int cur = nxt;
      if(dpt == n){
          int tmp;
          if(cur == 0)    tmp = sum + rpos[dpt];
          else            tmp = sum + lpos[dpt];
          ans = min(ans, tmp);
          return;
      }

      if(cur == 0){
          dfs(0, dpt + 1, sum + 2*rpos[dpt] + 1, n, m, ans);
          dfs(1, dpt + 1, sum + m, n, m, ans);
      }else{
          dfs(1, dpt + 1, sum + 2*lpos[dpt] + 1, n, m, ans);
          dfs(0, dpt + 1, sum + m, n, m, ans);
      }
  }

  int main(){
      int n, m;
      while(~scanf("%d%d", &n, &m)){
          m += 2;
          for(int i = n; i >= 1; i--){
              getchar();
              for(int j = 1; j <= m; j++){
                  G[i][j] = getchar();
              }
          }
          for(int i = 1; i <= n; i++){
              lpos[i] = 0;
              rpos[i] = 0;
              for(int j = 1; j <= m; j++){
                  if(G[i][j] == '1'){
                      lpos[i] = m - j;
                      break;
                  }
              }
              for(int j = m; j >= 1; j--){
                  if(G[i][j] == '1'){
                      rpos[i] = j - 1;
                      break;
                  }
              }
              //printf("lpos: %d  rpos: %d\n", lpos[i], rpos[i]);
          }

          for(int i = n; i >= 2; i--){
              bool flag = true;
              for(int j = 1; j <= m; j++){
                  if(G[i][j] == '1'){
                      flag = false;
                      break;
                  }
              }
              if(!flag)   break;
              n--;
          }

          int ans = inf;
          dfs(0, 1, 0, n, m, ans);

          printf("%d\n", ans);
      }
  }


Points and Powers of Two - CodeForces - 988D

题意
给定n个点,求点点距离为2的n次方的最大集合
思路
可以大概感觉得出,答案不会超过3,因为举不出任何一个有4个点的例子 QAQ
于是就对原数组sort,枚举长度查找可能性
最后耗时686ms

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  using namespace std;
  const int N = 2e5 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  ll a[N];

  int main(){
      int n;
      while(~scanf("%d", &n)){
          for(int i = 0; i < n; i++)     scanf("%lld", &a[i]);
          sort(a, a + n);

          int ansnum = 1;
          int x, y, z;
          for(ll len = 1; len <= (ll)1e10; len <<= 1){
              for(int i = 0; i < n; i++){
                  int p1 = lower_bound(a, a + i, a[i] - len) - a;
                  int p2 = lower_bound(a + i + 1, a + n, a[i] + len) - a;
                  if(p1 < n && p2 < n && a[i] - a[p1] == len && a[p2] - a[i] == len){
                      ansnum = 3;
                      x = p1, y = i, z = p2;
                      break;
                  }
                  if(p1 < n && a[i] - a[p1] == len){
                      ansnum = 2;
                      x = p1, y = i;
                  }
                  if(p2 < n && a[p2] - a[i] == len){
                      ansnum = 2;
                      x = i, y = p2;
                  }
              }
              if(ansnum == 3)     break;
          }

          if(ansnum == 1)         printf("1\n%lld\n", a[0]);
          else if(ansnum == 2)    printf("2\n%lld %lld\n", a[x], a[y]);
          else                    printf("3\n%lld %lld %lld\n", a[x], a[y], a[z]);
      }
  }


Doctor - CodeForces - 84D

题意
每个动物i需要看病ai次,他们着排队到医生那么看病,对于每个动物i,如果看完这一次后还需要看病,那么他们就会排到队尾去,否则直接出队伍,问医生接待k个动物后队列的样子(按重数计),如果接待不满k个动物,则输出-1
思路
实际上,可以固定队列,而用一个下标的移动来代替动物重新到队尾
即循环不断的将数组中的元素减1,为0的元素跳过不减,直到减了k次
那么可以把答案二分出来,寻找一个数num使得答案b <= k,剩余的k - ans部分则从数组开始减1,减到最后那个位置的下一位就是队伍头,最后从队伍头开始遍历数组中还不为0的数字,保存下标,最后输出即可
还有一个问题是什么时候为-1,那就是剩下的k - ans的部分没有分配完的时候会为-1,此时说明接待不满,才会整个数组为0了却还有剩余

  #include <cstdio>
  #include <cstring>
  #include <algorithm>
  #include <iostream>
  #include <vector>
  using namespace std;
  const int N = 1e5 + 15;
  const int inf = 0x3f3f3f3f;
  typedef long long ll;

  int a[N];

  ll judge(int b, int n){
      ll sum = 0;
      for(int i = 1; i <= n; i++){
          sum += min(a[i], b);
      }
      return sum;
  }

  int main(){
      int n;
      ll k;
      while(~scanf("%d%lld", &n, &k)){
          vector<int> vec;
          for(int i = 1; i <= n; i++)     scanf("%d", &a[i]);

          int l = 0, r = (int)1e9;
          int b;
          ll anssum;
          while(l <= r){
              int m = (l + r) >> 1;
              ll tmp = judge(m, n);
              if(tmp <= k){
                  b = m;
                  anssum = tmp;
                  l = m + 1;
              }else{
                  r = m - 1;
              }
          }
          for(int i = 1; i <= n; i++){
              a[i] = max(a[i] - b, 0);
          }

          int endpos = n;
          int i = 1, j = 1;
          for(i = 1, j = 1; i <= n && j <= k - anssum; i++){
              if(a[i]){
                  a[i]--;
                  j++;
                  endpos = i;
              }
          }

          if(j > k - anssum){
              int startpos = endpos + 1;
              if(startpos == n + 1)   startpos = 1;
              while(startpos != endpos){
                  if(a[startpos])     vec.push_back(startpos);
                  startpos++;
                  if(startpos == n + 1)   startpos = 1;
              }
              if(a[endpos])   vec.push_back(endpos);

              for(int i = 0; i < vec.size(); i++){
                  printf("%d", vec[i]);
                  if(i < vec.size() - 1)  putchar(' ');
              }
              puts("");
          }else{
              puts("-1");
          }

      }
  }