2024牛客寒假算法基础集训营1【难题详解】

F. 鸡数题!

第二类斯特林数

题意:有n个二进制位,m个数,每个数能获取至少1个二进制位,并且没有两个数的某一二进制位均为1。

等价于:将n个不同小球放到m个相同盒子,且盒子不能为空的方案数

所以答案即为第二类斯特林数

O(nlogn)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
int ksm(int a,int n){
    int r=1ll;
    while(n){
        if(n&1)r=r*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return r;
}
namespace binom {
    int fac[N], ifac[N];
    int __ = []
    {
        fac[0] = 1;
        for (int i = 1; i <= N - 5; i++)
            fac[i] = fac[i - 1] * i % mod;
        ifac[N - 5] = ksm(fac[N - 5], mod - 2);
        for (int i = N - 5; i; i--)
            ifac[i - 1] = ifac[i] * i % mod;
        return 0;
    }();
    inline int C(int n, int m)
    {
        if (n < m || m < 0)return 0;
        return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
    }
    inline int A(int n, int m)
    {
        if (n < m || m < 0)return 0;
        return fac[n] * ifac[n - m] % mod;
    }
}
using namespace binom;
void solve(){
    int n,m;cin>>n>>m;
    int sum=0;
    for(int i=0;i<=m;i++){
        sum=(sum+((m-i)%2?-1:1)*(ksm(i,n)*ifac[i]%mod*ifac[m-i]%mod)+mod)%mod;
    }
    cout<<sum<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

H. 01背包,但是bit

贪心/分类讨论/二进制:

关键:要枚举分界点

dp[j]表示:满足m的二进制表示下第j位为1,且背包的体积=m-(1<<j) 所能装的价值

但是这个dp数组实际还有一个状态没有表示到,就是当背包的体积为m的情况,再考虑一下这个即可

O(27n)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
    int n,m;cin>>n>>m;
    vector<int>dp(30);
    for(int i=0;i<n;i++){
        int a,b;cin>>a>>b;
        for(int j=26;j>=0;j--){
            if((m>>j)&1){
                if(b<(1ll<<j))dp[j]+=a;
                else if(((b>>j)|((m>>j)-1))==(m>>j)-1)dp[j]+=a;
            }
        }
        if((b|m)==m)dp[27]+=a;
    }
    int ans=0;
    for(int i=0;i<=27;i++){
        ans=max(ans,dp[i]);
    }
    cout<<ans<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    cin>>_;
    while(_--)solve();
    return 0;
}

K. 牛镇公务员考试

可以发现,一定会形成一个环,且环可能延申有边(这就是基环树)

且如果环上某一点延申边的话,则该边的方案数=该点的方案数

而该点的方案数=环的方案数

∴ ans=各个环的方案数的乘积

O(n)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
    int n;cin>>n;
    vector<int>nx(n+1);
    string s[n+1];
    for(int i=1;i<=n;i++){
        int a;cin>>a;
        cin>>s[i];
        nx[i]=a;
    }
    vector<int>book(n+1);
    vector<int>v(n+1);
    int ans=1;
    for(int i=1;i<=n;i++){
        int j=i;
        while(!book[j]){book[j]=i;j=nx[j];}
        if(book[j]==i){
            int root=j;
            int cnt=0;
            for(int k=0;k<5;k++){
                v[root]=k;
                int t=s[root][k]-'A';
                j=nx[root];
                while(j!=root){
                    t=s[j][t]-'A';
                    j=nx[j];
                }
                if(t==k)cnt++;
            }
            ans=ans*cnt%mod;
        }
    }
    cout<<ans<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

D. 数组成鸡

预处理/分治

由于M的范围是[-1e9,1e9],如果数组中>30个数的绝对值>=2,那么就不用考虑,因为2^30>1e9

所以要时刻保证数组中不能有30个绝对值超过2的数

做法:当n>=30时,枚举每个数,分别算出如果其是1/-1时数组所能得到的数,存进map里。

这样的数x不超过30个因为要满足 n > mp[x]+mp[x-2] > n-30 。反证:假设这样的数超过了30个,也就是说 n - (mp[x]+mp[x-2]) 一定> 30 ,矛盾。

2<=n<30时,直接让数组中的每个数加上[-1e5,1e5],因为  \sqrt {1e9} = 1e4.5 。

O(30*n)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
unordered_map<int,int>mp;
unordered_map<int,int>cnt;
void solve(){
    int n,q;cin>>n>>q;
    set<int>st{0};
    vector<int>v(n);
    for(int i=0;i<n;i++){
        cin>>v[i];
        mp[v[i]]++;
    }
    cnt=mp;
    if(n==1){
        while(q--){
            int a;cin>>a;
            yes;
        }
        return;
    }else if(n>=30){
        for(auto [x,y]:cnt){
            if(n-mp[x]-mp[x-2]>=30)continue;
            int flag=1;
            int sum=1;    
            for(int i=0;i<n;i++){
                sum*=(v[i]-(x-1));
                if(sum>1e9||sum<-1e9){flag=0;break;}
            }
            if(flag)st.insert(sum);
            if(n-mp[x]-mp[x+2]>=30)continue;
            flag=1;sum=1;
            for(int i=0;i<n;i++){
                sum*=(v[i]-(x+1));
                if(sum>1e9||sum<-1e9){flag=0;break;}
            }
            if(flag)st.insert(sum);
        }
    }else{
        sort(all(v));
        for(int i=n-1;i>=0;i--)v[i]-=v[0];
        for(int j=-1e5;j<=1e5;j++){
            for(int i=0;i<n;i++){
                int flag=1;
                int sum=1;
                for(int i=0;i<n;i++){
                    sum*=(v[i]-j);
                    if(sum>1e9||sum<-1e9){flag=0;break;}
                }
                if(flag)st.insert(sum);
            }
        }
        reverse(all(v));
        for(int i=n-1;i>=0;i--)v[i]-=v[0];
        for(int j=-1e5;j<=1e5;j++){
            for(int i=0;i<n;i++){
                int flag=1;
                int sum=1;
                for(int i=0;i<n;i++){
                    sum*=(v[i]-j);
                    if(sum>1e9||sum<-1e9){flag=0;break;}
                }
                if(flag)st.insert(sum);
            }
        }
    }
    while(q--){
        int a;cin>>a;
        if(st.count(a))yes;
        else no;
    }
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

J. 又鸟之亦心

二分

关键点:第 i 个任务结束时,一定有一个人在 𝑎𝑖 处

所以check时:用set记录某一个人(具体是谁不重要)在𝑎𝑖处,另一个人可能的位置集合,若某时刻set为空则false

初始时:某个人在a[0],另一个人(可能是x也可能是y)可能的位置是{x,y}

O(nlognlogn)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f;
void solve(){
    int n,x,y;cin>>n>>x>>y;
    vector<int>a(n);
    for(int i=0;i<n;i++)cin>>a[i];

    function<int(int)> check=[&](int mid)->int{
        set<int>st;
        if(abs(x-y)<=mid){st.insert(x);st.insert(y);}
        else return 0;
        for(int i=0;i<n;i++){
            if(i&&abs(a[i]-a[i-1])<=mid){
                st.insert(a[i-1]);
            }
            while(st.size()&&*st.begin()<a[i]-mid){
                st.erase(st.begin());
            }
            while(st.size()&&*st.rbegin()>a[i]+mid){
                st.erase(*st.rbegin());
            }
            if(!st.size())return 0;
        }
        return 1;
    };

    int l=0,r=1e9;
    while(l+1!=r){
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid;
    }
    cout<<r<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值