PAT : 团体程序设计天梯赛-练习集L3 个人题解

另把天梯赛所有题解内容全部打包成了一个文档,可以自行下载:https://download.csdn.net/download/daixinliangwyx/11170075

L3-001 凑零钱

解法:dfs

坑点:对于零钱总和小于m的,就不需要进行dfs了。如果不进行这个特判,最后一个数据点会超时。

代码:

#include
using namespace std;
int n, k;
int a[10010], b[10010];
int dfs(int i, int sum, int c) {
  int flag = 0;
  if(sum > k) return 0;
  if(sum == k) {
    for(int pp = 0; pp < c; pp++) {
      printf(pp == 0 ? "%d" : " %d", b[pp]);
    }
    printf("\n");
    return 1;
  }
  for(int j = i + 1; j < n; j++) {
    b[c] = a[j];
    flag = dfs(j, sum + a[j], c+1);
    if(flag) return 1;
  }
  return 0;
}
int main() {
  scanf("%d%d", &n, &k);
  int flag = 0, sum = 0;
  for(int i = 0; i < n; i++) {
    scanf("%d", &a[i]);
    sum += a[i];
  }
  sort(a, a+n);
  if(sum >= k) {
    for(int i = 0; i < n; i++) {
      if(a[i] > k) break;
      b[0] = a[i];
      flag = dfs(i, a[i], 1);
      if(flag) break;
    }
  }
  if(flag == 0) printf("No Solution\n");
  return 0;
}

L3-002 特殊堆栈 

解法:用stack来模拟入栈出栈,用vector来取中间值

代码:

#include
using namespace std;
int n, num;
stack s;
vector v;
string op;
int main() {
  cin >> n;
  vector::iterator it;
  while(n--) {
    cin >> op;
    if(op == "Push") {
      cin >> num;
      s.push(num);
      it = lower_bound(v.begin(), v.end(), num);
			v.insert(it, num);
    } else if (op == "Pop") {
      if(s.empty()) cout << "Invalid" << endl;
      else {
        cout << s.top() << endl;
        v.erase(lower_bound(v.begin(), v.end(), s.top()));
        s.pop();
      }
    } else {
      if(s.empty()) cout << "Invalid" << endl;
      else {
        cout << v[(s.size()+1)/2-1] << endl;
      }
    }
  }
  return 0;
}

L3-003 社交集群 

解法:裸并查集

代码:

#include
using namespace std;
int n, num, k, sum;
vector v[1010];
int tot[1010], pre[1010], ans[1010];
int find(int x) {
  if(x == pre[x]) return pre[x];
  return pre[x] = find(pre[x]);
}
void join(int x, int y) {
  int fx = find(x), fy = find(y);
  if(fx != fy) {
    pre[fx] = fy;
    tot[fy] += tot[fx];
  }
}
int main() {
  scanf("%d", &n);
  sum = 0;
  for(int i = 1; i <= n; i++) {
    pre[i] = i;
    tot[i] = 1;
  }
  for(int i = 1; i <= n; i++) {
    scanf("%d:", &num);
    for(int j = 0; j < num; j++) {
      scanf("%d", &k);
      v[k].push_back(i);
    }
  }
  for(int i = 1; i <= 1000; i++) {
    int len = v[i].size();
    for(int j = 0; j < len-1; j++) {
      join(v[i][j], v[i][j+1]);
    }
  }
  for(int i = 1; i <= n; i++) {
    if(i == find(i)) ans[sum++] = tot[i];
  }
  printf("%d\n", sum);
  sort(ans, ans+sum);
  for(int i = sum-1; i >= 0; i--) {
    printf(i == sum-1 ? "%d" : " %d", ans[i]);
  }
  printf("\n");
  return 0;
}

L3-004 肿瘤诊断

解法:就是个三维立体的bfs求联通块数量,我是直接将三维的坐标信息放在了二维数组里(像题目输入数据的形式那样存储),也就是将下一层平面的坐标信息接在了上一层的坐标信息后面(下一层平面的第一行接在上一平面的最后一行后面)。方向则要改成前后左右上下这六个方向。

坑点:判断下一个点的x、y的范围时,对于next.y只用判断左右是否越界即可,但对于next.x不仅要判越界还要判前后方向(与前一个点now要在同一个平面)和上下方向(与前一个点now不能在同一个平面)的范围。

代码:

#include
using namespace std;
long long n, m, l, t, num, vv, sum;
long long v[80000][130];
long long dir[6][2];
struct point {
  long long x, y;
};
void bfs(long long sx, long long sy) {
  queue q;
  point now, next;
  now.x = sx;
  now.y = sy;
  q.push(now);
  v[sx][sy] = 0;
  while(!q.empty()) {
    now = q.front();
    q.pop();
    vv++;
    for(long long i = 0; i < 6; i++) {
      next.x = now.x + dir[i][0];
      next.y = now.y + dir[i][1];
      if(next.y < 0 && next.y >= m) continue;//判断左右是否越界,越界就跳过 
      if(next.x < 0 || next.x >= n*l) continue;//判断前后、上下是否越界,越界就跳过 
      else {//如果在前后和上下的总范围内 
        if(i == 2 || i == 3) {//前后 
          if(next.x / n != now.x / n) continue;//不在一个平面就跳过 
        }
        if(i == 4 || i == 5) {//上下
           if(next.x / n == now.x / n) continue;//在一个平面就跳过
        }
      }
      if(v[next.x][next.y] == 1) {
        v[next.x][next.y] = 0;
        q.push(next);
      }
    }
  }
}
int main() {
  sum = 0;
  scanf("%lld%lld%lld%lld", &n, &m, &l, &t);
  dir[0][0] = 0;
  dir[0][1] = 1;
  dir[1][0] = 0;
  dir[1][1] = -1;
  dir[2][0] = 1;
  dir[2][1] = 0;
  dir[3][0] = -1;
  dir[3][1] = 0;
  dir[4][0] = n;
  dir[4][1] = 0;
  dir[5][0] = -n;
  dir[5][1] = 0;
  for(long long i = 0; i < n*l; i++) {
    for(long long j = 0; j < m; j++) {
      scanf("%lld", &v[i][j]);
    }
  }
  for(long long i = 0; i < n*l; i++) {
    for(long long j = 0; j < m; j++) {
      if(v[i][j]) {
        vv = 0;
        bfs(i, j);
        if(vv >= t) sum += vv;
      }
    }
  }
  printf("%lld\n", sum);
  return 0;
}

L3-005 垃圾箱分布

解法:裸迪杰斯特拉。将每个垃圾箱编号接在n个居民点后面,对每个垃圾箱进行迪杰斯特拉然后进行选择就行了。

坑点:1、因为输入点包含垃圾箱(带'G')所以输入的时候要按字符串输入,然后再转成数值,注意垃圾箱是<=10的,所以垃圾箱号码可能是两位哦,一开始没注意这个范围,WA在了最后一个测试点上。2、一开始读题有误,题目上说“垃圾箱的位置必须选在到所有居民点的最短距离最长的地方”,也就是说选出的垃圾箱i到每个居民点j的最短距离(dis[i][j])中最短的那一段距离要是最长的,我一开始理解成了要让总的dis[i][j]之和最长(因为题目说的是到所有居民点,我就以为是求和,我说样例怎么对不上,还是自己读错题了呀)。(ps:平均距离加上0.0000001是为了%lf四舍五入时候的浮点误差,其实好像这里加不加都能过)

代码:

#include
#define inf 0x3f3f3f3f
using namespace std;
int n, m, k, ds, len, uu, vv, flag, sumdis, mindis, realsumdis, ans, maxmindis;
int way[1030][1030], to[1030], dis[1030][1030], vis[1030];
char u[10], v[10];
void Dijstl(int s) {
  to[s] = 0;
  for(int i = 1; i <= n+m; i++) {
    int minn = inf, next = -1;
    for(int j = 1; j <= n+m; j++) {
      if(vis[j] == 0 && to[j] < minn) {
        minn = to[j];
        next = j;
      }
    }
    if(next == -1) break;
    else
      vis[next] = 1;
    for(int j = 1; j <= n+m; j++)
      if(vis[j] == 0 && to[next] + dis[next][j] < to[j]) to[j] = to[next] + dis[next][j];
  }
}
int main() {
  flag = 0;
  maxmindis = -1;
  scanf("%d%d%d%d", &n, &m, &k, &ds);
  for(int i = 1; i <= n+m; i++) {
    for(int j = 1; j <= n+m; j++) {
      if(i == j) way[i][j] = 0;
      else
        way[i][j] = inf;
    }
  }
  for(int i = 0; i < k; i++) {
    scanf("%s%s%d", u, v, &len);
    if(u[0] == 'G') {
      uu = 0;
      for(int j = 1; j < strlen(u); j++)
        uu = uu * 10 + (int)(u[j] - '0');
      uu += n;
    } else {
      uu = atoi(u);
    }
    if(v[0] == 'G') {
      vv = 0;
      for(int j = 1; j < strlen(v); j++)
        vv = vv * 10 + (int)(v[j] - '0');
      vv += n;
    } else {
      vv = atoi(v);
    }
    way[uu][vv] = len;
    way[vv][uu] = len;
  }
  for(int i = n+1; i <= n+m; i++) {
    memset(vis, 0, sizeof(vis));
    memset(to, inf, sizeof(to));
    for(int ii = 1; ii <= n+m; ii++) {
      for(int jj = 1; jj <= n+m; jj++) {
        dis[ii][jj] = way[ii][jj];
      }
    }
    Dijstl(i);
    sumdis = 0;
    mindis = inf;
    int flag2 = 0;
    for(int j = 1; j <= n; j++) {
      if(to[j] > ds || to[j] == inf) {
        flag2 = 1;
        break;
      }
      sumdis += to[j];
      if(i != j) mindis = min(mindis, to[j]);
    }
    if(flag2 == 0) {
      flag = 1;
      if(mindis > maxmindis) {
        ans = i;
        maxmindis = mindis;
        realsumdis = sumdis;
      } else if(mindis == maxmindis) {
        if(sumdis < realsumdis) {
          ans = i;
          maxmindis = mindis;
          realsumdis = sumdis;
        }
      }
    }
  }
  if(flag) printf("G%d\n%d.0 %.1lf\n", ans-n, maxmindis, realsumdis*1.0/n);
  else
    printf("No Solution\n");
  return 0;
}

L3-006 迎风一刀斩

天呐,计算几何的数学题,看到就头大,太繁琐了,得考虑很多种情况,看了网上别人的代码,都是100多行起步的。码起来没有乐趣,告辞!

 

L3-007 天梯地图 

解法:因为起点固定,就直接对长度和时间分别进行迪杰斯特拉。

坑点:在处理“如果最快到达路线不唯一,则输出几条最快路线中最短的那条”这个问题时,在时间的Dijistrl里面是可以直接用一个一维数组来记录到j点的最短长度的(因为源点唯一,就可以当作路径的迪杰斯特拉处理);也可以用Floyd记录任意两个点的最短路径(不会超时),但是在时间相等时进行判断的地方:fdis[s][next] + fdis[next][j] <= fdis[s][j]这里必须要有等号否则只能得23分(测试点2错误)。

另外提供两组自造数据:


input:

5 4
1 2 1 2 5
1 3 1 2 4
2 4 1 5 1
3 4 1 4 2
1 4

output:

Time = 6; Distance = 6: 1 => 3 => 4


input:

5 5
2 1 1 4 4
1 0 1 2 2
2 3 1 1 1
3 4 1 2 2
4 0 1 3 3
2 0

output:

Time = 6: 2 => 3 => 4 => 0
Distance = 6: 2 => 1 => 0


代码:

#include
#define inf 0x3f3f3f3f
using namespace std;
int n, m, v1, v2, oneway, length, ti, s, e, tmp, flag;
int disto[510], dis[510][510], timeto[510], tme[510][510], disvis[510], timevis[510], sum[510];
int dispre[510], timepre[510];
int a[510], b[510], ka, kb;
int fdisto[510];
void Dijstrldis() {
  disto[s] = 0;
  for(int i = 0; i < n; i++) {
    int minn = inf, next = -1;
    for(int j = 0; j < n; j++) {
      if(disvis[j] == 0 && disto[j] < minn) {
        minn = disto[j];
        next = j;
      }
    }
    if(next == -1) break;
    else
      disvis[next] = 1;
    for(int j = 0; j < n; j++) {
      if(disvis[j] == 0 && disto[next] + dis[next][j] < disto[j]) {
        disto[j] = disto[next] + dis[next][j];
        dispre[j] = next;
        sum[j] = sum[next] + 1;
      } else if(disvis[j] == 0 && disto[next] + dis[next][j] == disto[j]) {
        if(sum[next] + 1 < sum[j]) {
          sum[j] = sum[next] + 1;
          dispre[j] = next;
        }
      }
    }
  }
}
void Dijstrltime() {
  timeto[s] = 0;
  for(int i = 0; i < n; i++) {
    int minn = inf, next = -1;
    for(int j = 0; j < n; j++) {
      if(timevis[j] == 0 && timeto[j] < minn) {
        minn = timeto[j];
        next = j;
      }
    }
    if(next == -1) break;
    else
      timevis[next] = 1;
    for(int j = 0; j < n; j++) {
      if(timevis[j] == 0 && timeto[next] + tme[next][j] < timeto[j]) {
        timeto[j] = timeto[next] + tme[next][j];
        timepre[j] = next;
        fdisto[j] = fdisto[next] + dis[next][j];
      } else if(timevis[j] == 0 && timeto[next] + tme[next][j] == timeto[j]) {
        if(fdisto[j] > fdisto[next] + dis[next][j]) {
          timepre[j] = next;
          fdisto[j] = fdisto[next] + dis[next][j];
        }
      }
    }
  }
}
int main() {
  scanf("%d%d", &n, &m);
  for(int i = 0; i < n; i++) {
    for(int j = 0; j < n; j++) {
      dis[i][j] = inf;
      tme[i][j] = inf;
    }
  }
  for(int i = 0; i < n; i++) {
    sum[i] = 1;
    disto[i] = inf;
    timeto[i] = inf;
  }
  for(int i = 0; i < m; i++) {
    scanf("%d%d%d%d%d", &v1, &v2, &oneway, &length, &ti);
    dis[v1][v2] = length;
    tme[v1][v2] = ti;
    if(oneway == 0) {
      dis[v2][v1] = length;
      tme[v2][v1] = ti;
    }
  }
  scanf("%d%d", &s, &e);
  Dijstrldis();
  tmp = e;
  ka = 0;
  a[ka++] = tmp;
  while(tmp != s) {
    tmp = dispre[tmp];
    a[ka++] = tmp;
  }
  Dijstrltime();
  tmp = e;
  kb = 0;
  b[kb++] = tmp;
  while(tmp != s) {
    tmp = timepre[tmp];
    b[kb++] = tmp;
  }
  flag = 1;
  if(ka == kb) {
    for(int i = 0; i < ka; i++) {
      if(a[i] != b[i]) {
        flag = 0;
        break;
      }
    }
  } else {
    flag = 0;
  }
  if(flag) {
    printf("Time = %d; Distance = %d: ", timeto[e], disto[e]);
    for(int i = ka-1; i >= 0; i--) 
      printf(i == ka-1 ? "%d" : " => %d", a[i]);
    printf("\n");
  } else {
    printf("Time = %d: ", timeto[e]);
    for(int i = kb-1; i >= 0; i--) 
      printf(i == kb-1 ? "%d" : " => %d", b[i]);
    printf("\nDistance = %d: ", disto[e]);
    for(int i = ka-1; i >= 0; i--) 
      printf(i == ka-1 ? "%d" : " => %d", a[i]);
    printf("\n");
  }
  return 0;
}

L3-008 喊山

解法:简单bfs,将每个山头的邻近山头存在一个邻接表里,每次查询山头时,直接从该山头开始bfs,一层层往外搜(也就是从近到远)。在bfs里面定义一个step记录此时搜到了第几层,从队列里每取出一个山头就通过山头所处层数是否大于step来判断一下取出的山头是否为更外一层,如果是则记录该山头同时更新step。最后bfs结束得到的也就是最外一层的山头编号了。

坑点:感觉这个题目出锅了啊,题目上说“若这样的山头不只一个,则输出编号最小的那个”,我一开始是用优先队列来维护同一层的山头编号小的先取出来,但只得了21分;而去掉优先队列采用普通队列却得了满分,普通队列显然无法满足题目条件,佛了佛了~~

献上自造的出锅数据:


input:

7 5 4
3 1
1 2
2 3
4 5
5 6
1 4 5 7


满分代码output:

3

6

4

0

优先队列21分代码output:

2

6

4

0


这组数据我只是对题目给出的输入样例调换了m对山头的输入顺序,满分代码得到的答案却与输出样例不同(显然满分代码没有实现题目所说的要求),与输出样例一致的用优先队列来维护较小编号的代码反而只得了21分。

代码:

#include
using namespace std;
int n, m, k, u1, u2, num;
int vis[10010];
vector v[10010];
struct shan {
  int id, sum;
//  friend bool operator <(shan a, shan b) {//21分 
//    return a.id > b.id;
//  }
};
void bfs(int s) {
//  priority_queue q;//21分 
  queue q;
  shan now, next;
  now.id = s;
  now.sum = 0;
  vis[s] = 1;
  q.push(now);
  int ans = 0, step = 0;
  while(!q.empty()) {
//    now = q.top();//21分 
    now = q.front();
    q.pop();
    if(now.sum > step) {
      ans = now.id;
      step++;
    }
    for(int i = 0; i < v[now.id].size(); i++) {
      next.id = v[now.id][i];
      next.sum = now.sum + 1;
      if(vis[next.id] == 0) {
        q.push(next);
        vis[next.id] = 1;
      }
    }
  }
  printf("%d\n", ans);
}
int main() {
  scanf("%d%d%d", &n, &m, &k);
  for(int i = 0; i < m; i++) {
    scanf("%d%d", &u1, &u2);
    v[u1].push_back(u2);
    v[u2].push_back(u1);
  }
  while(k--) {
    memset(vis, 0, sizeof(vis));
    scanf("%d", &num);
    bfs(num);
  }
  return 0;
}

L3-010 是否完全二叉搜索树

解法:用map容器来存左右孩子,从根结点开始递归分治来建二叉搜索树,然后通过bfs来输出层序序列。至于判断是否为完全二叉树,只需要在bfs里面给每个结点的左右孩子按层序编上号,然后比对最后一个结点的编号是否等于n就行了(如果最后一个结点的编号为n则说明该结点前面的结点都是按序编满的,属于完全二叉树)。

代码:

#include
using namespace std;
int n, a[25];
map L, R, num;
void build(int root, int value) {
  if(value > root) {
    if(L[root] == 0) {
      L[root] = value;
      return;
    } else {
      build(L[root], value);
    }
  } else {
    if(R[root] == 0) {
      R[root] = value;
      return;
    } else {
      build(R[root], value);
    }
  }
}
void bfs(int root) {
  queue Q;
  Q.push(root);
  int cnt = 0, lastpoint = -1, p = 1;
  num[root] = p;
  while (!Q.empty()) {
    int tn = Q.front();
    lastpoint = tn;
    Q.pop();
    printf(cnt++ == 0 ? "%d" : " %d", tn);
    if (L[tn]) {
      Q.push(L[tn]);
    }
    if (R[tn]) {
      Q.push(R[tn]);
    }
    num[L[tn]] = ++p;
    num[R[tn]] = ++p;
  }
  if(num[lastpoint] == n) printf("\nYES\n");
  else
    printf("\nNO\n");
}
int main() {
  scanf("%d", &n);
  for(int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
    if(i > 1) build(a[1], a[i]);
  }
  bfs(a[1]);
  return 0;
}

L3-011 直捣黄龙

解法:迪杰斯特拉+打印路径。由于题目给的是城镇的代号(字符串),因此需要给每个城镇一个整型的编号,至于城镇代号跟城镇编号的一一映射可以用两个map来存储。

代码:

#include
#define inf 0x3f3f3f3f
using namespace std;
int n, k, len;
int to[210], dis[210][210], sump[210], sum[210], num[210], pre[210], vis[210], sumpath[210];
string s1, s2, s, u, v;
map m1;
map m2;
void Dijstr(int start) {
  to[start] = 0;
  for(int i = 1; i <= n; i++) {
    int minn = inf, next = -1;
    for(int j = 1; j <= n; j++) {
      if(vis[j] == 0 && to[j] < minn) {
        minn = to[j];
        next = j;
      }
    }
    if(next == -1) break;
    else
      vis[next] = 1;
    for(int j = 1; j <= n; j++) {
      if(vis[j] == 0 && to[next] + dis[next][j] < to[j]) {
        to[j] = to[next] + dis[next][j];
        sump[j] = sump[next] + 1;
        sum[j] = sum[next] + num[j];
        sumpath[j] = sumpath[next];
        pre[j] = next;
      } else if(vis[j] == 0 && to[next] + dis[next][j] == to[j]) {
        sumpath[j]= sumpath[j] + sumpath[next];
        if(sump[next] + 1 > sump[j]) {
          sump[j] = sump[next] + 1;
          sum[j] = sum[next] + num[j];
          pre[j] = next;
        } else if(sump[next] + 1 == sump[j]) {
          if(sum[j] < sum[next] + num[j]) {
            sum[j] = sum[next] + num[j];
            pre[j] = next;
          }
        }
      }
    }
  }
}
int main() {
  cin >> n >> k >> s1 >> s2;
  for(int i = 1; i <= n; i++) {
    for(int j = 1; j <= n; j++) {
      dis[i][j] = inf;
    }
  }
  for(int i = 1; i <= n; i++) {
    sump[i] = 1;
    sumpath[i] = 1;
    to[i] = inf;
  }
  m1[s1] = 1;
  m2[1] = s1;
  for(int i = 2; i <= n; i++) {
    cin >> s >> num[i];
    m1[s] = i;
    m2[i] = s;
    sum[i] = num[i];
  }
  for(int i = 0; i < k; i++) {
    cin >> u >> v >> len;
    dis[m1[u]][m1[v]] = len;
    dis[m1[v]][m1[u]] = len;
  }
  Dijstr(1);
  int t = m1[s2], k = 0, path[210];
  path[k++] = t;
  while(t != 1) {
    t = pre[t];
    path[k++] = t;
  } 
  for(int i = k-1; i >= 0; i--) {
    if(i == k-1) cout << m2[path[i]];
    else
      cout << "->" << m2[path[i]];
  }
  printf("\n%d %d %d\n", sumpath[m1[s2]], to[m1[s2]], sum[m1[s2]]);
  return 0;
}

L3-012 水果忍者

解法:总体思想就是先在给出的某一条线段上固定一个端点,以该端点来遍历其他所有线段,通过该端点与其它线段的两个端点相连来确定斜率范围,看是否存在一个斜率范围可以同时穿过所有线段(也就是这个斜率范围,是在该端点与其他任意线段的两个端点连线的斜率范围内的)。 

代码:

#include
#define inf 0x3f3f3f3f
using namespace std;
int n;
double maxk, mink, ansmaxk, ansmink, ansx, ansy;
struct line {
  double x, maxy, miny;
}l[10010];
int cmp(line a, line b) {
  if(a.x < b.x) return 1;
  return 0;
}
int main() {
  scanf("%d", &n);
  for(int i = 0; i < n; i++) 
    scanf("%lf%lf%lf", &l[i].x, &l[i].maxy, &l[i].miny);
  sort(l, l+n, cmp);//排一下序,使所有线段按x坐标从小到大排好 
  for(int i = 0; i < n; i++) {//把该线段上的点作为答案直线上的一个点 
    ansmaxk = inf;//初始化经过该线段上点的答案斜率范围 
    ansmink = -inf;
    int j;
    for(j = 0; j < n; j++) {
      if(i != j) {
        if(i < j) {//得到可以同时穿过线段i和j的答案直线的斜率范围 
          maxk = (l[i].miny - l[j].maxy) / (l[i].x - l[j].x);
          mink = (l[i].miny - l[j].miny) / (l[i].x - l[j].x);
        } else {
          maxk = (l[j].miny - l[i].miny) / (l[j].x - l[i].x);
          mink = (l[j].maxy - l[i].miny) / (l[j].x - l[i].x);
        }
        if(ansmaxk < mink || ansmink > maxk) break;//以i线段为基础的答案斜率不满足当前同时穿过两线段的斜率范围,说明不能穿过所有线段,直接break
        if(maxk < ansmaxk) {
          ansmaxk = maxk;
          ansx = l[j].x;
          ansy = l[j].maxy;
        } 
        ansmink = max(mink, ansmink);
      }
    } 
    if(j == n) {//存在经过i线段上点的一个斜率范围所得到的答案直线可以同时穿过所有线段 
      printf("%.0lf %.0lf %.0lf %.0lf\n", l[i].x, l[i].miny, ansx, ansy); //线段i上取了最低点,则另一条线段要取最高点 
      return 0;
    }
  }
}

L3-013 非常弹的球

解法:设抛出角为θ,由E=m*v​*v​​/2,得到抛出速度v=sqrt(2*E/m),水平方向上的速度Vx=v*cosθ,垂直方向上的速度Vy=v*sinθ,由gt=Vy,得到上升到最高点所需时间为Vy/g,由Vt^2-Vo^2=2*g*H(Vt为末速度0,Vo为初速度Vy,此时g为方向加速度-g),得到上升的最高高度H=Vt*Vt/(2*g),那么又由H=g*t*t/2得到从最高点下落到地面的时间就为Vt/g即Vy/g,所以从球抛出到第一次下落到地面上用时为t=2*Vy/g,忽略了空气阻力那么水平方向上的投掷距离则为X=Vx*t=2*Vx*Vy/g=2*v*cosθ*v*sinθ/g,由sin(2*θ)=sinθ*cosθ/2得到X=v*v*sin(2*θ)/g,当θ取45度角时可以使X最大,X=v*v/g=2*E/(m*g)。这就是第一次掉落到地面时的投掷距离,因为反弹后只损失p%的动能,后续的水平投掷距离就仍然按上面的方法计算,这就是一个循环的过程了,直到动能变为0为止。

坑点:测试点1、3超时或者答案错误:循环终止条件不能为E>0,因为存在浮点数误差,要用E > 1e-9。

代码:

#include
using namespace std;
const double g = 9.8; 
double w, p, E = 1000, sum = 0, s;
int main() {
  scanf("%lf%lf", &w, &p);
  w /= 100;
  do {
    sum += 2 * E / (w * g);
    E = E * (100 - p) / 100;
  } while(E > 1e-9);
  printf("%.3lf\n", sum);
  return 0;
}

L3-014 周游世界

解法:因为相邻站点之间的线路只属于一家公司,所以在存储这个信息的时候可以用vector容器,容器下标作为起点站点,往容器里面push结构体,结构体存的是起点站点的相邻站点以及这两相邻站点所属公司。在查询的时候,就用暴搜就可以了,从起始站点开始搜,不断走到可以到达的相邻站点,走到终点进行比较更新再回溯,这样肯定是能到达满足条件的最终路线的。至于路线存储就可以用两个vector分别来存,每当进行换乘的时候(更换了公司),就把新公司和该公司的起始站点(换乘站点)存入,也就是说,vector每一项都是一个换乘站点,最后输出的每一个公司的路线就是第i项(起始站点)到达第i+1项(换乘站点),因此dfs是需要一个参数nowc来保存前一站点到达当前站点所属公司的,在每次搜索时判断一下目前所属公司与当前站点到下一站点所属公司是否相同,就可以知道当前站点是不是换乘站点。

代码:

#include
#define inf 0x3f3f3f3f
using namespace std;
int n, m, t, s, e, qs, qe, stationnum, companynum, vis[10000];
struct info {
  int next, company;//下一个站点编号,该区间所属公司编号
};
vector q[10000];
vector path, anspath, c, ansc;
void dfs(int now, int snum, int cnum, int nowc) {//当前站点编号,站点数量,涉及的公司数量,当前线路所属公司
  if(now == qe) {
    if(snum < stationnum || (snum == stationnum && cnum < companynum)) {
      stationnum = snum;
      companynum = cnum;
      ansc = c;
      anspath = path;
    }
    return;
  }
  for(int i = 0; i < q[now].size(); i++) {
    if(!vis[q[now][i].next]) {
      vis[q[now][i].next] = 1;
      if(nowc == q[now][i].company) dfs(q[now][i].next, snum+1, cnum, nowc);//当前站点和下一站点是同一公司的
      else {//不是同一个公司,说明需要换乘了
        c.push_back(q[now][i].company);//将换乘公司存入
        path.push_back(now);//存入当前站点,也就是所走线路上每次换乘()的第一个站点
        dfs(q[now][i].next, snum+1, cnum+1, q[now][i].company);
        c.pop_back();//回溯
        path.pop_back();
      }
      vis[q[now][i].next] = 0;
    }
  }
}
int main() {
  scanf("%d", &n);
  for(int i = 1; i <= n; i++) {
    scanf("%d", &m);
    scanf("%d", &qs);//先输入一个区间起点
    for(int j = 1; j < m; j++) {
      scanf("%d", &qe);//与区间起点相邻的区间终点
      struct info io;
      io.next = qe;
      io.company = i;
      q[qs].push_back(io);//存储了qs到qe区间(相邻两个站点)的信息
      io.next = qs;
      q[qe].push_back(io);//存储了qe到qs区间(相邻两个站点)的信息
      qs = qe;//滑动更新区间起点
    }
  }
  scanf("%d", &t);
  while(t--) {
    stationnum = inf;
    companynum = inf;
    memset(vis, 0, sizeof(vis));
    path.clear(), anspath.clear(), c.clear(), ansc.clear();
    scanf("%d%d", &qs, &qe);
    vis[qs] = 1;//标记访问数组,防止不断走环进入死循环
    dfs(qs, 0, 0, 0);
    if(stationnum == inf) printf("Sorry, no line is available.\n");
    else {
      printf("%d\n", stationnum);
      anspath.push_back(qe);//因为存的是每一次换乘线路的起点站,所以需要最后把终点存进去(终点之后是没有换乘的)
      for(int i = 0; i < companynum; i++)
        printf("Go by the line of company #%d from %04d to %04d.\n", ansc[i], anspath[i], anspath[i+1]);
    }
  }
  return 0;
}

L3-015 球队“食物链”

解法:dfs+剪枝。

坑点:一开始用全排列函数对链的顺序进行排列然后对每组排列进行判断,T了最后两个测试点。再然后直接dfs暴搜,T了测试点4。才考虑对dfs进行剪枝优化,必须在每次进行搜索的时候,判断一下后面的球队有没有哪个球队赢过第一个球队,如果没有就不需要进行下一次搜索了(后面的点没有能与第一个点相连的,即组不了环链),直接return节省时间。

代码:

#include
using namespace std;
int n, flag;
int order[25], vis[25];
char res[25][25];
vector v[25];
void dfs(int p, int k) {
  if(flag) return;
  if(k >= n) {
    if(res[p][order[1]] == 'W' || res[order[1]][p] == 'L') {
      flag = 1;
      for(int i = 1; i <= n; i++) 
        printf(i == 1 ? "%d" : " %d", order[i]);
    }
    return;
  }
  int i;
  for(i = 1; i <= n; i++) //剪枝优化,如果剩下的点没有能和起点相连的,就直接return 
    if(!vis[i] && (res[i][order[1]] == 'W' || res[order[1]][i] == 'L')) break;
  if(i == n+1) return;
  for(i = 1; i <= n; i++) {
    if(vis[i] == 0 && (res[p][i] == 'W' || res[i][p] == 'L')){
      order[k+1] = i;
      vis[i] = 1;
      dfs(i, k+1);
      vis[i] = 0;
    }
  }
}
int main() {
  flag = 0;
  scanf("%d", &n);
  for(int i = 1; i <= n; i++) {
    getchar();
    for(int j = 1; j <= n; j++) {
      scanf("%c", &res[i][j]);
    }
  }
  for(int i = 1; i <= n; i++) {
    memset(vis, 0, sizeof(vis));
    order[1] = i;
    vis[i] = 1;
    dfs(i, 1);
    if(flag) break;
  } 
  if(!flag) printf("No Solution");
  return 0;
}

L3-016 二叉搜索树的结构

解法:先递归建二叉搜索树,然后用bfs看层数和双亲结点。

坑点:测试点2:查询的点有可能不在树里面,即输入的建树点里面没有需要查询的点。测试点3:查询a是a的双亲,应该输出No。

代码:

#include
using namespace std;
int n, t, num1, num2, a[105];
char s1[100], s2[100], s3[100], s4[100], s5[100], s6[100];
map L, R, ceng, pre, vis;
struct tree {
  int num, step;
};
void build(int root, int value) {
  if(value < root) {
    if(L[root] == -1) {
      L[root] = value;
      return;
    } else {
      build(L[root], value);
    }
  } else {
    if(R[root] == -1) {
      R[root] = value;
      return;
    } else {
      build(R[root], value);
    }
  }
}
void bfs(int root) {
  queue Q;
  tree now, next;
  now.num = root;
  now.step = 1;
  Q.push(now);
  while (!Q.empty()) {
    now = Q.front();
    Q.pop();
    ceng[now.num] = now.step;
    if (L[now.num] != -1) {
      pre[L[now.num]] = now.num;
      next.num = L[now.num];
      next.step = now.step + 1;
      Q.push(next);
    }
    if (R[now.num] != -1) {
      pre[R[now.num]] = now.num;
      next.num = R[now.num];
      next.step = now.step + 1;
      Q.push(next);
    }
  }
}
int main() {
  scanf("%d", &n);
  for(int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
    L[a[i]] = -1;
    R[a[i]] = -1;
    vis[a[i]] = 1;
    if(i > 1) build(a[1], a[i]);
  }
  bfs(a[1]);
  scanf("%d", &t);
  while(t--) {
    scanf("%d%s", &num1, s1);
    if(s1[0] == 'a') {
      scanf("%d%s%s", &num2, s2, s3);
      if(!vis[num1] || !vis[num2]) {
        printf("No\n");
        continue;
      }
      if(s3[0] == 's') {
        if(pre[num1] == pre[num2]) printf("Yes\n");
        else
          printf("No\n");
      } else {
        scanf("%s%s%s", s4, s5, s6);
        if(ceng[num1] == ceng[num2]) printf("Yes\n");
        else
          printf("No\n");
      }
    } else {
      scanf("%s%s", s2, s3);
      if(s3[1] == 'o') {
        if(num1 == a[1]) printf("Yes\n");
        else
          printf("No\n");
      } else if(s3[1] == 'a') {
        scanf("%s%d", s4, &num2);
        if(!vis[num1] || !vis[num2]) {
          printf("No\n");
          continue;
        }
        if(pre[num2] == num1) printf("Yes\n");
        else
          printf("No\n");
      } else if(s3[1] == 'e') {
        scanf("%s%s%d", s4, s5, &num2);
        if(!vis[num1] || !vis[num2]) {
          printf("No\n");
          continue;
        }
        if(L[num2] == num1) printf("Yes\n");
        else
          printf("No\n");
      } else {
        scanf("%s%s%d", s4, s5, &num2);
        if(!vis[num1] || !vis[num2]) {
          printf("No\n");
          continue;
        }
        if(R[num2] == num1) printf("Yes\n");
        else
          printf("No\n");
      }
    }
  }
  return 0;
}

L3-020 至多删三个字符

解法:DP。参考别人思路得到:

dp[i][j]表示从前i个字符中删除恰好j个字符,剩下字符按序构成的字符串的种类数。

状态转移方程:

dp[i][j] = dp[i-1][j] + dp[i-1][j-1]; (j>1,不删除第i个字符的情况+删除第i个字符的情况)

dp[i][j] = 1;(j==0,即初始状态)

注意初始状态的i要从0开始,因为前i个字符,当算i=1时需要用到i=0的值。

但是上面的状态转移实际上是有重复计算的:

比如abcdec 一共6个字符长,dp[6][3] = dp[5][2] + dp[5][3];

dp[5][2]代表前5个字符里删2个字符,这肯定包括了删除第4和第5个字符("de")这种情况,然后再把第6个字符也删除形成dp[6][3]的一份子,这相当于删除了dec,如右所示 abcdec ;而dp[5][3]代表前5个字符里删3个字符,这里面肯定包括了删除第3,4,5个字符的情况,如右所示 abcdec; 这样的dp[6][3]就对得到字符串"abc"进行了两次统计计算,造成了重复。

至于减去重复计算部分的话,需要在每递推到一个dp[i][j]时,就从第i-1到第1个字符里从后往前找有没有等于第i个字符的,找到第一个后停下,假设这个位置是k,那么[k,i-1]和[k+1,i]这两种删除方式得到的剩下字符串就是相同的,参考上面红色部分,而且从[k+1,i-1]这段是两种删除方式中都会删掉的,所以[k,i]这段里有i-k个字符被删掉了,要满足此时j的dp[i][j],就需要从[1,k-1]这段里面再删掉(j-(i-k))个字符,而dp[k-1][j-(i-k))]就是从前k-1个字符中删除(j-(i-k))个字符能形成多少不同子串,这个值贡献到了dp[i][j]的计算中,而且这个值在计算dp[i][j]累加时被加了两次(即参考上面红色部分的两次删除情况,dp[5][2]计算了dp[2][0],dp[5][3]也计算了dp[2][0]),现在减掉一倍的它就可以了,然后就break;进行下一次状态转移。

为什么找到一个相同的后就break呢,因为i和j都是从小到大状态转移的,对于前面串的去重是已经进行过了的,不需要继续找。

坑点:总个数可能非常大,需要开long long。

代码:

#include
using namespace std;
long long dp[1000010][5];
string s;
int main() {
  cin >> s;
  int len = s.size();
  for(int i = 0; i <= len; i++)
    dp[i][0] = 1;
  for(int i = 1; i <= len; i++) {
    for(int j = 1; j <= 3; j++) {//不能从0开始,否则可能会修改初态的值 
      dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
      for(int k = i-1; k > 0 && j >= i-k; k--) {
        if(s[i-1] == s[k-1]) {
          dp[i][j] -= dp[k-1][j-(i-k)];
          break;
        }
      }
    }
  }
  printf("%lld\n", dp[len][0]+dp[len][1]+dp[len][2]+dp[len][3]);
  return 0;
}

L3-021 神坛

解法:先按照极角排序,再用相邻向量求面积来不断更新最小值,O(n^2)。

极角排序相关学习推荐:https://www.cnblogs.com/aiguona/p/7248311.html

坑点:因为两个点的相应坐标相乘可能超出int范围,所以要开long long来存。

代码:

#include
#define inf 1ll<<60
using namespace std;
typedef long long ll;
int n, k;
struct point {
  ll x, y;
} p[5010], xl[5010];
int cmp(point a, point b) {
  if(b.y * a.x > b.x * a.y) return 1;
  return 0;
}
int main() {
  ll minn = inf;
  scanf("%d", &n);
  for(int i = 0; i < n; i++)
    scanf("%lld%lld", &p[i].x, &p[i].y);
  for(int i = 0; i < n; i++) {
    k = 0;
    for(int j = 0; j < n; j++) {
      if(i != j) {
        xl[k].x = p[j].x - p[i].x;
        xl[k].y = p[j].y - p[i].y;
        k++;
      }
    }
    sort(xl, xl+k, cmp);
    for(int j = 1; j < k; j++)
      minn = min(minn, abs(xl[j].y*xl[j-1].x-xl[j].x*xl[j-1].y));
  }
  printf("%.3lf\n", minn/2.);
  return 0;
}

 

你可能感兴趣的:(天梯赛)