北京信息科技大学校赛 题解 | AK记录贴
比赛链接:https://ac.nowcoder.com/acm/contest/940#question
花了一天时间全部解决,题目不难,全是基础题+模板题。
A - kotori和糖果
链接:https://ac.nowcoder.com/acm/contest/940/A
题目描述
kotori共有n块糖果,每块糖果的初始状态是分散的,她想把这些糖果聚在一堆。但她每次只能把两堆糖果合并成一堆。
已知把两堆数量为a和b的糖果聚在一堆的代价是|a-b|。
kotori想知道,她把这n块糖果聚在一堆的最小代价是多少?
解题思路
直接贪心+搜索,复杂度O(n),可以解决1e6以内的查询。
对于1e6~1e18区间内的,还是用搜索。由于搜索过程中,奇数偶数的情况大约各一半,搜索分支数不会超过2^logn,具体是多少我也不知道。。。
先用迭代预处理1e6以内的,对剩下的进行记忆化搜索。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> using namespace std; typedef long long ll; const int maxn = 1000100; ll ans[maxn]; void init() { ans[1] = 0; ans[2] = 0; ans[3] = 1; for(int i=4;i<maxn;i++) { if(i&1) ans[i] = ans[i/2] + ans[i/2+1] + 1; else ans[i] = 2*ans[i/2]; } } ll dfs(ll n) { if(n==1) return 0; if(n==2) return 0; if(n<maxn) return ans[n]; if(n&1) return dfs(n/2) + dfs(n/2+1)+1; else return 2*dfs(n/2); } int main() { init(); int T; cin>>T; ll n; while(T--){ scanf("%lld", &n); printf("%lld\n", dfs(n)); } return 0; }
B - kotori和气球
链接:https://ac.nowcoder.com/acm/contest/940/B
题目描述
kotori最近迷上了摆气球的游戏。她一共有n种气球,每种气球有无数个。她要拿出若干个气球摆成一排。
但是,由于气球被施放了魔法,同样种类的气球如果相邻会发生爆炸,因此若两个相邻的气球种类相同被视为不合法的。
kotori想知道,摆成一排m个一共有多少种不同的方案?
由于该数可能过大,只需要输出其对109取模的结果。
解题思路
开始犹豫半天没写,以为用什么Polya定理。然后又想着是不是要dp,拿纸一推算,结果不就是n*(n-1)^(m-1)嘛。
交上去就一直WA。。。
WA了半天,定睛一看,对109取模。。。当做1e9了,太天真了。。。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> using namespace std; typedef long long ll; const ll mod = 109; ll pow(ll a, ll n) { ll res = 1; while(n) { if(n&1) res = res * a % mod; a = a * a % mod; n >>= 1; } return res; } int main() { ll n, m; cin>>n>>m; printf("%lld\n", n*pow(n-1, m-1) % mod); return 0; }
C - kotori和出道
链接:https://ac.nowcoder.com/acm/contest/940/C
题目描述
kotori和一些偶像们围成一圈,一共有n个偶像(包括kotori自己),她们的序号顺时针从1到n。现在她们从1号开始进行顺时针报数:1,2,1,2,1……
每个报到2的人出列。直到剩下最后一个人为止。那么这个剩下的人就能被选中发专辑出道啦!
kotori可以暗箱操作决定自己的序号。她很想发专辑,于是向聪明的你求助,初始序号设定为多少才可以呢?
解题思路
约瑟夫环的翻版问题,也就是m==2的特例。
研究了半天约瑟夫环的最后一个人编号,在这篇博客指导下,学会了递推公式
f[1] = 0; i=1
f[i] = (f[i-1] + m) % i; (i>1)
然而还是要O(n)解决这道题。
最后打表找规律,发现每个2^k区间内,答案+2递增,于是O(1)解决。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> using namespace std; typedef long long ll; ll low(ll n) { ll res = 1; while(res<=n/2) res *= 2; return res; } int main() { int t; cin>>t; while(t--) { ll n; scanf("%lld", &n); printf("%lld\n", (n-low(n))*2+1); } return 0; }
D - kotori和迷宫
链接:https://ac.nowcoder.com/acm/contest/940/D
题目描述
kotori在一个n*m迷宫里,迷宫的最外层被岩浆淹没,无法涉足,迷宫内有k个出口。kotori只能上下左右四个方向移动。她想知道有多少出口是她能到达的,最近的出口离她有多远?
解题思路
最最基础的迷宫问题,简单BFS,一发AC。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<queue> using namespace std; char mp[35][35]; int n, m; int dis, sx, sy, ans; struct node { int x, y, dis; node(int xx, int yy, int d): x(xx), y(yy), dis(d){} }; const int dx[] = {0, 0, 1, -1}; const int dy[] = {1, -1, 0, 0}; bool vis[35][35]; void bfs() { node n0 = node(sx, sy, 0); queue<node> q; q.push(n0); vis[sx][sy] = 1; while(q.size()) { node nd = q.front(); q.pop(); // printf("(%d,%d):%d\n", nd.x, nd.y, nd.dis); if(mp[nd.x][nd.y]=='e') { ans++; if(nd.dis<dis) dis = nd.dis; continue; } for(int i=0;i<4;i++) { int nx = nd.x+ dx[i]; int ny = nd.y+ dy[i]; if(nx>=0 && nx<n && ny>=0 && ny<m && mp[nx][ny]!='*' && !vis[nx][ny]) { q.push(node(nx, ny, nd.dis+1)); vis[nx][ny] = 1; } } } } int main() { cin>>n>>m; for(int i=0;i<n;i++) { scanf("%s", mp[i]); for(int j=0;j<m;j++) { if(mp[i][j]=='k') { sx = i, sy = j; } } } dis = 10000; bfs(); if(dis==10000) printf("-1\n"); else printf("%d %d\n", ans, dis); return 0; }
E - kotori和素因子
链接:https://ac.nowcoder.com/acm/contest/940/E
题目描述
kotori拿到了一些正整数。她决定从每个正整数取出一个素因子。但是,kotori有强迫症,她不允许两个不同的正整数取出相同的素因子。
她想知道,最终所有取出的数的和的最小值是多少?
注:若a%k==0,则称k是a的因子。若一个数有且仅有两个因子,则称其是素数。显然1只有一个因子,不是素数。
解题思路
最后才A这道题,也是一发过。先预处理素因子,简单的DFS。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<set> using namespace std; const int maxn = 1010; set<int> prms[maxn+1]; int n, a[11]; void init() { for(int i=2;i<=maxn;i++) { int j = i, p = 2; while(j>1) { if(j%p==0) { prms[i].insert(p); j /= p; } else { p++; } } } } int ans; bool vis[maxn]; void dfs(int k, int sum) { if(k==n) { ans = min(sum, ans); return; } for(set<int>::iterator it=prms[a[k]].begin();it!=prms[a[k]].end();it++) { if(!vis[*it]) { vis[*it] = true; dfs(k+1, sum+*it); vis[*it] = false; } } } int main() { init(); cin>>n; for(int i=0;i<n;i++) { cin>>a[i]; } ans = 100000; dfs(0, 0); if(ans==100000) { printf("-1\n"); } else { printf("%d\n", ans); } return 0; }
F - kotori和n皇后
链接:https://ac.nowcoder.com/acm/contest/940/F
题目描述
kotori最近在研究n皇后的问题。
所谓n皇后问题是这样的:一个n*n的地图,上面一共放n个皇后,保证任意两个皇后都不能互相攻击(每个皇后可以攻击同一行、同一列以及同一45度角斜线和135度角斜线上的所有其他皇后)。
kotori思考了很久都无法得出答案,整个人都变成琴梨了。她于是拿了一堆皇后在一个无穷大的棋盘上模拟,按照次序一共放了k个皇后。
但是,皇后的站位太复杂了,kotori甚至不知道是否存在两个皇后会互相攻击。于是她想问问聪明的你,在第i个皇后放置在棋盘上之后,是否存在两个皇后可以互相攻击?
解题思路
如果对原来的n皇后问题的相互攻击处理方法有一定了解的话,这题就非常的easy了。
简单来说,用四个数组分别记录行、列、左斜线、右斜线上是否已经放上了皇后,只需要O(1)就能判断新加入的皇后与前面所有的皇后能飞互相攻击。
利用set存储,时间复杂度O(nlogn)。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<set> using namespace std; typedef long long ll; const ll N = 1e9+7; set<ll> R; set<ll> C; set<ll> A; set<ll> B; int main() { int k, xi, yi; cin>>k; scanf("%d %d", &xi, &yi); R.insert(xi); C.insert(yi); A.insert(xi+yi); B.insert(N+xi-yi); int flag = k+1; for(int i=2;i<=k;i++) { scanf("%d %d", &xi, &yi); if(flag<k) continue; if(R.find(xi)!=R.end()) flag = i; else R.insert(xi); if(C.find(yi)!=C.end()) flag = i; else C.insert(yi); if(A.find(xi+yi)!=A.end()) flag = i; else A.insert(xi+yi); if(B.find(N+xi-yi)!=B.end()) flag = i; else B.insert(N+xi-yi); // printf("%d\n", flag); } int t; cin>>t; while(t--) { int p; scanf("%d", &p); if(p>=flag) printf("Yes\n"); else printf("No\n"); } return 0; }
G - kotori和抽卡(二)
链接:https://ac.nowcoder.com/acm/contest/940/G
题目描述
kotori最近喜欢上了lovelive这个游戏,因为她发现自己居然也是里面的一个人物。
lovelive有个抽卡系统。共有R、SR、SSR、UR四个稀有度,每次单抽对应稀有度的概率分别是80%,15%,4%,1%。
然而,kotori抽了很多次卡还没出一张UR,反而出了一大堆R,气得她想删游戏了。她想知道n次单抽正好出m张R卡的概率是多少?
解题思路
概率为C(n, m)*p^m*(1-p)^(n-m)。
不会的话翻看《概率论与数理统计》教材吧。不过这好像是高中知识?
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; double C(int n, int m) { double res = 1; for(int i=1;i<=m;i++) { res = res*(n-i+1)/i; } return res; } double pow(double a, int n) { double res = 1; while(n) { if(n&1) res *= a; a *= a; n >>= 1; } return res; } int main() { int n, m; cin>>n>>m; printf("%.4lf\n", C(n, m)*pow(0.8, m)*pow(0.2, n-m)); return 0; }
H - andy和购物
链接:https://ac.nowcoder.com/acm/contest/940/H
题目描述
解题思路
基于贪心的思想,最贵的货物使用最多的折扣(bj较小的),这样优惠肯定最多,花费也就最少了。
那么将货物价格与折扣分别排序,两者相乘求和即为答案。
时间复杂度O(nlogn+n)。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<algorithm> using namespace std; int prc[1010]; double dsc[1010]; int main() { int t; cin>>t; while(t--) { int n; cin>>n; for(int i=0;i<n;i++) { scanf("%d", &prc[i]); } for(int i=0;i<n;i++) { scanf("%lf", &dsc[i]); } sort(prc, prc+n); sort(dsc, dsc+n); double res = 0; for(int i=0;i<n;i++) { res += prc[i]*dsc[n-1-i]; } printf("%.3lf\n", res); } return 0; }
I - andy种树
链接:https://ac.nowcoder.com/acm/contest/940/I
题目描述
andy在他的庄园里种了n棵树,排列成一排,标号为1到n。最开始的时候n棵树的高度都是0,也就是种子刚刚被埋下,树还没有长出来。
andy会一种魔法,他每使用一次魔法,就可以让树标号落在连续区间[l, r]里的树的高度增加1。他可以使用q次这种魔法,然后他很好奇,在使用了q次魔法之后,他的所有树的高度分别是多少呢?
解题思路
树状数组/线段树裸题,分析可以参考我的前一篇博客:树状数组 | 三道模板题小结。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> using namespace std; #define lowbit(x) ((x)&(-x)) int n, m; int C[500010]; // 维护差分信息 int sum(int x) { int res = 0; while(x) { res += C[x]; x -= lowbit(x); } return res; } void add(int x, int d) { while(x<=n) { C[x] += d; x += lowbit(x); } } int main() { cin>>n>>m; while(m--) { int x, y; scanf("%d %d", &x, &y); add(x, 1); add(y+1, -1); } printf("%d", sum(1)); for(int i=2;i<=n;i++) { printf(" %d", sum(i)); } return 0; }
J - andy的树被砍了
链接:https://ac.nowcoder.com/acm/contest/940/J
题目描述
andy又开始种树了,他觉得老用魔法不太好,这次他决定老老实实地每天种一棵树,第i天种一颗高度为hi的树,按理说老老实实种树就完事了,哪有那么多问题呢?但是他们学校有个叫kotori的人,非常爱砍树,每天都会把所有andy已经种下的树砍掉ci,如果第i天的时候某棵树的高度已经小于等于ci了,那么这棵树就会死亡,以后再也不会被砍了。并且如果到了第n天,有一些树还没被砍,那么kotori就会在第n + 1天把这些树全部砍死。
解题思路
题意很好理解,对于第i天的树,只要被砍到高度h<=0就被砍死。
如果对每棵树都进行判断,最坏情况下要判断n*n次,显然会超时。
我们可以把砍掉的高度累加求和,即找到最小的d使
h[i] - (c[i]+...+c[d]) <= 0
即
h[i]-(cc[d]-cc[i-1]) ==> h[i]+cc[i-1]<=cc[d]
那么二分查找cc[d],复杂度O(n*logn)。
AC代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; int h[500010], c[500010]; ll cc[500010]; int main() { int n; cin>>n; for(int i=1;i<=n;i++) { scanf("%d", &h[i]); } for(int i=1;i<=n;i++) { scanf("%d", &c[i]); cc[i] = cc[i-1] + c[i]; } // 找到最小的d使h[i]-(c[i]+...+c[d])<=0 // 即h[i]-(cc[d]-cc[i-1])==>h[i]+cc[i-1]<=cc[d] // 二分查找cc[d] for(int i=1;i<=n;i++) { printf("%d ", lower_bound(cc+1, cc+1+n, h[i]+cc[i-1])-cc); } putchar('\n'); return 0; }
(完)