2019牛客暑假多校训练营(第四场)

A:https://ac.nowcoder.com/acm/contest/884/A

给出n个点,和相连的边,k个人,让k个人在最短时间内同一地点相遇。

一句话题解:考虑距离最远的两个关键点,设它们的距离为d,d/2上取整即为答案。

• 必要性:这两个人要碰面,必然要走至少d/2步。

• 充分性:我们取两人路径中和一头距离为d/2上取整的一个点,让所有人在这相聚。如

果有一个人在d/2时间内到不了,那么它和路径两头中与它远的那一头的距离大于d,与

最远的假设矛盾。

• 找到这样最远的一对点类似找树的直径。可以直接dp,也可以采用两遍bfs:从任意一个关

键点开始,找到离它最远的关键点x,再从x开始bfs,找到的新的最远点和x形成的就是直径。

两次bfs找到直径,向上取整除以二

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int n,k,a,b;
vector<int> v[maxn];
queue<int> q;
bool vis[maxn];
int d[maxn],mx,l,pos[maxn];
void bfs(int s)
{
    mx=0;
    memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d));
    q.push(s);
    vis[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=0;i<v[x].size();i++){
            int y=v[x][i];
            if(vis[y]) continue;
            d[y]=d[x]+1;
            if(pos[y]&&d[y]>mx){
                mx=d[y];l=y;
            }
            q.push(y);
            vis[y]=1;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    memset(pos,0,sizeof(pos));
    for(int i=0;i<n-1;i++){
        scanf("%d%d",&a,&b);
        v[a].push_back(b);
        v[b].push_back(a);
    }
    for(int i=0;i<k;i++){
        scanf("%d",&a);
        pos[a]=1;
    }
    bfs(a);
    bfs(l);
    printf("%d\n",(mx+1)/2);
    return 0;
}

C:https://ac.nowcoder.com/acm/contest/884/C

很经典的题  max​{min(al…r​)×sum(bl…r​)} 。

找到a数列的每个值是最小值的区间 l,r,用单调栈。

维护b数列前缀和的最大值和最小值,sum(b[l..r])=s[r]-s[l-1]。

分类讨论a[i]为正还是负,查询  i..r-1最大的s  和  l..i-1最小的s,相减即可


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=3e6+5;
const ll inf=1e18;
ll n,a[maxn],b[maxn],s[maxn],l[maxn],r[maxn];
stack<ll> sl,sr;
struct tree
{
    struct node
    {
        ll l,r,mi,mx;
    }t[maxn*4+5];
    ll x,y;
    void build(ll l,ll r,ll k)
    {
        t[k].l=l;
        t[k].r=r;
        if(l==r)
        {
            t[k].mx=s[l];
            t[k].mi=s[l];
            return ;
        }
        ll mid=(l+r)/2;
        build(l,mid,k*2);
        build(mid+1,r,k*2+1);
        t[k].mi=min(t[k*2].mi,t[k*2+1].mi);
        t[k].mx=max(t[k*2].mx,t[k*2+1].mx);
    }
    ll ask1(ll k)
    {
         if(t[k].l>=x&&t[k].r<=y){
            return t[k].mi;
         }
         ll mii=inf;
         ll mid=(t[k].l+t[k].r)/2;
         if(x<=mid) mii=min(mii,ask1(k*2));
         if(y>mid) mii=min(mii,ask1(k*2+1));
         return mii;
    }
    ll ask2(ll k)
    {

        if(t[k].l>=x&&t[k].r<=y){
            return t[k].mx;
         }
         ll mxx=-inf;
         ll mid=(t[k].l+t[k].r)/2;
         if(x<=mid) mxx=max(mxx,ask2(k*2));
         if(y>mid) mxx=max(mxx,ask2(k*2+1));
         return mxx;
    }
}T;
int main()
{
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    s[0]=0;
    for(ll i=1;i<=n;i++){
        scanf("%lld",&b[i]);
        s[i]=s[i-1]+b[i];
    }
    T.build(1,n,1);
    for(ll i=1;i<=n;i++){
        while(!sl.empty()&&a[sl.top()]>=a[i]) sl.pop();
        if(sl.empty()) l[i]=0;
        else l[i]=sl.top();
        sl.push(i);
    }
    for(ll i=n;i>0;i--){
        while(!sr.empty()&&a[sr.top()]>=a[i]) sr.pop();
        if(sr.empty()) r[i]=n+1;
        else r[i]=sr.top();
        sr.push(i);
    }
    ll mx=-inf,vl,vr;
    for(ll i=1;i<=n;i++){
        if(a[i]>=0){
             mx=max(mx,a[i]*(s[r[i]-1]-s[l[i]]));
        }
        else{
            T.x=l[i],T.y=i-1;
            vl=T.ask2(1);
            T.x=i,T.y=r[i]-1;
            vr=T.ask1(1);
            mx=max(mx,a[i]*(vr-vl));
        }
    }
    printf("%lld\n",mx);
    return 0;
}


D:https://ac.nowcoder.com/acm/contest/884/D

给出一个数,用最少数量的3的倍数配凑出该数,运算为或。

• 分类讨论。

• 一个二进制位mod 3只可能是1或者2。

• 如果a是3的倍数,那么我们直接取{a}即可。

• 否则如果a的二进制位只有一位或两位,我们根本取不出0以外的三的倍数,所以无解。

• 接下来考虑a至少有三位的情况。

• 若a mod 3=1:

• 如果a中的二进制位有至少两个mod 3=1的,设它们为p和q,我们取{a-p,a-q}即可。

• 如果a中的二进制位有恰好一个mod 3=1的,那么设mod 3=1的这个位为p,mod 3=2

的某个位为q,我们取{a-p,p+q}即可。

• 如果a中的二进制位没有mod 3=1的,那么假设有三个mod 3=2的位p,q,r,我们取{a-

p-q,p+q+r}即可。

• 若a mod 3=2只需把上面的讨论中1与2互换即可,是完全对称的。

把3的倍数的数找到,其他的罗列出来,看区别,就可以想到消掉某一位1,2,4,8等等。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<ll> v[3];
int main()
{
    ll t,n;
    scanf("%lld",&t);
    while(t--){
        scanf("%lld",&n);
        for(ll i=0;i<3;i++) v[i].clear();
        if(n%3==0){
            printf("1 %lld\n",n);
            continue;
        }
        ll m=n,vv=1;
        //记录哪个位置有1
        while(m){
            if(m%2){
                v[vv%3].push_back(vv);
            }
            vv*=2;
            m/=2;
        }
        if(n%3==1){
            if(v[1].size()>=2){
                printf("2 %lld %lld\n",n-v[1][0],n-v[1][1]);
            }
            else if(v[1].size()==1&&v[2].size()){
                printf("2 %lld %lld\n",n-v[1][0],v[1][0]+v[2][0]);
            }
            else if(v[1].size()==0&&v[2].size()>=3){
                printf("2 %lld %lld\n",n-v[2][0]-v[2][1],v[2][0]+v[2][1]+v[2][2]);
            }
        }
        else{
            if(v[2].size()>=2){
                printf("2 %lld %lld\n",n-v[2][0],n-v[2][1]);
            }
            else if(v[2].size()==1&&v[1].size()){
                printf("2 %lld %lld\n",n-v[2][0],v[1][0]+v[2][0]);
            }
            else if(v[2].size()==0&&v[1].size()>=3){
                printf("2 %lld %lld\n",n-v[1][0]-v[1][1],v[1][0]+v[1][1]+v[1][2]);
            }
        }
    }
    return 0;
}

J:https://ac.nowcoder.com/acm/contest/884/J

给出n个点,m条边,s起点,t终点,k可忽略掉的边权。求最小花费。分层图!?也不用,直接优先队列加bfs

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,s,t,k;
ll a,b,c,ans;
struct edge
{
    ll dian,cost;
};
struct node
{
    ll dian,cnt,sum,pre;
    bool friend operator < (node x,node y){
        if(x.sum!=y.sum)
            return x.sum>y.sum;
        else
            return x.cnt<y.cnt;
    }
};
vector<edge> v[1005];
priority_queue<node> q;
void bfs(ll s)
{
    q.push({s,k,0,0});
    while(!q.empty()){
        node x=q.top();
        q.pop();
        ll aa=x.dian,bb=x.cnt,cc=x.sum,dd=x.pre;
        //printf("%lld %lld\n",bb,cc);
        if(aa==t){
            ans=cc;
            return ;
        }
        for(ll i=0;i<v[aa].size();i++){
            ll x=v[aa][i].dian,y=v[aa][i].cost;
            if(x==aa||x==dd) continue;
            if(bb){
                q.push({x,bb-1,cc,aa});
            }
            q.push({x,bb,cc+y,aa});
        }
     }
}
int main()
{
    scanf("%lld%lld%lld%lld%lld",&n,&m,&s,&t,&k);
    for(ll i=0;i<m;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        v[a].push_back({b,c});
        v[b].push_back({a,c});
    }
    bfs(s);
    printf("%lld\n",ans);
    return 0;
}

K:https://ac.nowcoder.com/acm/contest/884/K

给出一串字符串,求子串是300的倍数的数量,包括0.

300的倍数满足既是3的倍数,也是100的倍数。

用前缀和处理s[r]-s[l-1]=0,且s[r]、s[r+1]为0,则计算。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char s[100005];
ll sum=0,cnt[5];
int main()
{
    ll ans=0;
    scanf("%s",s+1);
    memset(cnt,0,sizeof(cnt));
    ll len=strlen(s+1);
    cnt[0]=1;//一定要注意最开始,什么都没有前缀和为0,有一个0!
    for(ll i=1;i<=len;i++){
        sum+=s[i]-'0';
        sum%=3;
        if(s[i]=='0') ans++;
        if(i+1<=len&&s[i]=='0'&&s[i+1]=='0'){
            ans+=cnt[sum];
        }
        cnt[sum]++;
    }
    printf("%lld\n",ans);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值