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
路漫漫兮
原文地址:https://www.cnblogs.com/lonely-wind-/p/12358878.html