Contest1585 - 2018-2019赛季多校联合新生训练赛第一场(部分题解)

Contest1585 - 2018-2019赛季多校联合新生训练赛第一场

C 10187 查找特定的合数

D 10188 传话游戏

H 10192 扫雷游戏

 

C 传送门

题干:

题目描述
    自然数中除了能被1和本身整除外,还能被其他数整除的数叫合数。每个合数都可以写成几个质数相乘的形式,这几个质数都叫做这个合数的质因数。比如8=2×2×2,2就是8的质因数。在1—N(N≤200000)按从小到大顺序排列的自然数序列中,查找第M个有X(2≤X≤6)个不同质因数的合数。例如,第3个有2个不同质因数的合数是12(12只有2、3两个不同的质因数,在12之前有2个不同质因数的合数分别为6和10)。

输入
共1行,分别为M,X。

输出
共1行,为第M个有X个不同质因数的合数。

样例输入
3 2

样例输出
12
View Code

题解:

  步骤:

    (1):用线性筛素数(欧拉筛法)在O(n)的时间内预处理出[1,2e5]间所有的质数。

    (2):从1到2e5开始枚举,对于数 i 找到其含有的质因子的个数,用tot[ ]数组存储。

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 using namespace std;
 6 #define mem(a,b) memset(a,b,sizeof(a))
 7 const int maxn=2e5+10;
 8 
 9 int M,X;
10 int tot[maxn];//tot[i] : i 含有的质因子个数
11 //===============线性筛素数(欧拉筛法)=====================
12 int vis[maxn];
13 int prime[maxn];
14 int cnt=0;//cnt用来计数,prime数组保存素数
15 void isprime(int n)
16 {
17     for(int i=2;i<=n;i++)
18     {
19         if(!vis[i])
20             prime[cnt++]=i;//如果未被标记过,则表示为素数
21         for(int j=0;j<cnt && i*prime[j]<=n;j++)//当标记的合数超出范围则退出
22         {
23             vis[i*prime[j]]=1;
24             if(i%prime[j] == 0)
25                 break;//关键步骤
26         }
27     }
28 }
29 //===========================================================
30 bool Check(int x)//二分检查 x 是否为素数
31 {
32     int l=-1,r=cnt+1;
33     while(r-l > 1)
34     {
35         int mid=l+((r-l)>>1);
36         if(prime[mid] == x)
37             return true;
38         if(prime[mid] > x)
39             r=mid;
40         else
41             l=mid;
42     }
43     return false;
44 }
45 void Updata(int num)
46 {
47     int x=sqrt(num);
48     for(int i=2;i <= x;++i)
49     {
50         if(num%i != 0 || i == num)
51             continue;
52         int j=num/i;
53         tot[num]=tot[num]+(Check(i) ? 1:0)+(i != j && Check(j) ? 1:0);
54     }
55 }
56 int Solve()
57 {
58     mem(vis,0);
59     mem(tot,0);
60     isprime(2e5);
61     for(int i=2;i <= 200000;++i)
62         Updata(i);//更新tot[i]
63     for(int i=2;i <= 200000;++i)
64     {
65         if(tot[i] == X)//查找第 M 个只有 X 个质因子的数
66             M--;
67         if(M == 0)
68             return i;
69     }
70 }
71 int main()
72 {
73     scanf("%d%d",&M,&X);
74     printf("%d\n",Solve());
75 }
View Code

  时间复杂度分析:

    (1):预处理线性筛 : O(n),n = 2e5

    (2):枚举 : O( n*√n * log(cnt) ),(n = 2e5,cnt = 17984);

  所以总的时间复杂度为 O( n*√n * log(cnt) );

 

D 传送门

题干:

题目描述
有这样一个朋友网络,如果a认识b,那么a收到某个消息,就会把这个消息传给b,以及所有a认识的人。但是,请你注意,如果a认识b,b不一定认识a。现在我们把所有人从1到n编号,给出所有“认识”关系,问如果i发布一条新消息,那么会不会经过若干次传话后,这个消息传回给了i(1≤i≤n)。

输入
第1行是两个数n(n<1000)和m(m<10000),两数之间有一个空格,表示人数和认识关系数。接下来的m行,每行两个数a和b,表示a认识b(1≤a,b≤n)。认识关系可能会重复给出,但1行的两个数不会相同。


输出
一共有n行,每行一个字符T或F。第i行如果是T,表示i发出一条新消息会传回给i;如果是F,表示i发出一条新消息不会传回给i。


样例输入
4 6
1 2
2 3
4 1
3 1
1 3
2 3


样例输出
T
T
T
F
View Code

题解:

  考察知识点:强连通分量分解

  以人作为顶点,人与人之间的认识关系作为边建立一个有向图。

  通过SCC判断某人a是否与其他人构成强连通分量,如果构成,那么此人可以通过他人得到自己传出去的消息,反之,将得不到。

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<vector>
 5 #include<map>
 6 #include<algorithm>
 7 using namespace std;
 8 #define mem(a,b) memset(a,b,sizeof(a))
 9 #define pb(x) push_back(x)
10 const int maxn=1e3+50;
11 
12 int n,m;
13 bool vis[maxn];//访问标记
14 int color[maxn];//所属强连通分量的编号
15 int tot[maxn];//记录强连通分量的编号出现的次数
16 vector<int >G[maxn],rG[maxn];//图,反向图
17 vector<int >vs;
18 void addEdge(int u,int v)
19 {
20     G[u].pb(v);
21     rG[v].pb(u);
22 }
23 void Dfs(int u)
24 {
25     vis[u]=true;
26     for(int i=0;i < G[u].size();++i)
27     {
28         int to=G[u][i];
29         if(!vis[to])
30             Dfs(to);
31     }
32     vs.pb(u);
33 }
34 void rDfs(int u,int k)
35 {
36     vis[u]=true;
37     color[u]=k;
38     tot[k]++;
39     for(int i=0;i < rG[u].size();++i)
40     {
41         int to=rG[u][i];
42         if(!vis[to])
43             rDfs(to,k);
44     }
45 }
46 void Solve()
47 {
48     mem(vis,false);
49     for(int i=1;i <= n;++i)
50         if(!vis[i])
51             Dfs(i);
52     mem(vis,false);
53     mem(tot,0);
54     int k=0;//强连通分量编号
55     for(int i=vs.size()-1;i >= 0;--i)
56     {
57         int u=vs[i];
58         if(!vis[u])
59             rDfs(u,++k);
60     }
61     for(int i=1;i <= n;++i)
62         printf("%c\n",tot[color[i]] > 1 ? 'T':'F');//如果当前节点所属的强连通分量编号大于1,输出'T'
63     printf("\n");
64 }
65 int main()
66 {
67     scanf("%d%d",&n,&m);
68     for(int i=1;i <= m;++i)
69     {
70         int u,v;
71         scanf("%d%d",&u,&v);
72         if(find(G[u].begin(),G[u].end(),v) == G[u].end())
73             addEdge(u,v);
74     }
75     Solve();
76 }
View Code

 

H 传送门

题干:

题目描述
    小Q空的时候挺喜欢玩玩电脑游戏的。自从编程技术提高后,他就想,要是自己也能开发出一款游戏来,那该多好啊!不过,小Q也不着急,先练好基本功再说。Windows中就有一款叫扫雷的小游戏,挺好玩的,不过想编出一个来,还真不容易。小Q就自己设想了一种简单的扫雷游戏:在n行2列的方格棋盘上,左列某些方格内埋有地雷,而右列每个方格中都有一个数字(),第I格的数字表示:左列第I-、I、I+1格(即:上、中、下三格)中埋雷的总数。
    你的任务是:根据右列的数字分析出左列格子中的地雷(0表示无雷,1表示有雷),并且统计出左列格子中地雷的总数。
小Q想,如果这样的任务能完成了,相信编出更复杂的扫雷游戏也就为期不远了。

输入
    第一行,一个整数N(≤N≤40),第二行有N个数字(以一个空格相隔),表示右列格子中的数字。输入数据保证正确有解。


输出
    第一行是N个0、1数字(没有空格相隔),表示左列每格中有无地雷。第二行一个整数,表示地雷总数。


样例输入
7
1 2 3 2 2 2 2

样例输出
0111011
5
View Code

题解:

  考察知识点:位运算??

  每个位置 i 的地雷数受 i-1,i,i+1 三个位置影响。

  1位置只受 1,2 位置影响,而 1,2 位置可能的状态有 00,01,10,11 四种(二进制数表示,0表示不含地雷,1表示含有地雷),而答案就是其中之一。

  根据 1 位置含有的地雷数枚举满足条件的 1,2 位置的状态,而 1,2 位置的状态一旦确定,通过 2 位置含有的地雷数确定 3 位置是否含有地雷,依次类推,通过 i 位置的

  地雷数确定 i+1 位置是否含有地雷,如果中间出现矛盾,枚举下一个满足条件的 1,2 位置的状态。

AC代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 const int maxn=50;
 5 
 6 int n;
 7 int a[maxn];
 8 int status[maxn];
 9 
10 //preStatus : (pos-2,pos-1,pos) 位置的状态对应的十进制数
11 bool Find(int preStatus,int pos)
12 {
13     //preStatus中的(pos-1,pos)才对当前位置的状态有影响
14     int curStatus=(preStatus&11);//二进制数运算,通过 &11 取出preStatus的(pos-1,pos)位置的状态
15     int tot=(curStatus&1)+(curStatus>>1&1);//(pos-1,pos)位置含有的地雷数
16     if(pos == n)//递归终止条件,判断是否满足最后一个位置的地雷数
17         return tot == a[n] ? true:false;
18     if(tot == a[pos])//如果(pos-1,pos)含有的地雷数 == a[pos],那么 pos+1 位置就不能含有地雷
19     {
20         status[pos]=(preStatus<<1);
21         return Find(status[pos],pos+1);
22     }
23     else if(tot == a[pos]-1)//如果(pos-1,pos)含有的地雷数 == a[pos]-1,那么 pos+1 位置必须含有地雷
24     {
25         status[pos]=(preStatus<<1|1);
26         return Find(status[pos],pos+1);
27     }
28     return false;
29 }
30 void Solve()
31 {
32     //1,2 位置可能的状态为四个二进制数 00,01,10,11,转换为十进制数为 0,1,2,3
33     for(int i=0;i <= 3;++i)//枚举 1,2 位置的状态
34     {
35         int tot=(i&1)+(i>>1&1);//1,2 状态含有的地雷数
36         if(tot == a[1])
37         {
38             status[1]=i;
39             if(Find(i,2))
40                 break;
41         }
42     }
43     int res=0;
44     for(int i=1;i < n;++i)
45     {
46         if(i == 1)
47         {
48             res += (status[1]&1)+(status[1]>>1&1);
49             printf("%d%d",status[1]>>1&1,status[1]&1);
50             continue;
51         }
52         res += (status[i]&1);
53         printf("%d",status[i]&1);
54     }
55     printf("\n%d\n",res);
56 }
57 int main()
58 {
59     scanf("%d",&n);
60     for(int i=1;i <= n;++i)
61         scanf("%d",a+i);
62     Solve();
63 }
View Code

 

posted @ 2018-12-03 09:10  HHHyacinth  阅读(444)  评论(0编辑  收藏  举报