北京信息科技大学校赛 题解 | 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代码

#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;
}
View Code

 

 

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代码

#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;
}
View Code

 

 

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代码

#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;
}
View Code

 

 

D - kotori和迷宫

链接:https://ac.nowcoder.com/acm/contest/940/D

题目描述

kotori在一个n*m迷宫里,迷宫的最外层被岩浆淹没,无法涉足,迷宫内有k个出口。kotori只能上下左右四个方向移动。她想知道有多少出口是她能到达的,最近的出口离她有多远?

 

解题思路

最最基础的迷宫问题,简单BFS,一发AC。

 

AC代码

#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;
}
View Code

 

 

E - kotori和素因子

 链接:https://ac.nowcoder.com/acm/contest/940/E

题目描述

kotori拿到了一些正整数。她决定从每个正整数取出一个素因子。但是,kotori有强迫症,她不允许两个不同的正整数取出相同的素因子。

她想知道,最终所有取出的数的和的最小值是多少?

注:若a%k==0,则称k是a的因子。若一个数有且仅有两个因子,则称其是素数。显然1只有一个因子,不是素数。

 

解题思路

最后才A这道题,也是一发过。先预处理素因子,简单的DFS

 

AC代码

#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;
}
View Code

 

 

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代码

#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;
}
View Code

 

 

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代码

#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;
}
View Code

 

 

H - andy和购物

链接:https://ac.nowcoder.com/acm/contest/940/H

题目描述

andy要去市场买n件货物,每件货物的价格为ai。商家为了吸引顾客,给每个买N件货物的顾客一个折扣清单,清单上有N个小于1的小数bj表示折扣。对于每个折扣bj,由用户自行决定用它使哪个货物的价格变成bj * ai,并且只能用一次。
andy想让你帮他算一下他最少的花费。

 

解题思路

基于贪心的思想,最贵的货物使用最多的折扣(bj较小的),这样优惠肯定最多,花费也就最少了。

那么将货物价格与折扣分别排序,两者相乘求和即为答案。

时间复杂度O(nlogn+n)。

 

AC代码

#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;
}
View Code

 

 

I - andy种树

链接:https://ac.nowcoder.com/acm/contest/940/I

题目描述

andy在他的庄园里种了n棵树,排列成一排,标号为1到n。最开始的时候n棵树的高度都是0,也就是种子刚刚被埋下,树还没有长出来。

andy会一种魔法,他每使用一次魔法,就可以让树标号落在连续区间[l, r]里的树的高度增加1。他可以使用q次这种魔法,然后他很好奇,在使用了q次魔法之后,他的所有树的高度分别是多少呢?

 

解题思路

 树状数组/线段树裸题,分析可以参考我的前一篇博客:树状数组 | 三道模板题小结

 

AC代码

#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;
}
View Code

 

 

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代码

#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;
}
View Code

 

 


  (完)

 

posted @ 2019-07-03 11:47  izcat  阅读(512)  评论(0编辑  收藏  举报