牛客练习赛80 题解

A题

我写的是找是否存在两个1之间有0,并且要特判单一个1的情况

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
const int mod=1e9+7;
int main(){
    ios::sync_with_stdio(false);
    string s;
    cin>>s;
    int i;
    int cnt=0;
    int flag=0;
    for(i=0;i<s.size();i++){
        if(s[i]=='1'){
            flag=i+1;
            break;
        }
    }
    if(!flag){
        cout<<0<<endl;
    }
    else{
        for(i=0;i<s.size();i++){
            if(s[i]=='1')
                cnt++;
        }
        if(cnt==1){
            cout<<0<<endl;
            return 0;
        }
        if(cnt==s.size()){
            cout<<1<<endl;
            return 0;
        }
        cnt=0;
        flag=0;
        s=s+"0";
        for(i=flag;i<s.size();i++){
            if(s[i]=='0'&&s[i-1]=='1'){
                cnt++;
                if(i<s.size()-1&&s[i+1]=='1'){
                    flag=1;
                }
            }
        }
        int tmp=0;
        for(i=flag;i<s.size();i++){
            if(i>0&&s[i]=='1'&&s[i-1]=='0'&&s[i+1]=='0'){
                flag=1;
                break;
            }
        }
        cout<<cnt-flag<<endl;
    }
    return 0;
}
View Code

B题

看出规律,只有几种简单的情况,先维护一个和,之后加加减减

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
const ll mod=998244353;
ll a[N],b[N];
int main(){
    ios::sync_with_stdio(false);
    ll res=0,ans=0;
    int i;
    int n;
    cin>>n;
    for(i=0;i<n;i++){
        cin>>a[i];
        res+=a[i];
        res%=mod;
    }
    for(i=0;i<n;i++){
        cin>>b[i];
        ans+=b[i];
        ans%=mod;
    }
    for(i=0;i<3;i++){
        if(i==0){
            cout<<((res-a[0]+mod)%mod)*((ans-b[0]+mod)%mod)%mod<<" ";
        }
        else if(i==1){
            ll tmp1=(b[0]%mod*(res-a[1]+mod))%mod;
            ll tmp2=(a[0]%mod*(ans-b[1]+mod))%mod;
            ll tmp3=a[0]*b[0]%mod;
            cout<<(tmp1+tmp2-tmp3+mod)%mod<<" ";
        }
        else if(i==2){
            ll tmp1=a[0]*b[1]%mod+b[0]*a[1]%mod;
            cout<<tmp1%mod<<" ";
        }
    }
    for(i=3;i<n;i++)
        cout<<0<<" ";
    return 0;
}
View Code

C题

定义状态为f[i][j],i位,第一位以j开头,这样就能列出转移方程,使用矩阵快速幂优化

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
const int mod=100019;
struct node{
    int a[10][10];
    node(){
        memset(a,0,sizeof a);
    }
}S,T;
node operator *(node a,node b) {
    node tmp;
    for(int i=0;i<=9;i++) {
        for(int j=0;j<=9;j++) {
            for(int k=0;k<=9;k++) {
                tmp.a[i][j]=((ll)tmp.a[i][j]+(ll)a.a[i][k]*b.a[k][j]%mod)%mod;
            }
        }
    }
    return tmp;
}
node martix_pow(node s,ll b){
    int i;
    node tmp;
    for(i=0;i<10;i++){
        tmp.a[i][i]=1;
    }
    while(b){
        if(b&1)
            tmp=tmp*s;
        s=s*s;
        b>>=1;
    }
    return tmp;
}
int main(){
    ios::sync_with_stdio(false);
    int i;
    int n;
    cin>>n;
    for(i=1;i<=9;i++){
        S.a[0][i]=1;
    }
    for(i=0;i<10;i++){
        for(int j=0;j<10;j++){
            if(j<=i){
                T.a[i][j]=1;
            }
        }
    }
    T=martix_pow(T,n-1);
    S=S*T;
    ll ans=0;
    for(i=1;i<=9;i++){
        ans=(ans+S.a[0][i])%mod;
    }
    cout<<ans<<endl;
    return 0;
}
View Code

D题

首先显然的贪心就是尽量选的远是更优的,因为能选就选显然不劣。

一般来说是一个个加边试探过去,看看能否成立。这里就可以用倍增优化

直到最后一段区间没法用倍增,因为会超过K,这段区间使用二分check一下。

在这样的优化下,tarjan算法的复杂度就能够满足条件了

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e6+10;
const int mod=1e9+7;
int n,m;
ll k,sum;
int x[N],y[N];
vector<int> g[N];
int dfn[N],low[N];
int ins[N],idx;
stack<int> q;
void tarjan(int u){
    dfn[u]=low[u]=++idx;
    int i;
    q.push(u),ins[u]=1;
    for(i=0;i<g[u].size();i++){
        int j=g[u][i];
        if(!dfn[j]){
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(ins[j]){
           low[u]=min(low[u],dfn[j]);
        }
    }
    if(dfn[u]==low[u]){
        ll tmp=0;
        while(1){
            int t=q.top();
            q.pop();
            ins[t]=0;
            tmp++;
            if(t==u)
            break;
        }
        sum+=(tmp*tmp);
    }
}
ll check(int l,int r){
    int i,j;
    r=min(r,m);
    vector<int> num;
    for(i=l;i<=r;i++){
        g[x[i]].push_back(y[i]);
        num.push_back(x[i]);
        dfn[x[i]]=dfn[y[i]]=0;
    }
    sum=n;
    for(i=0;i<num.size();i++){
        if(!dfn[num[i]]){
            idx=0;
            tarjan(num[i]);
            sum-=idx;
        }
    }
    for(i=l;i<=r;i++){
        g[x[i]].clear();
    }
    return sum;
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m>>k;
    int i;
    for(i=1;i<=m;i++){
        cin>>x[i]>>y[i];
    }
    int now=1;
    int ans=0;
    for(now=1;now<=m;now++){
        int i=0;
        while(check(now,now+(1<<i)-1)<=k){
            if(now+(1<<i)-1>m)
                break;
            i++;
        }
        int l=now+(1<<(i-1))-1,r=min(m,now+(1<<i)-1);
        while(l<r){
            int mid=l+r+1>>1;
            if(check(now,mid)<=k){
                l=mid;
            }
            else{
                r=mid-1;
            }
        }
        now=l;
        ans++;
    }
    cout<<ans<<endl;
    return 0;
}
View Code

E题

首先看看官方题解:

-----------------------------------------------

考虑先把所有询问离线,然后从小到大枚举询问右端点,用某种数据结构维护每个左端点的答案。

考虑对于一个点,只保留最后一次覆盖它的线段,并把这个点的权值记为线段的标号。

考虑把相邻的权值相同的点合并成同一段来维护,段的权值就是这些点的权值。那么新加入一条线段,就相当于合并(或者说删除)它覆盖到的旧的段,把这些段的权值变成当前枚举到的右端点。

当然可能有一些段会被当前线段从中截断,这种段至多有两个,可以先把它们分解成两段。于是我们可以假定新加入的线段覆盖到的每个段都是完整的。

对应在统计答案的数据结构上,如果合并了一个权值是 xx 的段 AA,相当于数据结构上 [x+1,r][x+1,r] 这个区间的答案整体加上段 AA 的长度,其中 rr 是当前的右端点。

由于询问是单点询问,因此可以用树状数组来维护差分数组实现区间修改(或者直接使用线段树)。

考虑如何维护出覆盖了哪些线段。可以使用 set 来维护每两个连续段之间的分界点,每次暴力合并连续段即可。根据均摊理论,总复杂度就是O(nlogn)。

--------------------------------------------------

解释就是因为我们从小枚举右端点,因此树状数组就能够维护的是i到当前r的最大覆盖范围。

我们用set维护所有线段,并且每条线段都用最大的位置来取代他,这样就能够在树状数组上就可以刚好维护而不会重复维护。

具体注释可以看代码。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pll;
const int N=2e5+10;
const int mod=1e9+7;
ll tr[N];
ll ans[N];
int lowbit(int x){
    return x&-x;
}
void add(int x,int c){
    int i;
    for(i=x;i<N;i+=lowbit(i)){
        tr[i]+=c;
    }
}
ll sum(int x){
    int i;
    ll res=0;
    for(i=x;i;i-=lowbit(i)){
        res+=tr[i];
    }
    return res;
}
set<pll> s;
int L[N],R[N];
vector<pll> num[N];
int main(){
    ios::sync_with_stdio(false);
    int i;
    int n,q;
    cin>>n>>q;
    for(i=1;i<=n;i++){
        cin>>L[i]>>R[i];
    }
    for(i=1;i<=q;i++){
        int a,b;
        cin>>a>>b;
        num[b].push_back({a,i});
    }
    s.insert({1e9+10,0});//m1存贮的是线段的两两分界点
    for(i=1;i<=n;i++){
        int l=L[i],r=R[i];
        int pos=-1,d=l-1;
        auto it=s.lower_bound({l-1,0});//找到大于l-1的第一个点
        while(1){
            int x=it->first,id=it->second;//找到位置和id
            int tmp=min(x,r)-d;//计算长度
            add(id+1,tmp);//因为到id的位置这段都已经覆盖过了,所以用了i后,id+1到i需要加上这段长度
            add(i+1,-tmp);
            if(pos==-1){
                pos=id;//记录分界点
            }
            if(x<=r){
                auto it1=it;
                it++;
                d=x;
                s.erase(it1);
            }
            else{
                break;
            }
        }
        if(d>1){
            s.insert({l-1,pos});//l-1的还是由碰到的第一个所控制的
        }
        s.insert({r,i});//到l-r是由i控制的
        for(int j=0;j<num[i].size();j++){
            ans[num[i][j].second]=sum(num[i][j].first);
        }
    }
    for(i=1;i<=q;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}
View Code
没有人不辛苦,只有人不喊疼
原文地址:https://www.cnblogs.com/ctyakwf/p/14650347.html