sc2017新高二&高一模拟赛2 总结

a 大杂烩

题目描述

石头、剪刀和布是好朋友。
有一天,他们看到电视上播着一个新游戏:石头剪刀布。他们感到非常不高兴,于是他们决定研究该游戏的进阶版:超级英雄大乱斗。
该游戏的玩法和标准的石头剪刀布非常类似,不过有一点不同,这个游戏不是简单的石头、剪刀和布,而是由十个不同的超级英雄进行对打!下面是十个超级英雄分别对打的结果(如果a行b列的值是1的话,表示a能战胜b;如果a行b列的值是-1的话,表示b能战胜a;如果是0,表示平局):

这里写图片描述

接下来,石头和剪刀玩了很多把游戏,我们知道了他们各把游戏出的超级英雄牌,我们需要计算出石头赢了多少把,剪刀赢了多少把。

对于10%的数据,N=1。
对于50%的数据,石头只会打出SPIDER牌。
对于100%的数据,N<=100000。


输入格式

第一行一个正整数N,表示玩的局数。
接下来N行,每行两个字符串,分别表示石头和剪刀在这一局中出的超级英雄牌。


输出格式

一行两个整数,表示石头和剪刀分别赢的把数。


输入样例

1
SPIDER SPIDER


输出样例

0 0


解题思路(模拟+打表)

大水题。不要打错表,不要打错超级英雄的名字。


代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <map>

using namespace std;

map <string, int> M; 

int compare[15][15] = {
                   {0, 1, 1, 1, 1, 1, 1, 1, 1, 1}, 
                   {-1, 0, 1, -1, 1, -1, 1, -1, 1, -1},
                   {-1, -1, 0, 1, 1, -1, 1, 1, -1, 1},
                   {-1, 1, -1, 0, 1, -1, 1, -1, 1, -1},
                   {-1, -1, -1, -1, 0, 1, 1, 1, 1, -1},
                   {-1, 1, 1, 1, -1, 0, 1, -1, -1, -1},
                   {-1, -1, -1, -1, -1, -1, 0, 1, 1, 1},
                   {-1, 1, -1, 1, -1, 1, -1, 0, 1, -1},
                   {-1, -1, 1, -1, -1, 1, -1, -1, 0, 1},
                   {-1, 1, -1, 1, 1, 1, -1, 1, -1, 0}
                   };
int n, cnt1, cnt2;
string A, B;
int main(){

    M["SPIDER"] = 0;
    M["HULK"] = 1;
    M["CA"] = 2;
    M["IRON"] = 3;
    M["THOR"] = 4;
    M["DD"] = 5;
    M["DOOM"] = 6;
    M["KP"] = 7;
    M["RS"] = 8;
    M["HYDRA"] = 9;
    cin >> n;
    for(int i = 1; i <= n; i++){
      cin >> A >> B;
      if(compare[M[A]][M[B]] == 1)  cnt1 ++;
      else if(compare[M[A]][M[B]] == -1)  cnt2 ++;
    }

    printf("%d %d\n", cnt1, cnt2);

    return 0;
}

b 瓜分领土

题目描述

石头、剪刀和布闹别扭了,他们要分家。
他们生活在一个离散的一维空间里,简单点说,他们拥有在一条直线上的N间房子,每间房子有一个风水值(有正有负)。
然后,他们决定将这N间房子分成非空的三个连续段,从左到右数,第一段的房子全部属于石头,第二段的房子全部属于剪刀,第三段的房子全部属于布。
由于他们希望公平,并且又由于剪刀是他们的老大哥,他们决定根据这些条件制定了一个评判标准:
设石头拥有的房子的风水值和为a,剪刀拥有的房子的风水值和为b,布拥有的房子的风水值和为c,剪刀拥有n间房子。
那么通过给定一个参数x。
那么,这种分配的合理值就是max(a,b,c)-min(a,b,c)+x*n.
合理值越小,表示这种分配越合理。
因此,我们现在就是要求出这个最小的合理值。

对于30%的数据,N<=10.
对于70%的数据,N<=1000.
对于100%的数据,N<=100000,保证所有运算结果在long long范围内。


输入格式

第一行一个正整数N。
第二行有N个整数,表示房子的风水值,按从左到右的顺序给出。
第三行一个整数x。


输出格式

一行一个整数,表示最小的合理值。


输入样例

4
1 1 1 1
-1


输出样例

-1


解题思路(枚举+线段树+离散化+二分+数学分类)

本题线段树好题。

暴力枚举断点 O(n2) 有70分,然而我没用long long炸成60。正解就是只枚举一重,设剪刀拥有的最后一间房子的位置为 i ,然后前面石头的最后一间房子j就用线段树找。

至于具体如何做就要分成6种不同情况,消去min和max(解不等式,具体见代码),变成一个和变量 ij (包括 sum[i],sum[j] )有关的函数,同时确定 sum[j] 的范围(连续一段)。通过枚举固定 i ,离散化sum[j],用线段树维护最小的与j有关的那部分,用公式计算,然后就可以 O(nlogn) 搞定了。

考试时我想到过只枚举 i ,然后解不等式分成6种情况讨论。但是我没有立刻去解上下界,导致我没有想到线段树,我想的是二分。然而前面的sum[j]都是离散的一些点,不连续,是没有单调性的,所以我就弃了交暴力。但离散化将离散的点变成连续的一段,就可以作为下标用线段树等一维数据结构维护。这个常见的方法我以后一定要牢记。

为什么还要二分呢?假如我们预处理离散化 sum 数组,后面解出的上下界中可能在原数组中没有,这时就要二分 L R在线段树底层的位置,就是找后继与前趋。倘若不二分也可以,搞出所有上下界后再离散化或者在线段树里加哨兵也行。另外一些细节,比如解出上下界要向哪边取整,线段树中开一个大小为6的数组维护不同值(每种情况算式不同)或每做一次清空一次之类的实现方法,就具体见代码吧。

ps:这题我调试了一个晚上,反复对照数学公式,反复检查线段树,最后发现是二分写错了!找后继的代码复制了找前趋的,然后修改时傻了,将找后继一开始的 L=n,R=0 ,搞得我一晚上连爆了3次零。。


代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define N 100010
#define oo 1e18

using namespace std;

typedef long long LL;
int n;
LL x, a[N], sum[N], f[N], ans;
struct Ab{int id;  LL val;} P[N];
int rank[N], H;
struct Tnode{
    LL v[6];
}tree[N<<2];

bool cmp(Ab A, Ab B){return A.val < B.val;}

void build(int root, int L, int R){
    if(L == R){
      for(int i = 0; i < 6; i++)  tree[root].v[i] = oo;
      return;
    }
    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    build(Lson, L, mid);
    build(Rson, mid+1, R);
    for(int i = 0; i < 6; i++)  tree[root].v[i] = oo;
}

void update(int root, int L, int R, int x, int op, LL val){
    if(x > R || x < L)  return;
    if(L == x && x == R){
      tree[root].v[op] = (tree[root].v[op] > val) ? val : tree[root].v[op];
      return;
    }
    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;

    update(Lson, L, mid, x, op, val);
    update(Rson, mid+1, R, x, op, val);
    tree[root].v[op] = (tree[Lson].v[op] < tree[Rson].v[op]) ? tree[Lson].v[op] : tree[Rson].v[op];
}

LL query(int root, int L, int R, int x, int y, int op){
    if(x > R || y < L)  return oo;
    if(x <= L && y >= R)  return tree[root].v[op];
    int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
    LL temp1 = query(Lson, L, mid, x, y, op);
    LL temp2 = query(Rson, mid+1, R, x, y, op);
    return (temp1 < temp2) ? temp1 : temp2;
}

int Find_p(LL x){
    int L = 1, R = n + 1;
    while(L + 1 < R){
      int mid = (L + R) >> 1;
      if(P[mid].val <= x)  L = mid;
      else  R = mid;
    }
    return rank[P[L].id];
}

int Find_s(LL x){
    int L = 0, R = n;
    while(L + 1 < R){
      int mid = (L + R) >> 1;
      if(P[mid].val >= x)  R = mid;
      else  L = mid;
    }
    return rank[P[R].id];
}

void Work0(int i){
    if(i != 1){
      LL L = 2 * sum[i] - sum[n], R = sum[i] / 2;
      if(L > R || R < P[1].val || L > P[n].val)  return;
      L = Find_s(L);  R = Find_p(R);
      f[i] = min(f[i], sum[n] - sum[i] + x * (LL)i + query(1, 1, H, L, R, 0));
    }
    update(1, 1, H, rank[i], 0, - sum[i] - x * (LL)i);
}
void Work1(int i){
    if(i != 1){
      LL L = (sum[i] + 1) / 2, R = sum[n] - sum[i];
      if(L > R || R < P[1].val || L > P[n].val)  return;
      L = Find_s(L);  R = Find_p(R);
      f[i] = min(f[i], sum[n] - 2 * sum[i] + x * (LL)i + query(1, 1, H, L, R, 1));
    }
    update(1, 1, H, rank[i], 1, sum[i] - x * (LL)i);
}
void Work2(int i){
    if(i != 1){
      LL L = (sum[i] + 1) / 2, R = 2 * sum[i] - sum[n];
      if(L > R || R < P[1].val || L > P[n].val)  return;
      L = Find_s(L);  R = Find_p(R);
      f[i] = min(f[i], sum[i] - sum[n] + x * (LL)i + query(1, 1, H, L, R, 2));
    }
    update(1, 1, H, rank[i], 2, sum[i] - x * (LL)i);
}
void Work3(int i){
    if(i != 1){
      LL L = P[1].val, R = min(2 * sum[i] - sum[n], sum[n] - sum[i]);
      if(L > R || R < P[1].val || L > P[n].val)  return;
      L = Find_s(L);  R = Find_p(R);
      f[i] = min(f[i], sum[i] + x * (LL)i + query(1, 1, H, L, R, 3));
    }
    update(1, 1, H, rank[i], 3, - 2 * sum[i] - x * (LL)i);
}
void Work4(int i){
    if(i != 1){
      LL L = sum[n] - sum[i], R = sum[i] / 2;
      if(L > R || R < P[1].val || L > P[n].val)  return;
      L = Find_s(L);  R = Find_p(R);
      f[i] = min(f[i], 2 * sum[i] - sum[n] + x * (LL)i + query(1, 1, H, L, R, 4));
    }
    update(1, 1, H, rank[i], 4, - sum[i] - x * (LL)i);
}
void Work5(int i){
    if(i != 1){
      LL L = max(2 * sum[i] - sum[n], sum[n] - sum[i]), R = P[n].val;
      if(L > R || R < P[1].val || L > P[n].val)  return;
      L = Find_s(L);  R = Find_p(R);
      f[i] = min(f[i], - sum[i] + x * (LL)i + query(1, 1, H, L, R, 5));
    }
    update(1, 1, H, rank[i], 5, 2 * sum[i] - x * (LL)i);
}

int main(){

    scanf("%d", &n);
    for(int i = 1; i <= n; i++)  scanf("%lld", &a[i]);
    sum[0] = 0;
    for(int i = 1; i <= n; i++)  sum[i] = sum[i-1] + a[i];
    for(int i = 1; i <= n; i++){
      P[i].id = i;
      P[i].val = sum[i];
    }

    sort(P+1, P+n+1, cmp);
    rank[P[1].id] = 1;
    for(int i = 2; i <= n; i++){
      rank[P[i].id] = rank[P[i-1].id];
      if(P[i].val != P[i-1].val)  rank[P[i].id] ++;
    }

    H = rank[P[n].id];
    build(1, 1, H);

    for(int i = 2; i < n; i++)  f[i] = oo;
    scanf("%lld", &x);

    for(int i = 1; i < n; i++){
      Work0(i);
      Work1(i);
      Work2(i);
      Work3(i);
      Work4(i);
      Work5(i);
    }
    ans = oo;
    for(int i = 2; i < n; i++)  ans = min(ans, f[i]);

    printf("%lld\n", ans);

    return 0;
}

c 进击的石头

题目描述

石头一不小心掉进了一个复杂的地图里!
这个地图是一个由N个节点、M条边组成的连通无向图,可能会有重边,但没有自环,节点编号从1到N。石头一开始在1号节点。由于石头非常害怕,于是开始等概率地随机游走(举个例子,比如说如果当前节点有K条边,那么以1/K的概率走其中一条边),并走了很久很久很久。
突然,石头停下来了,想要知道现在自己在哪个节点。
于是,石头希望你告诉他,他在各个节点的概率。如果不确定的话,输出“-1”。

对于30%的数据,N<=10,M<=20.
对于90%的数据,N<=300,M<=10000.
对于100%的数据,N<=2000,M<=50000.


输入格式

第一行两个正整数N,M。
接下来M行每行两个正整数x,y.表示存在一条(x,y)的无向边。


输出格式

一行N个整数,表示停在各个节点的概率,保留六位小数。如果不确定,只输出一行一个-1.


输入样例

3 3
1 2
2 3
3 1

2 1
1 2


输出样例

0.333333 0.333333 0.333333

-1


解题思路(求概率+判断二分图)

这题我的初次得分是70分,原因是因为不会判断输出-1的情况。

这题有两种做法,先说一说比较暴力的那种。那就是建好图,然后按题目描述的去做,每走完一步就可以得到当前点的概率,然后不断迭代,直到上一次这个点的概率与这次十分接近为止。就是说,频率稳定在一个数附近,我们就认为这个数是概率。当做了足够多的次数频率扔无法稳定时,就代表概率确定,输出-1。这样做能够拿90分。另外一个点会超时。我们设置上一次此点概率 f 与本次此点概率g,满足 |fg|<Eps 时就认为稳定。当所有点都稳定时就退出迭代。所以 Eps 要取一个适当的值。一般8位~9位比较恰当。

然而我考试的时候并没有去想这种类似于“实验求概率”的方法,我利用了一条数学学方程 E(xi)=E(xi) 。学过数学的都知道,这是期望的线性性质。然而类比期望,其实单求概率也是有同样的性质。我们将题目改一下:每个点有一个分数,分数都为1,求石头最后在每个点的期望得分。这样,由于权值是1,期望与概率在数值上就相等了!于是我们便可以用这条方程解题。

这样做有什么好处呢?我们注意到方程左边求得是和的期望,右边是期望的和。左边考虑的是整体,右边对每个变量独立考虑并直接线性相加。在此题中,我们只考虑每个点独立的概率,由于走了无穷多步,所以开始从哪里出发并不重要了,按理说从哪出发最后概率都是一样的。于是这就相当于上一个点可以在任意位置,到达这个点只能通过这个点连出去的边到达。而每个点都有这样连出去的边。于是到达每个点的概率就等于 。于是我们直接统计度数就可以求到所有点的概率了。

这样做是70分的,因为如何判断-1呢?像上面说的,如果对于从每个点出发概率不一样,那么就没有确定的概率。还有一种角度,我们模拟时类似于分层图上的遍历,如果从奇数层出发和从偶数层出发,概率不一样的话,就没有确定的概率。实在不好理解可以从样例入手。我们从1出发,到达2,再不断下去,和从2出发下去的“最终”情形是不同的!

在实验了多组数据后,我们发现一个结论,从1开始染色一种确定的颜色,有两种颜色,每相邻两层换色。如果最后每个点只能染一种颜色,则没有确定的概率!于是我们就是在找二分图!二分图的最小着色数是2,所以1确定了染色,整幅图的颜色就唯一确定了。此时就是-1的情况。

这是为什么呢?lzh大佬没有留下明确的结论。因为这有点涉及数学了。其实我们可以这样理解,如果最后停在任意的点,在此之前走了奇数步和走了偶数步的概率不同,那就没有概率了(频率永远无法稳定)!因为我们不知道停下的时候,走了奇数步或偶数步,我们不知道最后停在了二分图的左边那层还是右边那层。这样从不同点出发,概率也就不同了。数学中,无穷大是没有奇偶性的。于是我们总算勉强说服了自己去求二分图了,二分图的求法很简单,直接dfs模拟染色过程就行了。

时间复杂度 O(n)


代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 2010

using namespace std;

int n, m;
int deg[N];
bool to[N][N], odd[N], even[N];

void dfs(int now){
    for(int i = 1; i <= n; i++){
      if(!to[now][i])  continue;
      if(odd[now] && !even[i]){  
        even[i] = true;
        dfs(i);
      }
      if(even[now] && !odd[i]){  
        odd[i] = true;
        dfs(i);
      }
    }
}

int main(){

    scanf("%d%d", &n, &m);

    int a, b;
    for(int i = 1; i <= m; i++){
      scanf("%d%d", &a, &b);
      to[a][b] = to[b][a] = true;
      deg[a] ++;
      deg[b] ++;
    }

    odd[1] = true;
    dfs(1);
    for(int i = 1; i <= n; i++)
      if(!odd[i] || !even[i]){
        printf("-1\n");
        return 0;
      }


    int sum = 0;
    for(int i = 1; i <= n; i++)  sum += deg[i];

    for(int i = 1; i <= n; i++){
      double possibility = (double)deg[i] / (double)sum;
      printf("%.6lf ", possibility);
    }

    printf("\n");

    return 0;
}

总结

本次考试并不理想,主要是第二题long long没注意,对线段树的应用有些生疏,第三题也没好好去想和分析样例,数学基础不够扎实。

以后要多巩固复习,数据结构和期望、概率等方面一定要加强弥补。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值