牛客周赛 Round 62 & 38

 牛客周赛Round62

G-小红的数轴移动(二)_牛客周赛 Round 62 (nowcoder.com)

思路:建图,不妨把数轴往右移动100个单位,那么数轴变为[0,200],源点变为100。对于每一天操作ai,在数轴[0,100)建边i+ai,在数轴(100,200]建边i-ai。然后直接跑最短路。因为题目要把选择操作的编号依次输出出来,所以需要road[i]表示点i的前驱节点,还不够,因为要知道操作的编号。那么还需要roadId[i]表示点i是由哪个编号的操作到达的。并且还需要viss0表示在当前路径中,哪些操作已经被使用过了,避免一个操作在同一个路径中重复使用。之后还原路径,输出操作Id即可。要注意的是,不一定每次都能到达0点而停下来,所以还要判断不能到达0点的情况,一开始写着写着把这个给忽略了,默认以为保证能到达0点。

typedef struct info{
    int w,nod;
    vector<bool> viss;
    bool operator < (const info &x) const{
        return w>x.w;  优先队列中,小的在前
    }
}info;
int n,x;
int arr[105];
vector<array<int,3>> vct[205];
priority_queue<info> pq;
int dis[205],road[205],roadId[205];
bool vis[205];
void dijkstra(int s){
    for(int i=0;i<=200;i++) dis[i]=INT_MAX,vis[i]=0;
    dis[s]=0;
    vector<bool> viss(205,0);
    pq.emplace(info{0,s,viss});
    while(pq.size()){
        int from=pq.top().nod;
        vector<bool> viss0=pq.top().viss;
        pq.pop();
        if(vis[from]) continue;
        vis[from]=1;
        for(auto v:vct[from]){
            int to=v[0],w=v[1],id=v[2];
            if(dis[to]>dis[from]+w&&!viss0[id]){
                road[to]=from,roadId[to]=id;
                dis[to]=dis[from]+w;
                key:要标记当前id,是因为到了当前这个点,是不知道之前用过哪些id的操作的,为了避免重复使用,这里需要viss0
                viss0[id]=1;
                pq.emplace(info{dis[to],to,viss0});
                viss0[id]=0; //细节.同一个from,走向下一个点,自会标记当前的id.这里需要回溯一下。
            }
        }
    }
}
因为dijkstra的边权不能为负值,那么全部值偏移100. 那么0点变为100.
https://ac.nowcoder.com/acm/contest/91177/G
void solve(){           //G  建图
    cin>>n>>x;
    x+=100;
    int sum=0;
    for(int i=1;i<=n;i++){
        cin>>arr[i];
        sum+=arr[i];
        int step=arr[i];
        对于每一次操作,在每一个点都建边.
        for(int j=0;j<100;j++) vct[j].emplace_back(array<int,3>{j+step,step,i});
        for(int j=101;j<=200;j++) vct[j].emplace_back(array<int,3>{j-step,step,i});
    }
    dijkstra(x);
    bool mark[205]={0};
    int cur=100;
    vector<int> ans;
    while(cur!=x&&cur!=0){   还原路径
        ans.emplace_back(roadId[cur]);
        mark[roadId[100]]=1;
        cur=road[cur];
    }
    reverse(ans.begin(),ans.end());
    对于到达不了0点的情况,直接输出sum即可--写着写着把题目以为是到达0点的最短距离,误会题意了,以为一定可以到达0点是有解的.
    if(dis[100]==INT_MAX){
        cout<<sum<<endl;
        for(int i=1;i<=n;i++) cout<<i<<" ";
    }
    else{
        cout<<dis[100]<<endl;
        for(auto a:ans){
            cout<<a<<" ";
            mark[a]=1;
        }
        for(int i=1;i<=n;i++) if(!mark[i]) cout<<i<<" ";
    }
}

E-小红的中位数查询(easy)_牛客周赛 Round 62 (nowcoder.com)

思路:莫队的板子.但是hard冲不过去,会TLE。得需要主席树。

int n,m,B;
int arr[100005],p[100005];
typedef struct Q{
    int l,r,idx;
}Q;
Q q[100005];
//bool cmp(Q a,Q b){     普通分块排序
//    if(a.l/B!=b.l/B) return a.l<b.l;
//    return a.r<b.r;
//}
bool cmp(Q a,Q b){   玄学奇偶性排序
    if (p[a.l] ^ p[b.l]) return a.l/B < b.l/B ;
    else{
        if (p[a.l] & 1) return a.r < b.r;
        else return a.r > b.r;
    }
}
int l=0,r=0,ans[100005];
P2709 小B的询问
https://www.luogu.com.cn/problem/P2709
void solve(){           //E
    cin>>n>>m;
    B=sqrt(n);
    for(int i=1;i<=n;i++) cin>>arr[i];
    for(int i=1;i<=m;i++) cin>>q[i].l>>q[i].r,q[i].idx=i,p[q[i].l]=q[i].l/B,p[q[i].r]=q[i].r/B;
    sort(q+1,q+m+1,cmp);
    l=q[1].l+1,r=q[1].l;
    multiset<int> smal,big;
    for(int i=1;i<=m;i++){
        while(l>q[i].l){  //扩张
            --l;
            if(smal.size()==0||arr[l]>=*smal.begin()) smal.emplace(arr[l]);
            else big.emplace(arr[l]);
            while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
            while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
        }
        while(r<q[i].r){  //扩张
            ++r;
            if(smal.size()==0||arr[r]>=*smal.begin()) smal.emplace(arr[r]);
            else big.emplace(arr[r]);
            while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
            while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
        }
        while(l<q[i].l){ //收缩
            auto p0=smal.find(arr[l]);
            if(p0!=smal.end()) smal.erase(p0);
            else big.erase(big.find(arr[l]));
            while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
            while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
            l++;
        }
        while(r>q[i].r){ //收缩
            auto p0=smal.find(arr[r]);
            if(p0!=smal.end()) smal.erase(p0);
            else big.erase(big.find(arr[r]));
            while(smal.size()<=big.size()) smal.emplace(*big.rbegin()),big.erase(prev(big.end()));
            while(smal.size()-big.size()>1) big.emplace(*smal.begin()),smal.erase(smal.begin());
            r--;
        }
        ans[q[i].idx]=*smal.begin();
    }
    for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
}

D-小红的树上移动_牛客周赛 Round 62 (nowcoder.com)

思路:最最简单的期望题,只是dfs到叶子节点,然后计算答案。

const int mod=1e9+7;
int n;
vector<int> vct[100005];
int quickpow(int a,int b){
    int res=1;
    while(b){
        if(b&1) res*=a,res%=mod;
        a*=a,a%=mod;
        b>>=1;
    }
    return res;
}
int ans=0;
void dfs(int from,int fa,int fm,int cnt){
    if(vct[from].size()==1&&from!=1){
        ans+=cnt*quickpow(fm,mod-2)%mod,ans%=mod;
        return;
    }
    for(auto v:vct[from]) {
        if(v!=fa){
            int cc=vct[from].size();
            if(fa!=0) cc--;
            dfs(v,from,fm*cc%mod,cnt+1);    分母q可以在计算过程中直接取mod,最后计算p*(q^-1)即可.
        }
    }
}
void solve(){           //D 最简单的期望题..其实就是一个dfs.
    cin>>n;
    for(int i=1;i<=n-1;i++){
        int u,v; cin>>u>>v;
        vct[u].emplace_back(v);
        vct[v].emplace_back(u);
    }
    dfs(1,0,1,1);
    cout<<ans;
}

牛客周赛Round32

E-小苯的等比数列_牛客周赛 Round 38 (nowcoder.com)

思路:实际上就是暴力枚举每一个数字作为起点,再枚举公比。但是得用vis和q*arr[i]<=m剪枝,否则就TLE.

int n;
int arr[200005];
int mark[200005];
int vis[200005];
void solve(){       //牛客周赛Round38  //E
    cin>>n;
    int ans=0,mx=0;
    for(int i=1;i<=n;i++){
        cin>>arr[i];
        mark[arr[i]]++,mx=max(mx,arr[i]);
        ans=max(ans,mark[arr[i]]); //q==1
    }
    for(int i=1;i<=n;i++){
        if(vis[arr[i]]) continue;
        vis[arr[i]]=1;            //这个也不能缺,缺了就TLE
        for(int q=2;q*arr[i]<=mx;q++){  //枚举公比    //q*arr[i]<=mx 加了这个判断只跑51ms
            int cnt=0;
            for(int j=arr[i];j<=mx;j*=q){
                if(mark[j]) cnt++;
                else ans=max(ans,cnt),cnt=0;
            }
            ans=max(ans,cnt);
        }
    }
    cout<<ans;
}

F-小苯的回文询问_牛客周赛 Round 38 (nowcoder.com)

思路:又是莫队模板,处理扩展和收缩只要3个变量即可。写麻烦了可能会TLE。

int n,m,B;
int arr[100005],p[200005];
typedef struct Q{
    int l,r,idx;
}Q;
Q q[200005];
bool cmp(Q a,Q b){   玄学奇偶性排序
    if (p[a.l] ^ p[b.l]) return a.l/B < b.l/B ;
    else{
        if (p[a.l] & 1) return a.r < b.r;
        else return a.r > b.r;
    }
}
int l=0,r=0,ans[200005];
void solve(){     //牛客周赛Round38    //F  又是莫队
    cin>>n>>m;
    B=sqrt(n);
    unordered_map<int,int> ha;
    int num=0;
    for(int i=1;i<=n;i++) {
        cin>>arr[i];
        if(ha[arr[i]]==0) ha[arr[i]]=++num;  离散化
        arr[i]=ha[arr[i]];
    }
    for(int i=1;i<=m;i++) cin>>q[i].l>>q[i].r,q[i].idx=i,p[q[i].l]=q[i].l/B,p[q[i].r]=q[i].r/B;
    sort(q+1,q+m+1,cmp);
    l=q[1].l+1,r=q[1].l;
    int cnt[200005];
    int cntNex=0,cnt2=0,cnt3=0;
    for(int i=1;i<=m;i++){
        while(l>q[i].l){  //扩张
            --l;
            cnt[arr[l]]++;
            if(cnt[arr[l]]==2) cnt2++;
            if(cnt[arr[l]]==3) cnt3++;
            if(arr[l]==arr[l+1]) cntNex++;
        }
        while(r<q[i].r){  //扩张
            ++r;
            cnt[arr[r]]++;
            if(cnt[arr[r]]==2) cnt2++;
            if(cnt[arr[r]]==3) cnt3++;
            if(arr[r]==arr[r-1]) cntNex++;
        }
        while(l<q[i].l){ //收缩
            cnt[arr[l]]--;
            if(cnt[arr[l]]==1) cnt2--;
            if(cnt[arr[l]]==2) cnt3--;
            if(arr[l]==arr[l+1]) cntNex--;
            l++;
        }
        while(r>q[i].r){ //收缩
            cnt[arr[r]]--;
            if(cnt[arr[r]]==1) cnt2--;
            if(cnt[arr[r]]==2) cnt3--;
            if(arr[r]==arr[r-1]) cntNex--;
            r--;
        }
        if(cnt3||cnt2>cntNex) ans[q[i].idx]=1;
    }
    for(int i=1;i<=m;i++) ans[i]?cout<<"YES"<<endl:cout<<"NO"<<endl;
}

插入一题2021牛客暑假训练营3的签到容斥题:

J-Counting Triangles_2024牛客国庆集训派对day1 (nowcoder.com)

题意:给定一个完全图,每一条边都为黑或白色,问三元组(a,b,c),a<b<c且边(a,b),(b,c),(a,c)的颜色一样的个数.

思路:一开始还想着怎么dfs,或者枚举中间点然后左边右边同样颜色的个数相乘,但是这样是不知道a,c点是否连着相同的颜色,如果再枚举的话,复杂度是o(n^3)的。正解是容斥:因为是完全图,那么所有三元组的个数为C(3,n)。不符合条件的个数是多少?画图可以发现,在一个不满足条件的三元组的三条边种,必然两条边是颜色是一样的,一条是不一样的。并且必然有两个点连着黑白边,只有一个点连着同样颜色的边。那么想要筛去一个三元组只要计算每一个点 黑边个数*白边个数即可。又因为一个三元组中会有两个连着黑边边的点,意味着一个三元组会被筛去两次,那么总各个数除2即可。答案即为C(3,n)-cntt/2。

namespace GenHelper
{
    unsigned z1,z2,z3,z4,b,u;
    unsigned get()
    {
        b=((z1<<6)^z1)>>13;
        z1=((z1&4294967294U)<<18)^b;
        b=((z2<<2)^z2)>>27;
        z2=((z2&4294967288U)<<2)^b;
        b=((z3<<13)^z3)>>21;
        z3=((z3&4294967280U)<<7)^b;
        b=((z4<<3)^z4)>>12;
        z4=((z4&4294967168U)<<13)^b;
        return (z1^z2^z3^z4);
    }
    bool read() {
        while (!u) u = get();
        bool res = u & 1;
        u >>= 1; return res;
    }
    void srand(int x)
    {
        z1=x;
        z2=(~x)^0x233333333U;
        z3=x^0x1234598766U;
        z4=(~x)+51;
        u = 0;
    }
}
using namespace GenHelper;
int cntW[8005];
int cntB[8005];
看了题解之后,恍然大悟,是容斥..
总的答案:Cn3
减去不合法的三元组:枚举每一个点i,cntW[i]*cntB[i]就是不合法的三元组.
结果要除二,因为每一个不合法的三元组都被筛了两次.
不用考虑其中的三个数字的大小,因为无论数字的大小关系如何,总是可以存在a<b<c的,并且因为存在白,黑边,那么这个三元组就是不合法的.
https://ac.nowcoder.com/acm/contest/90188/J
void solve() {         G签到..
    int n, seed;
    cin >> n >> seed;
    srand(seed);
    for (int i = 1; i <= n; i++){
        for (int j = i + 1; j <= n; j++){
            read()?cntB[i]++,cntB[j]++:(cntW[i]++,cntW[j]++); //三目运算后面的逗号要加括号!!
        }
    }
    if(n<3) cout<<"0";
    else{
        int ans=n*(n-1)*(n-2)/(3*2*1);
        int cntt=0;
        for(int i=1;i<=n;i++) cntt+=cntW[i]*cntB[i];
        cout<<ans-cntt/2;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值