Codeforces round #481(Div.3) 题解

声明:本人水平有限,欢迎各位大佬来打脸( ̄ε(# ̄)

A. Remove Duplicates

【题意】
n个数,在相同的数中只保留出现最晚的那个,并将剩下的数按原顺序的先后输出。
【思路】
ai1000 a i ≤ 1000 ,所以可以开一1000的数组,记录每个数值的最后一次出现的位置,然后按位置排序输出对应的数值。
【代码】

#include<stdio.h>
#include<algorithm>
using std::sort;
#define N_max 100005
int n;

struct thing {
    int v;
    int id;
    thing():v(0),id(0){}
}loc[1002];
int cmp(thing t1, thing t2) {
    return t1.id < t2.id;
}
int main() {
    scanf("%d", &n);
    int ipt;
    for (int i = 1; i <=n; ++i) {
        scanf("%d", &ipt);
        loc[ipt].id = i;
        loc[ipt].v = ipt;
    }
    sort(loc, loc + 1002,cmp);
    int i; for (i = 0; loc[i].id == 0; ++i);
    printf("%d\n", 1002 - i);
    for (; i < 1002; ++i)
    printf("%d%c", loc[i].v,i==1001?'\n':' ');
}

B. File Name

【题意】
一个字符串,每次处理可以删除一个’x’,求至少几次操作使得字符串中没有”xxx”子串。
【思路】
对于每一个长度≥3的”xx…xx”子串,消除它需要的操作次数是它的长度 2 − 2 ,只要消除了所有的子串就可以结束了。
【代码】

#include<stdio.h>
#include<algorithm>
using std::sort;
#define N_max 100005
int n;

char ipt[102];
int cnt,ans;
int main() {
    scanf("%d", &n);
    cnt = 0; //当前子串含有'x'的个数
    ans = 0;//总操作次数
    int i;
    scanf("%c", &ipt[101]);//多余回车

    for (i = 0; i <n; ++i) {
        scanf("%c", &ipt[i]);
        if (ipt[i] == 'x')cnt++;
        else {
        //"x..x"子串结束,将它消除掉
            ans += (cnt - 2>0?cnt-2:0); 
            cnt = 0;
        }
    }
    //末尾还需要单独检测
    if (ipt[i] == 'x')cnt++;
    else {
        ans += (cnt - 2>0 ? cnt - 2 : 0); 
        cnt = 0;
    }
    printf("%d\n", ans);

}

C. Letters

【题意】
有n个公寓,每个公寓分别有 ai a i 个房间,现在把1~n号公寓的房间统一编号为1~ a1+a2+...+an a 1 + a 2 + . . . + a n 。输入m个编号,求这些编号对应的是第几个公寓的第几个房间。

【思路】
统一编号以后,每个房间所处的区间范围都确定了,分别是 [1,a1] [ 1 , a 1 ] , [a1+1,a1+a2] [ a 1 + 1 , a 1 + a 2 ] , [a1+a2+1,a1+a2+a3] [ a 1 + a 2 + 1 , a 1 + a 2 + a 3 ] , … 只要找到了房间的编号在哪两个区间的起点之间,就确定了它在哪个公寓,而求它是第几个房间只需要减去它所在的区间的起点就行了。

【注意】
公寓数量高达2e5,由于是连续编号,区间的起点是递增的,所以可以使用二分查找属于哪个区间。
编号范围是1e10,需要用long long 来存储。

【代码】

#include<stdio.h>
typedef long long ll;
ll a[200005];
int n, m;
int main() {
    scanf("%d %d", &n,&m);
    a[0] = 0;
    //输入预处理,保存的是区间的起点
    for (int i = 1; i <=n; ++i)
    {
        scanf("%lld", &a[i]);
        a[i] = a[i] + a[i - 1];
    }
    ll let;
    for (int i = 1; i <= m; ++i) {
        scanf("%lld", &let);
        int l = 0,r=n+1,mid;
        //二分查找区间起点
        while (l + 1 < r) {
            mid = ((l + r) >> 1);
            if (a[mid] < let)l = mid;
            else r = mid;
        }
        //查询的结果是它右边最近的区间起点
        //它是第几个房间:编号-前一个区间起点
        printf("%d %lld\n", r, let - a[r-1]);
    }
}

D. Almost Arithmetic Progression

【题意】
输入一个数字序列,对于每一个数只能进行一次[加1/减1/不操作]处理,问能否把序列处理为等差序列。

【分析】
直接判断并不好做,转而分析解空间。

根据等差数列的性质,如果首项、尾项和个数都知道了,那么这个数列就确定了。在这道题中,个数都是n,而由于操作的限制,首项只可能为 {a11,a1,a1+1} { a 1 − 1 , a 1 , a 1 + 1 } ,尾项只可能为 {an1,an,an+1} { a n − 1 , a n , a n + 1 }

那么也就意味着原数列有可能变为的等差数列只有九种,检查原数列能否变成这些等差数列,只需要判断每一位的差是否大于1就行了。

【思路】
分为两步:[1]生成一个等差数列,[2]检查能否处理为这个等差数列,并记录操作次数。检查结束后,选择操作次数最少的那个。

【代码】

#include<stdio.h>
#include<algorithm>
using std::sort;
#define min(a,b) ((a)<(b)?(a):(b))
#define mabs(x) ((x)>0?(x):(0-(x)))
#define N_max 100005

int n;
int a[N_max];
int help[N_max];
int cnt;
int check(int a0,int an) {
    //不存在这样的等差数列
    if (mabs(an - a0) % (n-1) != 0)return 0;

    cnt = 0;
    for (int i = 1; i <= n; ++i) {
        help[i] = a0 + (an - a0) / (n-1)*(i-1);
        if (mabs(a[i] - help[i]) > 1)return 0;
        else if (a[i] != help[i])cnt++;
    }
    return 1;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &a[i]);
    }
    if (n == 1) { printf("0"); return 0; }
    int ck;
    int flag=0, mi=100005;
    //遍历所有的起点和终点
    for (int l = a[1] - 1; l <= a[1] + 1; ++l) {
        for (int r = a[n] - 1; r <= a[n] + 1; ++r) {
            if (1 == check(l, r)) {
                flag = 1;
                //维护最少的操作次数
                mi = min(mi, cnt);
            }
        }
    }
    if (flag == 1)printf("%d", mi);
    else  printf("-1");
    return 0;
}

E. Bus Video System

【题意】
记录n个站人数的变化的个数,求最开始的时候的人数有多少种可能性(也就是取值范围的大小)。

【思路】
假设起始是0个人,按照记录模拟一遍人数的变化,并记录下人最多和最少的个数 max和min
假设起始的人数最少是l最大是r,那么只要满足

0lrwl+min>=0r+max<=w { 0 ≤ l ≤ r ≤ w l + m i n >= 0 r + m a x <= w

[l,r] [ l , r ] 就是解

【注意】
不懂为什么会卡long long

【代码】

#include<stdio.h>
#include<algorithm>
using std::sort;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define mabs(x) ((x)>0?(x):(0-(x)))
#define N_max 1003

typedef long long ll;
ll n,w, a[N_max];
/*
每站人数变化,记录y-x
*/

int main() {

    scanf("%lld %lld", &n,&w); a[0] = 0;
    ll mi=1000000009, ma=0;
    for (int i = 1; i <= n; ++i)
    {
        scanf("%lld", &a[i]);
        a[i] += a[i - 1];
        mi = min(a[i], mi);
        ma = max(a[i], ma);
    }
    ll l, r;
    //构造满足不等式的l,r
    if (mi < 0)l = 0 - mi;
    else l = 0;
    if (ma < 0)r = w;
    else r = w - ma;
    if (l <= r)printf("%lld", r - l + 1);
    else printf("0");
}

F. Mentors

【题意】
一个办公室有 n n 个程序员,水平为ri,如果两个程序员 a a b,如果 a a 的水平比b的水平高 (ra>rb) ( r a > r b ) ,并且两个人没有吵架,那么 a a 就可以取指导b写程序。求每个程序员可以指导的人数(猿数)
【思路】
可以指导的人数=水平更低的人数-水平低并吵架的人数
水平更低的人数:把程序员按水平排序,比他水平更低的人数就是水平比他低的人的最大排名。
水平低并吵架的人数:吵架的配对中,记录水平更低的人的数量
【代码】

#include<stdio.h>
#include<algorithm>
using std::sort;

#define N_max 200005

struct thing {
    int v;
    int id;
    thing():v(0),id(0){}
}pro[N_max];
int dis[N_max];
int cmp(thing t1, thing t2) {
    return t1.v != t2.v?t1.v<t2.v:t1.id<t2.id;
}
int cmpi(thing t1, thing t2) {
    return t1.id < t2.id;
}
typedef long long ll;

int n, k;
int ipt[N_max];
int cnt1[N_max];
int main() {
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", ipt + i);
        pro[i].v=ipt[i];
        pro[i].id = i;
    }
    //按水平排序
    sort(pro + 1, pro + n + 1, cmp);
    int a1, a2;
    //记录水平更低并吵架的人数
    for (int i = 0; i < k; ++i) {
        scanf("%d %d", &a1, &a2);
        if (ipt[a1] > ipt[a2])
            cnt1[a1]++;
        else if (ipt[a2] > ipt[a1])
            cnt1[a2]++;
    }

    for (int i = 1; i <= n; ++i) {
        int l = 0, r = n + 1, mid;
        //二分查找水平更低的人的排名
        while (l + 1 < r) {
            mid = (l + r) / 2;
            if (pro[mid].v <= ipt[i] - 1)l = mid;
            else r = mid;
        }
        //l就是水平低的人的最大排名
        printf("%d%c", l - cnt1[i], i == n ? '\n' : ' ');
    }
}

G. Petya’s Exams

【题意】
一共有n天,m场考试,每场考试在第 di d i 天,可以从第 si s i 天开始复习,总共需要 ci c i 天来复习。同一天只能复习一科,参加一科考试,或者啥也不干。也就是说每场考试的可复习时间为 si s i ~ di d i -1,并且要从中选出 ci c i 天只用来复习这场考试。问能否安排好时间让每一科都复习完。如果能,输出每天复习的科目编号,如果那天是考试就输出 m m +1;如果不能,输出-1。

【思路】
这个问题的最优解应该是:所有考试的复习,都安排在与其他考试冲突最少的时间段。可以通过对时间段按di递增,优先处理早结束的考试,并且给它安排的复习时间尽可能的早;而对于同一天的考试,只要每场考试的复习都是尽早安排的,在有解的情况下,它们一定不会冲突。如果按照冲突最少的方案来分配都找不到解,那么一定不存在解。

【代码】

#include<stdio.h>
#include<algorithm>
using std::sort;
#define N_max 102

//考试结构体
struct thing {
    int s, d, c;
    int id;
}ex[N_max];
//排序方式:按结束时间递增排序
int cmps(thing t1, thing t2) {
    return t1.d != t2.d ? t1.d < t2.d : (t1.s != t2.s ? t1.s > t2.s : t1.c < t2.c);
}

int n, m;
//日期安排的记录
int vis[102];
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; ++i)
    {
        scanf("%d %d %d", &ex[i].s, &ex[i].d, &ex[i].c);
        ex[i].id = i + 1;
        //考试的日子也不能冲突
        if (vis[ex[i].d] == 0)vis[ex[i].d] = m + 1;
        else { printf("-1"); return 0; }
    }
    sort(ex, ex + m, cmps);
    for (int i = 0; i < m; ++i) {
    //依次安排每场考试
        for (int t = ex[i].s; t < ex[i].d&&ex[i].c>0; ++t) {
        //遍历这场考试的可复习时间
            if (vis[t] == 0) {
                vis[t] = ex[i].id; ex[i].c--;
            }
        }
        //未能找到足够的时间
        if (ex[i].c > 0) { printf("-1"); return 0; }

    }
    for (int i = 1; i <= n; ++i) {
        printf("%d%c", vis[i], i == n ? '\n' : ' ');
    }
}

【总结】
思路一定要清楚,时刻着眼于解空间的构成来抓住问题的核心

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值