[题解] Codeforces Round #503 (by SIS, Div. 2)

版权声明:欢迎转载评论 https://blog.csdn.net/m0_37809890/article/details/81608765

暴露了很多问题

1020A. New Building for SIS 模拟

n栋h层的大楼排成一列,相邻大楼在a到b层有通路,k个询问,给出两个大楼的特定层(ta,fa,tb,fb),问至少需要几步可以互相到达?

如果ta=tb,答案是abs(fa-fb)
否则
如果fa<a且fb<a,答案是a-fa+a-fb+abs(ta-tb)
如果fa>b且fb>b,答案是fa-b+fb-b+abs(ta-tb)
否则答案是abs(fa-fb)+abs(ta-tb)

逻辑要清楚

1020B. Badge 模拟

n个少女,每个少女都有一名契约者是另一个少女(可以是自己),现在主人公要沿着少女i-少女i的契约者-少女i的契约者的契约者-….逐个收服,问哪个少女会被最先收服两次?输出i取遍1到n时的答案.

模拟,逐个找爸爸.

1020C. Elections 枚举

有m个候选人进行选举,当一名候选人的票数比其他所有候选人多时才能胜出.现在有n个选民,每个选民都有自己支持的候选人ci,但你可以花费pi元来收买这个选民让他选你.给定n(3000),m(3000),所有ci,pi,假设你现在是一号候选人,问至少花多少钱可以胜出?

想了很久的思路,没办法直接求解,但是n只有3000,可以枚举最后胜出时自己的票数至少为多少.

设自己的票数至少为xi,即其他人的票数最多为xi-1.首先遍历每个候选者,如果支持人数超过这个值,就收买那些最好收买的人使得人数恰好不超过这个值.
遍历收买完成后,如果自己的票数未达到xi,就从剩下所有别人的支持者中收买最好收买的几个使得自己的支持者达到xi.
这个方法我没有办法证明,但确实是对的,而且合情合理.

写起来也有很多细节,比如一个人如果在遍历阶段收买了,怎么在收尾阶段不重复收买他.
我这里使用了双指针扫描法,记录每个遍历时收买了的人然后逐个对比.

当写细节多的题时,一定要多写注释,搞清楚每个变量,每段代码的作用!

vector<int> save[M]; //单个候选人的支持者
vector<int> other; //全部其他候选人的支持者
int main(void)
{
    int n = read(), m = read();
    int st = 0; //初始支持自己的人数
    for(int i = 0; i < n; i++)
    {
        int c = read(), p = read();
        if(c == 1) st++;
        else save[c].push_back(p), other.push_back(p);
    }
    for(int i = 2; i <= m; i++)
        sort(save[i].begin(), save[i].end());
    sort(other.begin(), other.end());

    ll ans = LLONG_MAX;
    for(int need = 1; need <= (n + 1) / 2; need++) //自己所需人数
    {
        ll tmp = 0; //消耗金钱
        vector<int> die;
        int now = st; //当前支持自己的人数
        for(int i = 2; i <= m; i++)
        {
            int cut = save[i].size() - need + 1; //需要裁剪的人数
            for(int j = 0; j < cut; j++)
            {
                tmp += save[i][j];
                die.push_back(save[i][j]);
                now++;
            }
        }

        sort(die.begin(), die.end());
        int i = 0, j = 0, l = die.size(); //双指针法,ij分别为other与die的下标
        while(now < need)
        {
            if(j < l && other[i] == die[j])
                j++;
            else
            {
                tmp += other[i];
                now++;
            }
            i++;
        }
        ans = min(ans, tmp);
    }
    cout << ans << endl;

    return 0;
}

1020D. The hat 交互,二分

n(1e5,偶数)个人编号1到n按顺序围成一圈坐着,每个人手里有一个数字(±1e9),相邻两个人的数字之差为1,你可以提问最多60次某个人手里的数字是什么,然后输出一个编号i,要求编号为i的人与他对面(i±n/2)的人手里的数字相同,无解输出-1.

题意看了好长时间才看懂,最后隐约想到了解法但是根本实现不了,后来看neal大佬的题解才补完.

首先题目需要找到一个i,使得a[i]和a[i+n/2]相同.
有一个天才般的简化问题的方式:记d[i]=a[i]-a[i+n/2],则问题转化为找到一个满足d[i]=0的i.
因为a[i]和a[i+1]的差只可能是1或-1,所以d[i]和d[i+1]的差只可能是2,0,-2
如果d[i]为奇数,那么所有的d值都为奇数,不可能存在0,无解.
又因为d[i+n/2]=-d[i],如果不为0的话两者必定异号,则一定有解且在i与i+n/2之间.
开始二分,选择中间值与边缘值异号的一段继续二分直到找到d[i]=0的位置.
注意另一段也可能存在解,但选择边缘值异号的一段一定可以找到一个解.

关于交互题的一些注意事项
1. 交互题有很多二分题,想思路可以优先往这边靠
2. 可以将查询和输出操作封装起来,会好看也好写很多,本题中甚至将输出封装在了查询里.
3. 交互题的输入和输出实际上也是很灵活的,可以多次查询一次读取.

将交互题训练加入待做套餐,以后有(?)时(?)间(?)可以考虑训练一波,即使这个东西我只在cf上见过.

针对本题,这种将两个变量纠缠的函数简化成一个函数的方式,真的太好用了!

int n;
int query(int id)
{
    int v1,v2;
    printf("? %d\n? %d\n",id,id+n/2 );
    fflush(stdout);
    scanf("%d %d",&v1,&v2);
    if(v1-v2==0)
    {
        printf("! %d\n",id );
        exit(0);
    }
    return v1-v2;
}
int main(void)
{
    n=read();
    if(n%4)
    {
        printf("! -1\n");
        exit(0);
    }
    int lef = 1, rig = n/2;
    int lefv = query(lef);
    int lefs = lefv/abs(lefv);
    while(lef<=rig)
    {
        int mid = (lef+rig)>>1;
        int midv = query(mid);
        if(midv*lefs<0) rig=mid-1;
        else lef = mid+1;
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/81608765