Contest1585 - 2018-2019赛季多校联合新生训练赛第一场(部分题解)
Contest1585 - 2018-2019赛季多校联合新生训练赛第一场
C 10187 查找特定的合数
D 10188 传话游戏
H 10192 扫雷游戏
题干:
题目描述 自然数中除了能被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
题解:
步骤:
(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 }
时间复杂度分析:
(1):预处理线性筛 : O(n),n = 2e5
(2):枚举 : O( n*√n * log(cnt) ),(n = 2e5,cnt = 17984);
所以总的时间复杂度为 O( n*√n * log(cnt) );
题干:
题目描述 有这样一个朋友网络,如果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
题解:
考察知识点:强连通分量分解
以人作为顶点,人与人之间的认识关系作为边建立一个有向图。
通过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 }
题干:
题目描述 小Q空的时候挺喜欢玩玩电脑游戏的。自从编程技术提高后,他就想,要是自己也能开发出一款游戏来,那该多好啊!不过,小Q也不着急,先练好基本功再说。Windows中就有一款叫扫雷的小游戏,挺好玩的,不过想编出一个来,还真不容易。小Q就自己设想了一种简单的扫雷游戏:在n行2列的方格棋盘上,左列某些方格内埋有地雷,而右列每个方格中都有一个数字(0~3),第I格的数字表示:左列第I-1、I、I+1格(即:上、中、下三格)中埋雷的总数。 你的任务是:根据右列的数字分析出左列格子中的地雷(0表示无雷,1表示有雷),并且统计出左列格子中地雷的总数。 小Q想,如果这样的任务能完成了,相信编出更复杂的扫雷游戏也就为期不远了。 输入 第一行,一个整数N(2≤N≤40),第二行有N个数字(以一个空格相隔),表示右列格子中的数字。输入数据保证正确有解。 输出 第一行是N个0、1数字(没有空格相隔),表示左列每格中有无地雷。第二行一个整数,表示地雷总数。 样例输入 7 1 2 3 2 2 2 2 样例输出 0111011 5
题解:
考察知识点:位运算??
每个位置 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 }