牛客刷题总结


前言

这几天牛客网的刷题记录。


一、博弈论:Alice And Bob

题记: 一般的Nim游戏,只要满足:
先手必败状态:a1 ^ a2 ^ a3 ^ … ^an = 0
先手必胜状态:a1 ^ a2 ^ a3 ^ … ^an ≠ 0
*具体来说:
先手必败状态:SG1 ^ SG2 ^ SG3 ^ … ^SGn = 0
先手必胜状态:SG1 ^ SG2 ^ SG3 ^ … ^SGn ≠ 0

题目:https://ac.nowcoder.com/acm/contest/11166/A

Alice and Bob like playing games. There are two piles of stones with numbers n and m. Alice and Bob take turns to operate, each operation can take away k(k>0) stones from one pile and take away s×k(s≥0) stones from another pile. Alice plays first. The person who cannot perform the operation loses the game.

思路:打表解决,关键是要理解SG函数递推关系。就这道题来说:若设f[i][j]数组,i,j分别记录两堆石子的数量,f[i][j]记录获胜情况。 关键是:i,j若是必败,i+k,j+k×l就是必胜状态。

作者本意:让acmer得出结论:如果某堆石子数量是i,另一堆最多只有一种数量满足后手胜。
证明:
反证法:假设(i, p)和(i q)都是后手必胜,且q > p。那么在状态(i, q)时,先手可以在第二堆选q-p个,第一堆选0个,转移到后手胜的(, p),说明(i q)是先手胜,矛盾。
知道这个结论后,我们可以直接记录所有后手胜的pair,每次对于一个i,根据之前的pair去推导是否有一个满足后手胜的搭配j。复杂度是0(N2log N),实际速度近似O(N)。

AC的代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 5001;

bool f[N+5][N+5];

void init(){
    
    
    
    for(int i=0;i<N;i++){
    
    
        for(int j=0;j<N;j++){
    
    
            if(!f[i][j]){
    
    
                for(int k=1;i+k<N;k++){
    
    
                    for(int p=0;j+k*p<N;p++){
    
    
                        f[i+k][j+k*p] = 1;
//                      f[i+k*p][j+k] = 1;  // 这一步可能会越界数组.
                    }
                }
                for(int k=1;j+k<N;k++){
    
    
                    for(int p=0;i+k*p<N;p++){
    
    
                        f[i+k*p][j+k] = 1;
                    }
                }
            }
        }
    }
    
}

int main()
{
    
    
    init();
    int t;
    scanf("%d",&t);
    int a,b;
    while(t--){
    
    
        scanf("%d%d",&a,&b);
        
        if(f[a][b]){
    
    
            puts("Alice");
        }else{
    
    
            puts("Bob");
        }
    }
    
    return 0;
}

二、几何.

在这里插入图片描述

题目大意如图:给定r,a,b,h。求红线的长度。

两种方法:

  1. 推长度关系
    几何关系
    在这里插入图片描述
#include <iostream>
#include <cstdio>
#include <cmath>

void solve() {
    
    
    double r, a, b, h;
    cin >> r >> a >> b >> h;
    if(b > 2 * r) puts("Drop");
    else {
    
    
        puts("Stuck");
        double t, x, y;
        t = sqrt(h * h + (a - b) * (a - b) / 4);
        x = b * t / (a - b);
        y = sqrt(x * x - b * b / 4);
        double ans = 2.0 * x * r  / b - y;
        printf("%.10f\n", ans);
    }
}
 
int main() {
    
    
    //freopen("in.txt", "r",stdin);
    //freopen("out.txt", "w", stdout); 
    int t = 1;
    while (t --) {
    
    
        solve();
    }
    return 0;
}

2.二分法(暂时还不会,先贴一个代码)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

double n, m;
double s;
double r, a, b, h;
 
bool check(double mid)
{
    
    
    return s >= 2 * h * mid + 2 *
    (double)sqrt((b / 2 - a / 2) * (b / 2 - a / 2) + h * h) *
    (double)sqrt((h - mid) * (h - mid) + (b * b / 4) - r * r);
}
 
int main()
{
    
    
     
    cin >> r >> b >> a >> h;
    if(r * 2 < a) puts("Drop");
    else
    {
    
    
        puts("Stuck");
        s = b * b / 2 - a * b / 2 + 2 * h * h;
        double l = 0, r = 1e4;
        while(r - l > 1e-10)
        {
    
    
            double mid = (l + r) / 2.0;
            // cout << mid << endl;
            if(check(mid)) l = mid;
            else r = mid;
            // cout << l << ' ' << r << endl;
        }
        printf("%.10lf\n", r);
    }
    return 0;
}

三、鸽巢原理

题目:链接:https://ac.nowcoder.com/acm/contest/11166/F

A positive integer is 3-friendly if and only if we can find a continuous substring in its decimal representation, and the decimal integer represented by the substring is a multiple of 3.
The only line contains two integers L,R(1≤L≤R≤1018), indicating the query.
For each test case output one line containing an integer, indicating the number of valid x.

鸽巢原理:鸽子数量大于巢数量,使得多出来的鸽子只得和其他鸽子挤在一个巢穴里头.

就这道题而言:

  1. 看就想用数位DP做:
    限制比较少,数位DP的思路还是挺明显的。比如记录到某个位置i时,sum[j,i]%3=0/1/2的情况是否能满足,其中j是i之前的某个位置。
  2. n>=100 时必然合法
    从上述DP式子里稍作推理就会发现,根据鸽笼原理,只要位数不少于3位,必然出现一组前缀和%3相同的位置,所以他们这段区间必然%3 = 0。
    不允许前导0也能得出一样的结论。因为只要出现0,单个的0就直接合法了。这样我们只要对<100的n暴力即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

// 鸽巢定理
typedef long long ll;
vector<int> num;

void init(){
    
    
    num.push_back(1);  num.push_back(2);  num.push_back(4);
    num.push_back(5);  num.push_back(7);  num.push_back(8);
    num.push_back(11); num.push_back(14); num.push_back(17);
    num.push_back(22); num.push_back(25); num.push_back(28);
    num.push_back(41); num.push_back(44); num.push_back(47);
    num.push_back(52); num.push_back(55); num.push_back(58);
    num.push_back(71); num.push_back(74); num.push_back(77);
    num.push_back(82); num.push_back(85); num.push_back(88);
}

int main()
{
    
    
    int t;
    cin >> t;
    init();
    ll a,b,ans;
    
    while(t--){
    
    
        ans = 0;
        scanf("%lld%lld",&a,&b);
        
        for(int i=0;i<num.size();i++){
    
    
            if(num[i]>=a&&num[i]<=b)
                ans ++;
        }
        
        printf("%lld\n",b-a+1-ans);
    }
    
    return 0;
}

四、象棋(规律水题)

题目:链接:https://ac.nowcoder.com/acm/contest/11213/F

给定一个n×m的棋盘,全部摆满炮,我们视所有炮都不属于同一阵营,他们之间可以相互攻击但不能不进行攻击直接移动。请问经历若干次攻击,直到不能攻击后,最少能剩余多少个炮。

这里最少能剩余,没有理解透了,结束了看了下题解,发现正常情况下是剩余4个,看代码吧.

#include <iostream>
#include <cstdio>

using namespace std;

typedef  long long ll;

int main()
{
    
    
    int t;
    cin >> t;
    ll m,n;
    int ans;
    while(t--){
    
    
        
        scanf("%lld%lld",&m,&n);
        
        if(m==1&&n==1){
    
    
            ans = 1;
        }else if(m==1||n==1){
    
    
            ans = 2;
        }else{
    
    
            ans = 4;
        }
        
        cout << ans << endl;
    }
    
    
    return 0;
}

五、皇城PK

题目:
链接:https://ac.nowcoder.com/acm/contest/11213/E

有n名选手会进行mmm次比赛,每次比赛不会出现平局的情况,只会有一个胜者。在每次比赛完成之后,我们视胜者选手的实力比败者选手的实力强,如果出现选手AAA打败选手B,选手B打败选手C,选手C打败选手A,则视为他们的实力全部相同。
若该赛季最终冠军是属于实力最强者,请问依照现在已有的比赛结果,最多有多少个选手可能获得冠军(如果已知两个人的实力一样强,那么他们两个人都不能获得冠军)。

题记: 一开始做题时想到了"获得冠军"这道题目,取两个集合A.B,胜者加入A集合,败者加入B集合,若最终A.size()-B.size()=1,则一定能产生冠军.
后来把这道题想复杂了,认为,A不可能获得冠军的条件是:A打败了B,B打败了C,C打败了A,确实是这样,利用vector可以轻松将这种关系表示出来.不过直接超时了…见代码吧,很巧妙,我没想到.

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e5;

int p[N+5];

int main()
{
    
    
    int n,m;
    cin >> n >> m;
    int a,b;
    
    memset(p,-1,sizeof(p));
    
    while(m--){
    
    
        scanf("%d%d",&a,&b);
        
        if(p[a]==-1){
    
    
            p[a] = 1;
            p[b] = 0;
        }else{
    
    
            p[b] = 0;
        }
    }
    
    int ans = 0;
    for(int i=1;i<=n;i++){
    
    
        if(p[i]){
    
    
            ans++;
        }
    }
    
    cout << ans << endl;
    
    return 0;
}

六、字符串处理(找出两个字符串的不同处)

题目:
链接:https://ac.nowcoder.com/acm/contest/18196/G

One day, Binbin picked up a string S at the beach, but she likes string T. Therefore, she needs to take some action on the string to make the string S become the string T. She has two kind of operation to modify the string. The first operation is to delete Y characters after the X-th position of the string (including the X-th character).The second operation is to insert the string A after the X-th position of the string, if you want to insert it at the beginning, just insert A after the 0th position.
For example, there is a string S=“iwannaac”, choose the first operation, delete 2 characters after the 7th character, it will become “iwanna”; then choose the second operation, insert the string after the 6th character “wa”, it will become “iwannawa”.
How many operations does Binbin need to take at least to turn the string S into a string T ?
1≤∣S∣,∣T∣≤100000.
|S| represents the length of the string S.
|T| represents the length of the string T.

简化题意:就是给定两个字符串,如果这两个串的不同处>2,则输出2,否则输出1,完全相同则输出0.

针对字符串的处理问题,这道通过前后指针向中间靠拢判断.见代码

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int main()
{
    
    
    string s1,s2;
    cin >> s1 >> s2;
    
    int ans;
    
    int i = 0,j = 0,p,q;
    
    if(s1==s2) ans = 0;
    else {
    
    
        if(s1.size()<s2.size()){
    
    
            swap(s1,s2);
        }
        
        p = s1.size()-1,q = s2.size()-1;
        
        while(s1[i]==s2[j]){
    
    
            i++,j++;
        }
        
        
        while(s1[p]==s2[q]){
    
    
            p--,q--;
        }
        
        
        if(q<j){
    
    
            ans = 1;
        }else{
    
    
            ans = 2;
        }
        
    }
    
    printf("%d\n",ans);
    
    
    return 0;
}

另:
几个错误,看了好长时间,曾经一度以为是没有考虑全面?!
1.交换s1,s2时,比较成s1和s2了,应该比较s1.size()和s2.size()。
2.判断结果时没有考虑清除条件(可能是写反了)。
3.s1和s2可能会交换,不应该在交换之前给p,q赋值。


总结:

不知不觉居然写了近7000字了,虽然大部分都是题目占字数多,但是不得不说这些题当时做的,令我印象太深刻了…

猜你喜欢

转载自blog.csdn.net/m0_50435987/article/details/118876224