• 2019牛客暑期多校训练营(四)


    声明:本文章题目来源均为牛客网

    链接:https://ac.nowcoder.com/acm/contest/884#question

    A-meeting

    题目类型:DFS、BFS

    题目链接:https://ac.nowcoder.com/acm/contest/884/A

    题目大意

    给n个城市,城市与城市之间一共有n-1条边相连(就是一颗n个结点的树),然后有k个在不同城市的人(取树上的k个结点),他们决定聚在某一个城市,每走一条路所需要的时间为1,求这k个人聚到一起需要的最短时间。(n<105

    题目理解

    首先距离最长的两个点肯定是最后聚在一起的,而且相聚点在这条距离最长的路中间。

    求这条最长路可以先由任意一点进行DFS或BFS找出一条伪最长路,然后再由这条伪最长路的另一个端点再进行一次DFS或者BFS找到所需要的最长路,也就是找树的直径。

    证明:如下图

    图中AO<BO,CO<DO,BO<DO

    AO+DO<DO+OB

    设A点为选取的任意一点,进行DFS遍历,得到AD为伪最长路,那么再由B点DFS则BD为最长路(树的直径)。

    于是此题可用两次DFS解决,最后输出(最长路+1)/2(较长的一半为最短时间)。

    AC代码

    #include<iostream>
    #include<vector>
    using namespace std;
    vector<int> v[100010];
    int loc[100010],ans=0,res;
    void dfs(int now,int p,int len){
        if(loc[now]&&len>ans){
            ans=len;
            res=now;
        }
        int y=v[now].size();
        for(int i=0;i<y;i++){
            int next=v[now][i];
            if(next==p) continue;
            dfs(next,now,len+1);
        }
    }
    int main(){
        int n,k;
        cin >> n >> k;
        for(int i=0;i<n-1;i++){
            int v1,v2;
            cin >> v1 >> v2;
            v[v1].push_back(v2);
            v[v2].push_back(v1);    
        }
        while(k--){
            int x;
            cin >> x;
            loc[x]=1;
        }
        dfs(1,1,0);
        dfs(res,res,0);
        cout << (ans+1)/2;
        return 0;
    }

    后记

    题目考察树的直径,平时做树的题目较少,而且比赛的时候没有仔细读题与思考,导致签到题都没做。

    B-xor

    爆肝中~

    C-sequence

    题目类型:分治、线段树

    题目链接:https://ac.nowcoder.com/acm/contest/884/C

    题目大意

    有两个有n个元素的数组a和数组b,求一个[ l,r ]的区间,使得a数组元素在此区间的最小值乘上b数组在此区间的元素之和所得的数最大。

    (n < 3e6,-1e< ( ai,bi ) < 1e6)(1 <= l <= r <= n)

    题目理解

    对于每一个元素a[ i ],找到它左边第一个比它小的元素a[ l ],找到右边第一个比它小的元素a[ r ],那么区间[l+1, r-1]的min就为a[ i ]。

    可以用单调栈来求每个元素的区间。

    将a[ i ]看作min求答案,设sum[ i ]为b数组前缀和:

    • 如果a[ i ] > 0,则要最大化sum[l , r],也就是找到区间[i,r-1]的最大的sum,找到区间[l,i-1]的最小的sum,二者相减乘上a[ i ],找最大值即可;
    • 如果a[ i ]<0,则要最小化sum[l , r],也就是找到区间[i,r-1]的最小的sum,找到区间[l,i-1]的最大的sum,二者相减乘上a[ i ],找最大值即可。

    查询区间最值用st表和线段树都可以。

    AC代码

    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long LL;
     
    const int N = 4000005;
    int n;
    LL a[N], b[N], L[N], R[N];
    LL mx[N<<2], mn[N<<2], sum[N];
     
    void calc(){
        stack<int> st;
        a[0] = a[n + 1] = -INF;
        st.push(0);
        for(int i = 1; i <= n; i ++){
            while(!st.empty() && a[st.top()] >= a[i]) st.pop();
            L[i] = st.top();
            st.push(i);
        }
        while(!st.empty()) st.pop();
        st.push(n + 1);
        for(int i = n; i >= 1; i --){
            while(!st.empty() && a[st.top()] >= a[i]) st.pop();
            R[i] = st.top();
            st.push(i);
        }
    }
     
    void push_up(int rt){
        mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
        mn[rt] = min(mn[rt << 1], mn[rt << 1 | 1]);
    }
     
    void buildTree(int rt, int l, int r){
        if(l == r){
            mx[rt] = mn[rt] = sum[l];
            return;
        }
        int mid = (l + r) >> 1;
        buildTree(rt << 1, l, mid);
        buildTree(rt << 1 | 1, mid + 1, r);
        push_up(rt);
    }
     
    LL queryMax(int rt, int l, int r, int ql, int qr){
        if(l == ql && r == qr){
            return mx[rt];
        }
        int mid = (l + r) >> 1;
        if(qr <= mid) return queryMax(rt << 1, l, mid, ql, qr);
        else if(ql > mid) return queryMax(rt << 1 | 1, mid + 1, r, ql, qr);
        else return max(queryMax(rt << 1, l, mid, ql, mid), queryMax(rt << 1 | 1, mid + 1, r, mid + 1, qr));
    }
     
    LL queryMin(int rt, int l, int r, int ql, int qr){
        if(l == ql && r == qr){
            return mn[rt];
        }
        int mid = (l + r) >> 1;
        if(qr <= mid) return queryMin(rt << 1, l, mid, ql, qr);
        else if(ql > mid) return queryMin(rt << 1 | 1, mid + 1, r, ql, qr);
        else return min(queryMin(rt << 1, l, mid, ql, mid), queryMin(rt << 1 | 1, mid + 1, r, mid + 1, qr));
    }
     
    int main(){
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        for (int i = 1; i <= n; i++) {
            scanf("%lld", &b[i]);
            sum[i] = sum[i-1]+b[i];
        }
        calc();
        buildTree(1, 0, n);
        LL ans = 0;
        for(int i = 1; i <= n; i ++){
            int pl = L[i], pr = i - 1;
            int sl = i, sr = R[i] - 1;
            if(a[i] < 0){
                ans = max(ans, 1LL * a[i] * (queryMin(1, 0, n, sl, sr) - queryMax(1, 0, n, pl, pr)));
            }
            else if(a[i] > 0){
                ans = max(ans, 1LL * a[i] * (queryMax(1, 0, n, sl, sr) - queryMin(1, 0, n, pl, pr)));
            }
        }
        printf("%lld
    ", ans);
        return 0;
    }

    后记

    4场比赛都用到了单调栈,多做点类似的题这题也能很容易想到办法

    D-triples I

    题目类型:按位或运算,思维

    题目链接:https://ac.nowcoder.com/acm/contest/884/D

    题目大意

    输入T个数,每个数都能由3的倍数按位或得来,对于输入的数a,输出任意一组3的倍数,这些数的按位或的结果等于a。(1 < T < 1e5,1 < a < 1e18

    题目理解

    一个简单的数字构造题。对于一个数a,a模上3的结果只可能为0、1、2。

    如果为0,则输出a即可;

    如果不为0,则将其转化为二进制。比如样例中的7,将7写成二进制,为111,其二进制每一位代表的数字模3为121(4%3=1,2%3=2,1%3=1),

    得规律:如果二进制位1所在位置为奇数位,则模3为1,偶数位则为2。

    • 如果a%3=1。如果a的二进制有两个及以上的奇数位的1,假设b为一个奇数位的1代表的数字,再假设c为另一个奇数位的1代表的数字,按照上面所讲的规律,a-b和a-c都为3的倍数,根据按位或的性质(只要为1则为1),(a-b) | (a-c) = a。如果二进制只有一个奇数位的1,设b为这个1代表的数字,c为任意一个偶数位1所代表的数字,a-b和b+c都为3的倍数,且(a-b) | (b+c) = a。如果二进制没有奇数位的1,设b,c,d为任意3个不同的偶数位所代表的数字,a-b-c和a+b+c为3的倍数,且(a-b-c) | (b+c+d)
    • 如果a%3=2。原理和上面一样,只需要将每一步的奇偶性反过来就行了,这里就不说了。

    总结:就用原来的数字a构造3的倍数,去掉模3的有余数的数就行了,而且联想一下可以发现,只需要最多两个3的倍数就可以按位或成数字a。

    AC代码

    #include<iostream>
    #include<vector>
    #define sc(x) scanf("%d",&x);
    #define scll(x) scanf("%lld",&x);
    using namespace std;
    typedef long long ll;
    int main(){
        int t;sc(t);
        while(t--){
            ll n;scll(n);
            vector<int> one,two;
            for(int i=0;i<62;i++)
                if((n>>i)&1){//用vector容器分别保存二进制位奇偶性不同的1的位置 
                    if(i&1) two.push_back(i); //保存偶数位1 
                    else one.push_back(i);//保存奇数位1 
                } 
            if(n%3==0) printf("1 %lld
    ",n);
            else if(n%3==1){
                if(one.size()>=2) 
                    printf("2 %lld %lld
    ",n-(1LL<<one[0]),n-(1LL<<one[1]));
                else if(one.size()==1)  
                    printf("2 %lld %lld
    ",n-(1LL<<one[0]),(1LL<<two[0])+(1LL<<one[0]));
                else 
                    printf("2 %lld %lld
    ",n-(1LL<<two[0])-(1LL<<two[1]),(1LL<<two[0])+(1LL<<two[1])+(1LL<<two[2]));
            }else if(n%3==2){
                if(two.size()>=2) 
                    printf("2 %lld %lld
    ",n-(1LL<<two[0]),n-(1LL<<two[1]));
                else if(two.size()==1) 
                    printf("2 %lld %lld
    ",n-(1LL<<two[0]),(1LL<<two[0])+(1LL<<one[0]));
                else 
                    printf("2 %lld %lld
    ",n-(1LL<<one[0])-(1LL<<one[1]),(1LL<<one[0])+(1LL<<one[1])+(1LL<<one[2]));
            }
        }
        return 0;
    }

    后记

    构造题,找规律,也要看自己对位运算的理解以及运用。

    j-free

    题目类型:分层图最短路

    题目链接:https://ac.nowcoder.com/acm/contest/884/J

    题目大意

    有一个非直接连接的图,n个结点,m条边,每条边有花费l,要求在可以让k条边权值为0的情况下,求s点到t点的最小花费。

    (1 <= n,m <= 1e3,1 <= s,t <= n,1 <= l <= 1e6

    题目理解

    一道分层图最短路模板题,把保存花费的d数组多开一维记录k次机会的信息。d[ i ][ j ]达到 i 点用了 j 次机会的最小花费。

    更新信息的时候先更新同一层的(用了相同次机会)最小花费,然后再更新下一层的(再用一次机会)最小花费。

    dijkstra最短路就行了(用优先队列维护)。

    AC代码

    #include<iostream>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int inf=0x3f3f3f3f;
    int n,m,s,t,k,d[1010][1010];
    typedef pair<int,int> P;
    struct node{
        int to,cost;
        node(int a,int b){
            to=a;
            cost=b;
        }
    };
    vector<node> v[1010];
    void dijkstra(int s){ 
        memset(d,inf,sizeof(d));
        d[s][0]=0;
        queue<P> que;
        que.push(P(s,0));
        while(!que.empty()){
            P now=que.front();
            que.pop();
            int u=now.first,t=now.second;
            for(int i=0;i<v[u].size();i++){
                int x=v[u][i].to;
                int pay=v[u][i].cost;
                if(d[x][t]>d[u][t]+pay) //更新同层的最小花费 
                    d[x][t]=d[u][t]+pay,que.push(P(x,t));
                if(t<k&&d[x][t+1]>d[u][t]) //更新下一层的最小花费 
                    d[x][t+1]=d[u][t],que.push(P(x,t+1));
            }
        }
    }
    int main(){
        cin >> n >> m >> s >> t >> k;
        while(m--){
            int a,b,c;
            cin >> a >> b >> c;
            v[a].push_back(node(b,c));
            v[b].push_back(node(a,c));
        } 
        dijkstra(s);
        int ans=inf;
        for(int i=0;i<=k;i++)
            ans=min(ans,d[t][i]);
        printf("%d",ans);
        return 0;
    } 

    后记

    要不是出了这题我还不知道分层图这个东西,还是练习太少的锅。

  • 相关阅读:
    MPlayer源代码分析
    洛谷 P1025 数的划分
    洛谷 P1209 [USACO1.3]修理牛棚 Barn Repair
    洛谷 P1744 采购特价商品
    洛谷 P1443 马的遍历
    洛谷 P1294 高手去散步
    洛谷 P2104 二进制
    洛谷 P1088 火星人
    洛谷 P1062 数列
    洛谷 P2005 A/B Problem II
  • 原文地址:https://www.cnblogs.com/lastonepersonwhohavebitenbycompanies/p/11259203.html
Copyright © 2020-2023  润新知