XJTU-ACM暑期训练部分题解

1A 棋盘问题(POJ 1321)

题目大意: 给出一个不规则的棋盘, 求解摆放k个棋子的可行方案数

解题思路:

我首先的思路是采用dp的方法解决:

状态空间 f(i,S,x) , 代表选择了 [i+1,n] 行, 在 i+1 行只能选择 {x|xS} 列并且使用了 x 个棋子的情况下的可行方案数, 另外令mask(i)为第 i 行棋盘的可行列的集合, 这样一来, 状态转移方程为

f(i,S,0)=1f(n1,S,1)=count(x|xmask(n1)xS)f(i,S,x)=m=0n1f(i+1,Sm,x1) for mmask(n1)mS

其中 f(0,[1,n],k) 即为所求的解, 状态空间大小为 kn2n , 转移开销为 O(n) , 复杂度为 O(kn22n) , 数据不大( n8,kn )可过.

后来发现搜索也可以做这道题, 复杂会低一些

代码:

#include <cstring>
#include <iostream>
#include <cstdio>
using namespace std;

int n, k;

int f[10][1024][10];
int mask[10];
char str[10];

int main(){
  while(1){
    memset(f, 0, sizeof(f));
    memset(mask, 0, sizeof(mask));
    scanf("%d%d", &n, &k);
    if(n == -1 && k == -1) return 0;
    for(int i = 0; i < n; i++){
      scanf("%s", str);
      int ret = 0;
      for(int j = 0; str[j]; j++){
        if(str[j] == '.') ret |= (1 << j);
      }
      mask[i] = ret;
    }
    for(int j = (1 << n) - 1; j >= 0; j--){
      f[n-1][j][0] = 1;
      for(int m = 0; m < n; m++){
        f[n-1][j][1] += ((j & (1 << m)) == 0 && (mask[n-1] & (1 << m)) == 0);
      }
    }
    for(int i = n-2; i >= 0; i--){
      for(int j = (1 << n) - 1; j >= 0; j--){
        f[i][j][0] = 1;
        for(int x = 1; x <= k; x++){
          for(int m = 0; m < n; m++){
            if((j & (1 << m)) == 0 && (mask[i] & (1 << m)) == 0){
              f[i][j][x] += f[i+1][j | (1 << m)][x-1];
            }
          }
          f[i][j][x] += f[i+1][j][x];
        }
      }
    }
    printf("%d\n", f[0][0][k]);
  }
}

1B Channel Allocation(POJ 1129)

题目大意: 无向图染色, 问最少需要颜色的个数.

解题思路: 维护每个节点可以使用的channel, 再对节点做一遍遍历, 每次都选编号最小的channel, 这样选出来的方案就是最优方案.

复杂度: O(n3) , 题目数据限制 n26 .

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

int n;
char s[32];
int availble[30];

int main(){
  while(1){
    int ma = 0;
    memset(availble, 0, sizeof(availble));
    scanf("%d", &n);
    if(!n) return 0;
    for(int i = 0; i < n; i++){
      int p = -1;
      for(int j = 0; j < n; j++){
        if((availble[i] & (1 << j)) == 0){
          ma = max(ma, j+1);
          p = j;
          break;
        }
      }
      scanf("%s", s);
      for(int j = 2; s[j]; j++){
        availble[s[j] - 'A'] |= (1 << p);
      }
    }
    printf("%d channel%s needed.\n", ma, ma > 1 ? "s" : "");
  }
}

1H Desert King(POJ 2728)

题目大意: 在一个空间内有 n 个点求, 一个生成树, 使得

costlength=ΔzΔx2+Δy2

最小.

解题思路: 这是一道0-1分数规划的题, 所谓0-1分数规划, 就是从集合 {(x,y)|x>0y>0} 里选择元素, 使得

W=ΣxiΣyi

取得最大值.

首先对于这一类问题, 简单的贪婪算法是不可行的:

如总是选择最大的 xi 的策略, 对于 S={(2,4),(1,1),(1,1)} 不能得出最优结果,

总是选择最大的 xi/yi 的策略, 对于 S={(100,100),(1000,2000),(1,4)} 不能得出最优结果.

解决方案是对公式进行变形:

首先选择一个0-1向量 c 来代表是否选择某个元素, 如果找到一个 X 满足

c,Xni=1cixini=1ciyi

只要存在 c 使得可以 W 可以取得这样的X, 这个 X 就是原问题的解, 可以证明这个XX就是满足条件的所有 X 的最小值, 并且显然小于此X的值都不满足条件, 只要找到一个更好的验证方法, 就可以二分快速求解答案.

对原式变换得

c,i=1nci(yiXxi)0

即左式
i=1nci(yiXxi)
的最小值大于等于0, 则此 X 满足条件, 问题转化为求左式的最小值.

对于此题, 求左式的最小值就是以左式为边的权值求最小生成树, 本题可以用Prim算法求解, 复杂度为O(N2), 又由于精确度固定, 二分可以看成进行常数次操作, 所以总复杂度为 O(N2) .

另外除了二分法以外, 还可以使用Dinkelbach算法, 每次如果找到的

i=1nci(yiXxi)
为负的话, 以此 c 生成一个新的解, 用在此题可以减少迭代次数.

代码:

#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;

int n;
int xi[1010], yi[1010], zi[1010];
double dis[1010][1010];
double dist[1010][1010];
int visited[1010];
double lowcost[1010];
int lowedgei[1010];

void initdist(double x){
  for(int i = 0; i < n; i++){
    for(int j = i+1; j < n; j++){
      dist[j][i] = dist[i][j] = fabs(zi[i] - zi[j]) * x - dis[i][j];
    }
  }
}

int dinkelbach(double& x){
  // printf("testing %.3f\n", 1/x);

  initdist(x);
  memset(visited, 0, sizeof(visited));

  double sum = 0, costsum = 0;
  visited[0] = 1;

  for(int i = 1; i < n; i++){
    lowcost[i] = dist[0][i];
    lowedgei[i] = 0;
  }

  for(int i = 1; i < n; i++){
    double mi = 1e30, cost = 0;
    int mip = -1;
    for(int j = 0; j < n; j++){
      if(!visited[j] && lowcost[j] < mi){
        mip = j;
        mi = dist[lowedgei[j]][j];
        cost = fabs(zi[j] - zi[lowedgei[j]]);
      }
    }
    for(int j = 0; j < n; j++){
      if(!visited[j] && dist[mip][j] < lowcost[j]){
        lowcost[j] = dist[mip][j];
        lowedgei[j] = mip;
      }
    }
    visited[mip] = 1;
    sum += mi;
    costsum += cost;
  }

  if(1 / x - costsum / (costsum * x - sum) > 1e-6){
    x -= sum / costsum;
    return 1;
  }
  else{
    return 0;
  }

}

int main(){
  while(1){
    scanf("%d", &n);
    if(!n) break;
    for(int i = 0; i < n; i++){
      scanf("%d%d%d", &xi[i], &yi[i], &zi[i]);
    }
    double x = 0;
    for(int i = 0; i < n; i++){
      for(int j = i+1; j < n; j++){
        dis[i][j] = hypot(xi[i] - xi[j], yi[i] - yi[j]);
      }
    }
    while(dinkelbach(x))
      ;
    printf("%.3f\n", 1 / x);
  }
  return 0;
}

1I The Cow Lexicon(POJ 3267)

题目大意: 给定一个字典字符串的集合和一个目标字符串, 求最少从目标字符串里面删除多少个字符才能使得该字符串可以作为由若干个字典字符串连接起来的字符串.

解题思路: 对于空字符串, 解为0, 而对于每个非空字符串, 它的解都可以是删除它第一个字符之后的字符串的解再加一, 如果它的子序列包含某个字典词, 并且它的第一个元素在此子序列中, 那么它的解也可以是删除的子序列字符之外的字符个数再加上子序列结束后的字符串的解,此两者取最小值,即得到每个字符串的解,也就是可以用dp求解原问题.

状态空间: f(x) 代表取子串 [x,n] 需要删除的字符个数

状态转移: f(n)=0 , f(x)=min(f(x+1),f(y)+n)

复杂度: O(WL2)

代码:

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

vector<string> vs;
int dp[310];

int main(){
  int w, l;
  cin >> w >> l;
  string s;
  cin >> s;
  for(int i = 0; i < w; i++){
    string st;
    cin >> st;
    vs.push_back(st);
  }
  for(int i = l - 1; i >= 0; i--){
    dp[i] = dp[i+1] + 1;
    for(int j = 0; j < w; j++){
      if(vs[j][0] == s[i] && vs[j].size() <= l - i){
        int last = -1;
        for(int k = 0, x = i; ; k++, x++){
          if(k == vs[j].size() && x <= l){
            last = x;
            break;
          }
          if(x >= l){
            break;
          }
          while(vs[j][k] != s[x] && x < l){
            x++;
          }
        }
        if(last != -1){
          dp[i] = min(dp[i], (int)(last - i - vs[j].size() + dp[last]));
        }
      }
    }
  }
  cout << dp[0] << endl;
  return 0;
}

1J Find The Multiple(POJ 1426)

题目大意: 给出一个数 n200 , 求一个只包含0和1的数使得 n 可以通过乘一个非零正数来得到这个数

解题思路: 枚举只包含0和1的数, 此处可以用python枚举2进制数, 然后转化为字符串, 再转化为10进制数, 因为数据量小直接打表即可.

代码:

for i in range(1, 201):
    j = 1
    while True:
        if int(str(bin(j))[2:]) % i == 0:
            print('"',int(str(bin(j))[2:]),'"',',',sep='')
            break
        j += 1
#include <cstdio>
char table[210][110]{
  "0",
  ...
}

int main(){
  int n;
  while(scanf("%d", &n) && n){
    printf("%s\n", table[n-1]);
  }
}

2B Map Labeler(POJ 2296)

题目大意: 给出一个坐标系上的若干点,问把每个点作为一个正方形的上底边或下底边,保证所有正方形不重合(边恰好重合不算重合)的情况下,求正方形的最小边长.

解题思路: 由解的单调性可知可以二分答案求解, 问题的关键在于如何验证一个答案, 因为每个点只能作为上底边或者下底边, 可以把每个点i看做一个bool变量 ai , 为了保证不重合, 会有一些约束条件作用于这些变量, 于是问题转化为求是否存在一个 a 使得所有约束条件得到满足, 又称为SAT问题, SAT问题是NP完全的, 但由于问题涉及的都是两个正方形是否重合的关系, 即约束条件都是两个 ai , 也即是一种2-SAT问题, 可以化为所有子句不超过两个命题变元的合取范式.

解决2-SAT问题, 如果使用暴力的方法需要O(2^n) O(2n) 的时间复杂度, 显然是在大多数情况不可行的, 需要一种新的算法解决. 可以知道的是, 如果约束条件里存在a_i \iff \lnot a_i ai¬ai 或, 则一定不存在解. 由于\to 具有传递的性质, 因此可以得到所有的\to 的关系, 如果不存在a_i \iff \lnot a_i ai¬ai , 则一定有解, 要么 ai¬ai , 此时选 ai 为解, 否则 ¬aiai , 选 ¬ai 为解.

首先把原来的合取范式子式全部化为只含有 ¬ 的式子, 如果把所有 ai ¬ai 作为图的节点, 把所有 关系作为图中的有向边, 可以化为强连通分量问题, 在每个强连通分量内不同时存在 ai ¬ai 即满足条件.

构造强连通分量需要 O(n) 的时间复杂度, 遍历每两个点构造图需要 O(n2) 的时间复杂度, 二分需要 O(logXmax) 的复杂度, 于是总的复杂度为 O(n3logXmax) .

代码:

#include <vector>
#include <cstring>
#include <cstdio>
using namespace std;

int xi[110], yi[110];
int mat[220][220];
int visited[220];
int cmp[220];
vector<int> dfsq;
int n;

void dfs(int x){
  visited[x] = 1;
  for(int i = 0; i < 2*n; i++){
    if(!visited[i] && mat[x][i]){
      dfs(i);
    }
  }
  dfsq.push_back(x);
}

void rdfs(int x, int k){
  visited[x] = 1;
  cmp[x] = k;
  for(int i = 0; i < 2*n; i++){
    if(!visited[i] && mat[i][x]){
      rdfs(i, k);
    }
  }
}

void scc(){
  dfsq.clear();
  memset(visited, 0, sizeof(visited));
  for(int i = 0; i < 2*n; i++){
    if(!visited[i]) dfs(i);
  }
  memset(visited, 0, sizeof(visited));
  int k = 0;
  for(int i = 2*n - 1; i >= 0; i--){
    if(!visited[dfsq[i]]) rdfs(dfsq[i], k++);
  }
}

//down +n
int check(int x){
  memset(mat, 0, sizeof(mat));
  for(int i = 0; i < n; i++){
    for(int j = i + 1; j < n; j++){
      if(xi[i] - xi[j] < x && xi[i] - xi[j] > -x){
        if(yi[i] > yi[j]){
          if(yi[i] - yi[j] < x){
            mat[i][j+n] = 1;
            mat[j+n][i] = 1;
            mat[i+n][i] = 1;
            mat[j][j+n] = 1;
          }
          else if(yi[i] - yi[j] < 2*x){
            mat[i+n][j+n] = 1;
            mat[j][i] = 1;
          }
        }
        if(yi[j] > yi[i]){
          if(yi[j] - yi[i] < x){
            mat[i+n][j] = 1;
            mat[j][i+n] = 1;
            mat[i][i+n] = 1;
            mat[j+n][j] = 1;
          }
          else if(yi[j] - yi[i] < 2*x){
            mat[j+n][i+n] = 1;
            mat[i][j] = 1;
          }
        }
        if(yi[i] == yi[j]){
          mat[i][j+n] = 1;
          mat[j+n][i] = 1;
          mat[i+n][j] = 1;
          mat[j][i+n] = 1;
        }
      }
    }
  }
  scc();
  for(int i = 0; i < n; i++){
    if(cmp[i] == cmp[i+n]){
      return 0;
    }
  }
  return 1;
}

int main(){
  int kase;
  scanf("%d", &kase);
  for(int k = 0; k < kase; k++){
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
      scanf("%d%d", &xi[i], &yi[i]);
    }
    int low = 0, high = 10001;
    while (high - low > 1) {
      int mid = (low + high) / 2;
      if(check(mid)) low = mid; else high = mid;
    }
    printf("%d\n", low);
  }
  return 0;
}

2E Maya Calendar(POJ 1008)

题目大意: 玛雅有两种历法系统, 给定一种历法里的日期. 换算成另一种历法里的日期输出.

解题思路: 本题只要看懂了没有什么难度. 关于日期转换的问题. 我认为最好的解决方法是把日期化成一个整数, 再进行转换.

代码:

#include <cstdio>
#include <cstring>
using namespace std;

char haabmonth[19][10] = {
  "pop",
  "no",
  "zip",
  "zotz",
  "tzec",
  "xul",
  "yoxkin",
  "mol",
  "chen",
  "yax",
  "zac",
  "ceh",
  "mac",
  "kankin",
  "muan",
  "pax",
  "koyab",
  "cumhu",
  "uayet",
};

int haabsum[19] = {
  0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300, 320, 340, 360,
};

char tzolkin[20][10] = {
  "imix",
  "ik",
  "akbal",
  "kan",
  "chicchan",
  "cimi",
  "manik",
  "lamat",
  "muluk",
  "ok",
  "chuen",
  "eb",
  "ben",
  "ix",
  "mem",
  "cib",
  "caban",
  "eznab",
  "canac",
  "ahau",
};

char s[10];

int main(){
  int n;
  scanf("%d", &n);
  printf("%d\n", n);
  for(int i = 0; i < n; i++){
    int a, b;
    scanf("%d. %s %d", &a, s, &b);
    int c = -1;
    for(int j = 0; j < 19; j++){
      if(!strcmp(s, haabmonth[j])){
        c = j;
        break;
      }
    }
    int  = b * 365 + haabsum[c] + a;
    // printf("%d\n", );
    int ra, rb, rc;
    rc =  / 260;
    rb =  % 20;
    ra =  % 13 + 1;
    printf("%d %s %d\n", ra, tzolkin[rb], rc);
  }
  return 0;
}

2F THE DRUNK JAILER(POJ 1218)

题目大意: 有一间监狱, 有 n 个房间一开始全部关闭, 然后先改变1,2,3...n的状态, 再改变 2,4,6...n 的状态, 以此类推进行 n 次, 问最后多少房间开着.

解题思路: 由于数据不大, 可以直接O(n2)模拟解决, 不过更好的办法是对状态的改变进行分析, 发现一个房间状态的改变次数就是它编号的因数个数, 因为因数总是成对出现的, 而只有完全平方数具有一对因数相等的因数对, 因此只有完全平方数具有奇数个因数, 经历了奇数次变换, 因此问题转化为求给定范围 [1,n] 中完全平方数的个数, 即 n .

//模拟代码
#include <cstdio>
#include <cstring>
using namespace std;

int unlocked[110];

int main(){
  int n;
  scanf("%*d");
  while(scanf("%d", &n) == 1){
    memset(unlocked, 0, sizeof(unlocked));
    int cnt = 0;
    for(int i = 1; i <= n; i++){
      for(int j = i; j <= n; j += i){
        unlocked[j] = !unlocked[j];
      }
    }
    for(int i = 1; i <= n; i++){
      if(unlocked[i]){
        cnt++;
      }
    }
    printf("%d\n", cnt);
  }
  return 0;
}

2G Bridging signals(POJ 1631)

题目大意: 给定左右两列点和一些连线, 求最少删除多少条才能使所有线不交叉

解题思路: 既然连线不能交叉, 也就是左点连到的右点要有序上升, 又要求最少删除, 可以化为一个最长上升子序列问题, 贴模板即可求解.

#include <cstdio>
#include <algorithm>
using namespace std;
int arr[40010];
int m;

int upbound(int l, int h, int x){
  if(h - l == 1) return x > arr[l] ? h : l;
  else return x > arr[(l + h) / 2] ? upbound((l + h) / 2, h, x) : upbound(l, (l + h) / 2, x);
}

int main(){
  int kase;
  scanf("%d", &kase);
  for(int i = 0; i < kase; i++){
    int n;
    scanf("%d", &n);
    m = 0;
    for(int j = 0; j < n; j++){
      int x;
      scanf("%d", &x);
      int k = upbound(0, m + 1, x);
      arr[k] = x;
      m = max(k, m);
    }
    printf("%d\n", m);
  }
  return 0;
}

2H EXTENDED LIGHTS OUT(POJ 1222)

题目大意: 给定一个5x6的0-1矩阵, 可以选择操作某个点, 这会改变此点上下左右和自身的状态, 求一种操作方式把所有点都置为0.

解题思路: 因为对点的操作是幂等的, 所以最后的结果是是否改变某个点, 但对于5x6的矩阵, 也就是有 n=30 个元素, 直接搜索的话复杂度会达到 O(2n) , 会超时, 需要另外一种算法来降低复杂度.

首先把5x6的状态矩阵”展平”为30个元素的向量 a , 答案矩阵”展平”为向量 b , 通过观察可以发现, 其实可以看做 axor(Mxorb)=0 , 其中 M 的每一行Mi都是 i 会影响到的点的0-1行向量, 由于xor的逆运算是它本身, 原式化为a=Mxorb这样一个方程, 由于 a=b,c=daxorb=cxord , 所以可以用高斯消去法在多项式时间内求出 b , 可以用sort写出复杂度 O(n2(logn)2) 的程序.

代码:

#include <cstdio>
#include <algorithm>
using namespace std;

struct row{
  int mat;
  int y;
  bool operator< (const row& r2) const{
    return mat > r2.mat;
  }
};

row equation[30];
int initmat[30] = {
  0x30800000,
  0x38400000,
  0x1c200000,
  0x0e100000,
  0x07080000,
  0x03040000,
  0x20c20000,
  0x10e10000,
  0x08708000,
  0x04384000,
  0x021c2000,
  0x010c1000,
  0x00830800,
  0x00438400,
  0x0021c200,
  0x0010e100,
  0x00087080,
  0x00043040,
  0x00020c20,
  0x00010e10,
  0x00008708,
  0x00004384,
  0x000021c2,
  0x000010c1,
  0x00000830,
  0x00000438,
  0x0000021c,
  0x0000010e,
  0x00000087,
  0x00000043,
};

void print(){
  printf("\n\n");
  for(int i = 0; i < 30; i++){
    for(int j = 0; j < 30; j++){
      printf(" %d", (equation[i].mat >> (29 - j)) & 1);
    }
    printf(" | %d\n", equation[i].y);
  }
}

void gauss(){
  for(int i = 0; i < 30; i++){
    sort(equation + i, equation + 30);
    for(int j = i + 1; j < 30 && ((equation[j].mat >> (29 - i)) & 1); j++){
      equation[j].mat ^= equation[i].mat;
      equation[j].y ^= equation[i].y;
    }
  }
  for(int i = 29; i >= 0; i--){
    for(int j = i - 1; j >= 0; j--){
      if((equation[j].mat >> (29 - i)) & 1){
        equation[j].mat ^= equation[i].mat;
        equation[j].y ^= equation[i].y;
      }
    }
  }
}

int main(){
  int kase;
  scanf("%d", &kase);
  for(int k = 1; k <= kase; k++){
    for(int i = 0; i < 30; i++){
      scanf("%d", &equation[i].y);
      equation[i].mat = initmat[i];
    }
    gauss();
    printf("PUZZLE #%d\n", k);
    for(int i = 0; i < 5; i++){
      for(int j = 0; j < 6; j++){
        if(j) printf(" ");
        printf("%d", equation[i*6+j].y);
      }
      printf("\n");
    }
  }
  return 0;
}

2I Terrarium(POJ 3434)

题目大意: 给定若干条蛇, 模拟贪吃蛇的行进方式, 其中每条蛇按照先后顺序移动, 每次移动会依次尝试直行, 右转, 左转, 停止四个动作, 问 n 次移动后盘面的状态.

解题思路: 这是一道模拟题, 但是时间卡得很紧, 并且必须用一个双端队列记录蛇的位置, 不能仅仅记录头和尾, 否则可能会造成移动后更新到错误的尾.

代码(使用Visual C++提交):

#include <cstdio>
#include <cctype>
#include <deque>
using namespace std;

char mp[1010][1010];
int shi[30], shj[30];
int sti[30], stj[30];
int se[30], dri[30], drj[30];
int visited[1010][1010];
int a, b;
deque< pair<int, int> > snake[30];

inline int valid(int i, int j){
  return i >= 0 && i < a && j >= 0 && j < a && mp[i][j] == '.';
}

inline void update(int i, int tdri, int tdrj){
  mp[sti[i]][stj[i]] = '.';
  mp[shi[i]][shj[i]] = 'a' + i;
  shi[i] += tdri;
  shj[i] += tdrj;
  dri[i] = tdri;
  drj[i] = tdrj;
  snake[i].push_back(make_pair(shi[i], shj[i]));
  snake[i].pop_front();
  sti[i] = snake[i].front().first, stj[i] = snake[i].front().second;
  mp[shi[i]][shj[i]] = 'A' + i;
}

inline int findc(char c, int y, int x, int& ry, int& rx){
  visited[y][x] = 1;
  for(int i = -1; i <= 1; i++){
    if(!visited[y + i][x] && mp[y + i][x] == c){
      ry = y + i;
      rx = x;
      snake[c - 'a'].push_front(make_pair(ry, rx));
      return 1;
    }
  }
  for(int i = -1; i <= 1; i++){
    if(!visited[y][x + i] && mp[y][x + i] == c){
      ry = y;
      rx = x + i;
      snake[c - 'a'].push_front(make_pair(ry, rx));
      return 1;
    }
  }
  return 0;
}

inline void print(){
  for(int i = 0; i < a; i++){
    for(int j = 0; j < a; j++){
      putchar(mp[i][j]);
    }
    putchar('\n');
  }
}

int main(){
  scanf("%d%d", &a, &b);
  char c;
  if((c = getchar()) != ' '){
    ungetc(c, stdin);
  }
  for(int i = 0; i < a; i++){
    getchar();
    for(int j = 0; j < a; j++){
      mp[i][j] = getchar();
      if(mp[i][j] >= 'A' && mp[i][j] <= 'Z'){
        shi[mp[i][j] - 'A'] = i;
        shj[mp[i][j] - 'A'] = j;
        se[mp[i][j] - 'A'] = 1;
      }
    }
  }
  for(int i = 0; i < 30; i++){
    if(se[i]){
      sti[i] = shi[i];
      stj[i] = shj[i];
      snake[i].push_front(make_pair(shi[i], shj[i]));
      findc('a' + i, sti[i], stj[i], sti[i], stj[i]);
      dri[i] = shi[i] - sti[i];
      drj[i] = shj[i] - stj[i];
      while(findc('a' + i, sti[i], stj[i], sti[i], stj[i]))
      ;
    }
  }
  for(int t = 0; t < b; t++){
    for(int i = 0; i < 30; i++){
      if(se[i]){
        int tdri = dri[i];
        int tdrj = drj[i];
        if(valid(shi[i] + tdri, shj[i] + tdrj)){
          update(i, tdri, tdrj);
        }
        else{
          tdri = drj[i];
          tdrj = -dri[i];
          if(valid(shi[i] + tdri, shj[i] + tdrj)){
            update(i, tdri, tdrj);
          }
          else{
            tdri = -drj[i];
            tdrj = dri[i];
            if(valid(shi[i] + tdri, shj[i] + tdrj)){
              update(i, tdri, tdrj);
            }
          }
        }
      }
    }
  }
  print();
  return 0;
}

2J Pseudoprime numbers(POJ 3641)

题目大意: 可以利用费马定理判定一个数是否是质数, 然而可能会出现伪质数的情况, 判定对于某个两个数a,b使用 b 来判断a是否会把不是质数的 a 判为质数,

解题思路: 判定质数有确定性算法和不确定性算法, 只要对一个数使用朴素的确定性算法判定一次再使用费马定理判断一次就可以断定这个数是不是伪质数, 复杂度为O(n).

#include <cstdio>
#include <cmath>
#include <cstdlib>
using namespace std;

int powermod(long long base, int e, int mg){
  if(base == 0) return 0;
  long long ret = 1;
  while(e){
    if(e & 1){
      ret *= base;
      ret %= mg;
    }
    base *= base;
    base %= mg;
    e >>= 1;
  }
  return ret;
}

int fermat(int p, int a){
  return powermod(a, p, p) == a % p;
}

int check(int x){
  if(x < 2) return 0;
  for(int i = 2; i < sqrt(x) + 1e-5; i++){
    if(x % i == 0) return 0;
  }
  return 1;
}

int main(){
  while(1){
    int p, a;
    scanf("%d%d", &p, &a);
    if(!p || !a) return 0;
    printf("%s\n", fermat(p, a) && !check(p) ? "yes" : "no");
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值