• CSUST--2.28排位周赛第二场 (全解)


    emmm,你们的学长简直是牲口。。。本来你们都要爆零的, 后面zhrt觉得太不友好了,于是加了两题签到题。

    为了便于大家理解,有些题我会尝试一些乱七八糟的解法(可能会A,可能不会),可能会比较眼花缭乱,也不需要大家都掌握,不过某个出题人解法就一定要会了。

    题目链接:http://acm.csust.edu.cn/contest/70

    比赛过后交不了题,可在problem中搜索

    题目说明: 

    不许“冒泡”(签到题-冒泡排序)

    小K的漂流计划(签到题-贪心)

    RWBY(最短路)

    辛勤的蚂蚁(LCA)

    神奇的中位数(优先队列)

    不许“冒泡”

    题目大意:给你一个序列,让你通过相邻交换使得该序列有序,问最少需要交换多少次并乘以序列长度

    Sample Input  

    3
    5
    166 167 168 169 170
    5
    165 166 169 167 168
    3
    166 167 166

    Sample Output 

    0
    10
    3

    emmm,数据非常小,直接暴力搞就可以了,只不过注意只要序列有序就行了,所以我们要按照升序跑一次冒泡,按照降序跑一次冒泡然后取最小值就OK了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e3+10;
    
    int a[mac],b[mac];
    
    int main()
    {
        int t,n;
        scanf ("%d",&t);
        while (t--){
            scanf ("%d",&n);
            for (int i=1; i<=n; i++){
                scanf ("%d",&a[i]);
                b[i]=a[i];
            }
            int ans=0,ans1=0;
            for (int i=1; i<=n; i++)
                for (int j=i+1; j<=n; j++){
                    if (a[j]>a[j-1]) ans++,swap(a[j],a[j-1]);
                    if (b[j]<b[j-1]) ans1++,swap(b[j],b[j-1]);
                }
            printf("%d
    ",min(ans,ans1)*n);
        }
        return 0;
    }
    View Code

     

    小K的漂流计划

     题目大意:给你n个数字,让你删去最少的个数使得数列的极差小于等于k,问最少删去多少个数。

    Sample Input 

    5 0
    5 5 5 5 5
    

    Sample Output 

    0
    emmm,n只有1000,算是一道非常签到的题目了,我们先介绍$n^{2}$的做法(出题人的做法),要删去最少的数,那么也就是要保留最多的数,我们排个序,然后从头扫到尾,再从尾扫到头。再倒过来扫的过程中如果碰到a[j]-a[i]<=k的情况我们直接break,然后保留最大值$j-i+1$。
    以下是AC代码:
    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e3+10;
    
    int a[mac];
    
    int main()
    {
        int n,k;
        scanf ("%d%d",&n,&k);
        for (int i=1; i<=n; i++)
            scanf ("%d",&a[i]);
        sort(a+1,a+1+n);
        int ans=1;
        for (int i=1; i<=n; i++)
            for (int j=n; j>=i; j--)
                if (a[j]-a[i]<=k) {
                    ans=max(ans,j-i+1);
                    break;
                }
        printf("%d
    ",n-ans );
        return 0;
    }
    View Code

    接下来就是$logn$的解法,实际上除了排序是$logn$,真正算的时候只有$O(n)$,我们用$head$和$tail$两个指针,如果$a[tail]-a[head]<=k$的话$tail++$,否则的话就$head++$,然后每次保存最大值就OK了。

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e3+10;
    
    int a[mac];
    
    int main()
    {
        int n,k;
        scanf ("%d%d",&n,&k);
        for (int i=1; i<=n; i++)
            scanf ("%d",&a[i]);
        sort(a+1,a+1+n);
        int head=1,tail=1;
        int ans=1;
        while (tail<=n){
            while (a[tail]-a[head]<=k && tail<=n) tail++;
            if (a[tail]-a[head]<=k && tail<=n) ans=max(ans,tail-head+1);
            else ans=max(ans,tail-head);
            head++;
        }
        printf("%d
    ",n-ans);
        return 0;
    }
    View Code

     

    RWBY

    题目大意:一个图,你要从1走到n,你有k颗尘晶,在整个过程中你的尘晶不能低于0,每个点都有危险值,从危险值a到危险值b的地方(a>b)获得a-b颗尘晶,否则失去b-a颗尘晶。你也可以花费$x^{2}$个金币将某地的危险值下降x。问你最终的最小花费是多少,即金币数+路径长度。

    Sample Input 

    4 5 1
    1 2 3 4
    1 2 1
    1 3 1
    1 4 100
    2 4 1
    3 4 1

    Sample Output

    6

    emmm,应该很容易看出是用最短路求解,但比较头痛的是应对各种条件,我们可以先把尘晶这个变量给剔除掉,你有k颗尘晶,那么你先可以走到刚好满足$a[1]+k$的地方(通过氪金你一定能走到),然后通过金币将其每个点下降至$a[1]+k$那么你后面的路就不需要花费尘晶了,而这样想来花费也是最少的。那么我们就将每个点需要用到的金币同路径长度相加起来,这样就是一条路径的花费了,于是我们就可以愉快地跑一跑最短路就完事了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=2e5+10;
    typedef long long ll;
    #define debug printf ("++ ++")
    
    struct node{
        int to,next;
        ll w;
    }eg[mac<<1];
    int head[mac],val[mac],num=0;
    ll dis[mac],cost[mac];
    bool vis[mac];
    struct pt{
        int id;
        ll s;
        bool operator<(const pt &a)const{
            return s>a.s;
        }
    };
    
    void add(int u,int v,ll w)
    {
        eg[num]=node{v,head[u],w};
        head[u]=num++;
    }
    
    void dij(int st,int n)
    {
        priority_queue<pt>q;
        memset(dis,0x3f,sizeof dis);
        q.push(pt{st,0});
        dis[st]=0;
        while (!q.empty()){
            pt now=q.top();
            q.pop();
            int u=now.id;
            if (vis[u]) continue;
            vis[u]=true;
            for (int i=head[u]; i!=-1; i=eg[i].next){
                int v=eg[i].to;
                if (vis[v]) continue;
                if (dis[u]+eg[i].w<dis[v]){
                    dis[v]=dis[u]+eg[i].w;
                    q.push(pt{v,dis[v]});
                }
            }
        }
    }
    
    int main()
    {
        int n,m,k;
        memset(head,-1,sizeof head);
        scanf ("%d%d%d",&n,&m,&k);
        for (int i=1; i<=n; i++){
            scanf("%lld",&val[i]);
            if (val[i]>val[1]+k){
                cost[i]=val[i]-(val[1]+k);
                cost[i]=cost[i]*cost[i];
            }
        }
        for (int i=1; i<=m; i++){
            int u,v,w;
            scanf ("%d%d%d",&u,&v,&w);
            add(u,v,1LL*w+cost[v]);add(v,u,1LL*w+cost[u]);
        }
        dij(1,n);
        printf("%lld
    ",dis[n]);
        return 0;
    }
    View Code

    辛勤的蚂蚁

    题目大意:给你一棵树n个点,m只蚂蚁每只要从$s_{i}->t_{i}$,问第k秒时,每个点的蚂蚁数量,到达终点时不再移动。

    Sample Input 

    5 2 3
    1 2
    1 3
    2 4
    4 5
    
    2 5
    3 5

    Sample Output 

    0 0 0 1 1

     emmm,首先看到题目的时候确实有点蒙的,不过一会儿之后就应该能缓解了,一颗树,那么有关的路径算法我们不难想到LCA,而也正是只有LCA才能快速地求出大量s到t的距离,如果时间k大于等于lca(s,t),那没什么好说的了,直接ans[t]++。比较麻烦的是小于的时候,这个时候我们需要判断s的k步能走到哪里,很显然,倍增lca可以通过一步步往上爬祖先节点来求出时间k所达到的点。关键是理解倍增lca是怎么求的,然后这题就很简单了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    #define debug printf("++ ++
    ")
    const int mac=3e5+10;
    
    struct node { 
        int to, next, cost; 
    }eg[mac << 1];
    
    int n, m, tot = 0;
    int lg[mac], dis[mac], head[mac], depth[mac], father[mac][25];
    int ans[mac];
    
    void add(int u, int v, int w) {
        eg[tot] = node{ v, head[u], w };
        head[u] = tot++;
    }
    
    void dfs(int now, int fa) {
        depth[now] = depth[fa] + 1; father[now][0] = fa;
        for (int i = 1; i <= lg[depth[now]] + 1; ++i)
            father[now][i] = father[father[now][i - 1]][i - 1];
        for (int i = head[now]; ~i; i = eg[i].next)
            if (eg[i].to != fa) {
                dis[eg[i].to] = dis[now] + eg[i].cost;
                dfs(eg[i].to, now);
            }
    }
    
    int lca(int u, int v) {
        if (depth[u] < depth[v]) swap(u, v);
        while (depth[u] != depth[v]) u = father[u][lg[depth[u] - depth[v]]];
        if (u == v) return u;
        for (int i = lg[depth[u]]; i >= 0; --i) {
            if (father[u][i] != father[v][i]) {
                u = father[u][i];
                v = father[v][i];
            }
        }
        return father[u][0];
    }
    
    int lca2(int u, int v,int len) {
        while (len){
            int lsu=u;
            u = father[u][lg[len]];
            len-=dis[lsu]-dis[u];
        }
        return u;
    }
    
    int main()
    {
        lg[0] = -1;
        for (int i = 1; i < mac-5; ++i) lg[i] = lg[i >> 1] + 1, head[i] = -1;
        memset(father, 0, sizeof father);
        memset(depth, 0, sizeof depth);
        memset(dis, 0, sizeof dis);
        int k;
        scanf("%d%d%d", &n, &m, &k);
        for (int i = 1; i <= n - 1; ++i) {
            int u,v;
            scanf("%d%d", &u, &v);
            add(u,v,1);add(v,u,1);
        }
        dfs(1, 0);
        while (m--) {
            int s,t;
            scanf("%d%d", &s, &t);
            int id=lca(s,t);
            if (dis[s]-dis[id]+(dis[t]-dis[id])<=k) ans[t]++;
            else{
                if (dis[s]-dis[id]==k) ans[id]++;
                else if (dis[s]-dis[id]>k){//在左链
                    id=lca2(s,id,k);
                    ans[id]++;
                }
                else {
                    id=lca2(t,id,(dis[t]-dis[id])-(k-(dis[s]-dis[id])));
                    //右链需要往上爬的长度为右链长-(k-左链的长度)
                    ans[id]++;
                }
            }
        }
        for (int i=1; i<=n; i++)
            printf("%d%c",ans[i],i==n?'
    ':' ');
        return 0;
    }
    View Code

     

    神奇的中位数

    题目大意:给你n个数,每给你一个数后让你求当前检查序列的中位数,假定初始的ans=0,每次给完一个数之后,你将该数异或上一次的答案放入检查序列,求给出每个数之后的当前检查序列的中位数

    Sample Input 

    5
    1 2 3 0 1

    Sample Output 

    1
    1
    2
    2
    2

    emmm,题目没看懂?没关系,我们写一份最暴力的代码来一发就知道是怎么回事了:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e6+10;
    int a[mac];
    
    int main()
    {
        int n;
        scanf ("%d",&n);
        int cnt=0;
        int lastans=0;
        for (int i=1; i<=n; i++){
            scanf("%d",&a[i]);
            a[i]^=lastans;
            sort(a+1,a+1+i);
            lastans=a[(i+1)/2];
            printf("%d
    ",lastans);
        }
        return 0;
    }

    。。。先来一波第二慢的二分做法,我们可以利用vector的insert函数加上二分查找来做。。。。只不过insert函数复杂度貌似挺高的,我试了一下,不行。。。不过比上面的做法过的测试点更多是肯定的

    下面是T了的二分+vector做法

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e6+10;
    vector<int>g;
    
    int main()
    {
        int n;
        scanf ("%d",&n);
        int ans=0;
        g.push_back(-1);
        for (int i=1; i<=n; i++){
            int x;
            scanf("%d",&x);
            int now=ans^x;
            //printf("now,ans=%d %d
    ",now,ans );
            if (i==1) {
                printf("%d
    ",x);
                ans=x;
                g.push_back(x);
                continue;
            }
            int pos=lower_bound(g.begin(),g.end(),now)-g.begin();
           /* if (pos<g.sise()-1) 
                if (g[pos]>now) pos--;*/
            g.insert(g.begin()+pos,now);
            //for (auto u:g) printf("%d ",u);printf("
    ");
            ans=g[(i+1)/2];
            printf("%d
    ",ans);
        }
        return 0;
    }
    View Code

    然后考虑到有序序列,我们可以想的multiset,这个玩意儿很nb,用multiset写一发,只T了四个点,而且超过的时间也不多好像。。。。

    下面是T了的multiset做法

    #include <bits/stdc++.h>
    using namespace std;
    
    multiset<int> st;
    multiset<int>::iterator it;
    
    void in(int &x)
    {
        int f=0;
        char ch=getchar();
        while (ch>'9' || ch<'0') ch=getchar();
        while (ch>='0' && ch<='9') f=(f<<3)+(f<<1)+ch-'0',ch=getchar();
        x=f;
    }
    
    void out(int x)
    {
        if (x>=10)
            out(x/10);
        putchar(x%10+'0');
    }
    
    int main() {
        int n;
        int x, lastans = 0;
        in(n);
        for (int i = 1; i <= n; ++i) {
            in(x);
            x = x ^ lastans;
            st.insert(x);
            if (i == 1) it = st.begin();
            else {
                if (i & 1) {
                    if (lastans <= x && it != st.end()) it++;
                } 
                else {
                    if (lastans > x && it != st.begin()) it--;
                }
            }
            lastans = *it;
            out(lastans);
            putchar('
    ');
        }
        return 0;
    }
    View Code

    考虑到中位数算是序列第k大,那么我们可以试一下权值线段树,由于数值比较大,又是强制在线的,我们不能离散化,只能动态开点来一波,不过很遗憾的是,虽然跑得比multiset快一点,但还是T了四个点。。。大概在1100ms左右我的,只不过zhrt经过不断努力优化和利用测评姬的抖动终于挤进了1000ms,达到了980ms+。。。。

    以下是T了的权值线段树动态开点做法(各位也可以优化一下A了)

    #include <bits/stdc++.h>
    using namespace std;
    
    #define lson l,mid,ll[rt]
    #define rson mid+1,r,rr[rt]
    #define ls ll[rt]
    #define rs rr[rt]
    const int inf=2e9+10;
    const int mac=1e6+10;
    
    int ll[mac*32],rr[mac*32],sum[mac*32];
    int sz=0,root=0;
    
    void in(int &x)
    {
        int f=0;
        char ch=getchar();
        while (ch>'9' || ch<'0') ch=getchar();
        while (ch>='0' && ch<='9') f=(f<<3)+(f<<1)+ch-'0',ch=getchar();
        x=f;
    }
    
    void out(int x)
    {
        if (x>=10)
            out(x/10);
        putchar(x%10+'0');
    }
    
    void update(int l,int r,int &rt,int pos)
    {
        if (!rt) rt=++sz;
        sum[rt]++;
        if (l==r) return;
        int mid=(1LL*l+r)>>1;//(特别注意,会爆int,蒟蒻在这里RE,MLE了十几发)
        if (mid>=pos) update(lson,pos);
        else update(rson,pos);
    }
    
    int query(int l,int r,int rt,int pos)
    {
        if (l==r) return l;
        int mid=(1LL*l+r)>>1;
        if (sum[ls]>=pos)
            return query(lson,pos);
        else return query(rson,pos-sum[ls]);
    }
    
    int main() {
        int n;
        int x, lastans = 0;
        in(n);
        for (int i = 1; i <= n; ++i) {
            in(x);
            x = x ^ lastans;
            if (i==1){
                lastans=x;
                update(0,inf-5,root,x);
            }
            else {
                update(0,inf-5,root,x);
                int pos=(i+1)>>1;
                lastans=query(0,inf-5,root,pos);
            }
            out(lastans);
            putchar('
    ');
        }
        return 0;
    }
    View Code

    接下来就是较为巧妙的优先队列做法(出题人解法)了,既然要找中位数我们只需要将序列均分为两部分存在优先队列q1,q2中,其中q1为小根堆队列(返回最大值),q2为大根堆队列(返回最小值)那么我们就可以巧妙得将序列分为$1-q1,q2-max$,那么中位数就要么是q1.top(),要么是q2.top()了。

    接下来就是维护这两个优先队列了,也不是很难,每得到一个值之后我们先判断是否大于q1,如果是的话我们将其放入q2中,否则就放到q1中,接下来我们再检查q1和q2的长度,分为奇数偶数讨论,偶数就平分,那么答案就是q1.top(),奇数要么q1多一个数,要么q2多一个数,答案就是多的那个队列的top

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e6+10;
    int a[mac];
    
    int main()
    {
        int n;
        scanf ("%d",&n);
        for (int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        priority_queue<int,vector<int>,greater<int> >q2;//返回最小值
        priority_queue<int,vector<int>,less<int> >q1;//返回最大值
        /*1-q1,q1-q2*/
        int ans=a[1];
        printf("%d
    ",a[1]);
        q1.push(a[1]);
        for (int i=2; i<=n; i++){
            int now=a[i]^ans;
            if (now>q1.top()) q2.push(now);
            else q1.push(now);
            if (i&1){
                if (q1.size()>q2.size()+1) {
                    q2.push(q1.top());
                    q1.pop();
                }
                else if (q2.size()>=q1.size()){
                    q1.push(q2.top());
                    q2.pop();
                }
                ans=q1.top();
                printf("%d
    ",q1.top());
            }
            else{
                if (q1.size()>q2.size()){
                    q2.push(q1.top());
                    q1.pop();
                }
                else if (q2.size()>q1.size()){
                    q1.push(q2.top());
                    q2.pop();
                }
                ans=q1.top();
                printf("%d
    ",q1.top());
            }
        }
    
        return 0;
    }
    View Code
    路漫漫兮
  • 相关阅读:
    CoreData和SQLite多线程访问时的线程安全问题
    JSPatch 实现原理详解
    iOS开发工程师必备技能(持续更新)
    iOS音频播放、录音、视频播放、拍照、视频录制
    使用MVVM减少控制器代码实战(减少56%)
    如何结合场景利用block进行回调
    Masonry框架源码深度解析
    Ubantu编译安装FFmpeg
    HTTP Live Streaming网络视频直播调研
    RHEL/CentOS 6的更新源
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/12358878.html
Copyright © 2020-2023  润新知