洛谷 4083 题解

本文介绍了一个关于派交换的游戏,通过构建最短路径算法解决游戏中互赠派直至评分归零的问题。利用广搜算法和multiset数据结构优化空间和时间复杂度,实现高效求解。

题意简述

(题面很长,作者语死早,能这样不错了)
给定n,d(n&lt;=1e5,d&lt;=1e9)n,d(n&lt;=1e5,d&lt;=1e9)n,d(n<=1e5,d<=1e9),以及2n2n2n个派。初始前nnnBessieBessieBessie那里,后nnn个在ElsieElsieElsie那里。每个派用两个值描述,aaabbb,表示BessieBessieBessie的评分和ElsieElsieElsie的评分。然后BessieBessieBessieElsieElsieElsie要互相送出去派,BessieBessieBessie会先送一个手上有的派给ElsieElsieElsie,设BessieBessieBessie给这个派的评分是xxx,则ElsieElsieElsie会选一个她手上有的并且她评分在[x,x+d][x,x+d][x,x+d]之间的派送给BessieBessieBessie。然后设ElsieElsieElsie给送出去这个派的评分是yyy,那么BessieBessieBessie会送回来一个她手上有的并且评分在[y,y+d][y,y+d][y,y+d]之间的派送回来给ElsieElsieElsie,以此类推。如果某人收到一个派,她认为这个派的评分是0,这个活动就结束了。输出nnn行,第iii行表示如果BessieBessieBessie送出了第iii个派,这样的活动几次能结束。无法结束输出−1-11

数据

输入
2 1
1 1
5 0
4 2
1 4
输出
3
1

解释:

初始状态:
blog1.jpg
对于第一行,BessieBessieBessie先送了评分是(1,1)(1,1)(1,1)的派出去,如下图:

blog2.jpg

然后ElsieElsieElsie眼里这个派的评分是111,它会送一个评分在[1,2][1,2][1,2]之间的派给BessieBessieBessie。这样的派只有一个,就是评分是(4,2)(4,2)(4,2)的那个派。如下图:
blog3.jpg

然后BessieBessieBessie眼里这个派的评分是444,它会送一个评分在[4,5][4,5][4,5]之间的派给ElsieElsieElsie。只有(5,0)(5,0)(5,0)这个派满足条件。如下图:
blog4.jpg
此时ElsieElsieElsie收到了一个评分为000的派,活动结束,所以第一行输出333

对于第二行,BessieBessieBessie送了那个评分是(5,0)(5,0)(5,0)的派,直接一次结束,因为此时ElsieElsieElsie收到了一个评分是000的派。

思路

这个题目是我的一个考试题,并且没有中文翻译。在考试的时候,阅读也是花了我不少时间。当时我用了1:05AK了考试,别的题用了10分钟,这个题用了55分钟。不过这个题如果看懂了的话,并不是很难。

不难得到,我们珂以从反面考虑,即从目标节点(一个评分为000的派)倒过来跑单源最短路,如果到不了设置为−1-11,然后nnn个询问,每个都输出一下最短路即可。但是这个建图贼耗空间,并且写图多麻烦(链式前向星写法)。所以这个办法需要优化。
我们会发现,这个图上每个边的长度都是固定的为111,所以我们不用建图,只要在遍历到iii的时候,有办法求\color{red}求iii到哪些点,而不是存\color{red}不是存iii到哪些点,这样不就省很多空间了么?并且这样跑一遍广搜也是O(n)O(n)O(n)的。

珂是,怎么算点iii到哪些点呢。。。
维护两个multisetmultisetmultiset,取名sa,sbsa,sbsa,sb,分别表示BessieBessieBessieElsieElsieElsie两人手里候选的派。sasasa按每个派的bbb评分排序,sbsbsb按每个派的aaa评分排序。每次考虑能送出去哪些派的时候,只要lowerboundlower_boundlowerbound一下即可,如果满足条件,就删除掉候选,加入到队列,如果不满足条件了,因为multisetmultisetmultiset是有序的,所以我们就再也找不到满足条件的了,直接breakbreakbreak掉。

代码:

#include<bits/stdc++.h>
#define N 200100
using namespace std;

int n,d;
int a[N],b[N],dis[N];
void Input()
{
    scanf("%d%d",&n,&d);
    for(int i=1;i<=(n<<1);i++)
    {
        scanf("%d%d",&a[i],&b[i]);
        dis[i]=-1;//dis的初始值设置为-1
    }
}

struct cmp_a//sa的排序方法
{
    bool operator()(int x,int y) const
    {
        return b[x]>b[y];
    }
};
struct cmp_b//sb的排序方法
{
    bool operator()(int x,int y) const
    {
        return a[x]>a[y];
    }
};
multiset<int,cmp_a> sa;
multiset<int,cmp_b> sb;//sa和sb
int Q[N],head,tail;//队列
void Solve()
{
    for(int i=1;i<=n;i++)
    {
        if (b[i]==0)
        {
            Q[tail++]=i,dis[i]=1;
        }//珂以作为起点的先入队列
        else sa.insert(i);//否则入候选

        if (a[i+n]==0)
        {
            Q[tail++]=i+n,dis[i+n]=1;
        }
        else sb.insert(i+n);//差不多的道理
    }
    multiset<int,cmp_a> ::iterator isa;
    multiset<int,cmp_b> ::iterator isb;//sa和sb分开用,因为排序方式不同

    while(head<tail)
    {
        int i=Q[head++];//取队首
        if (i<=n)//在Bessie手里
        {
            while(1)
            {
                isb=sb.lower_bound(i);//lower_bound找一下
                if (isb==sb.end() or a[i]>a[*isb]+d)//不满足条件
                //找不到或者超过了范围
                {
                    break;//直接break,无FA♂珂说
                }
                dis[*isb]=dis[i]+1;
                Q[tail++]=*isb;
                sb.erase(isb);//否则就加入到队列,删除候选
            }
        }
        else//和上面差不多
        {
            while(1)
			{
				isa=sa.lower_bound(i);
				if(isa==sa.end() or b[i]>b[*isa]+d)
                {
                    break;
                }
				dis[*isa]=dis[i]+1;
				Q[tail++]=*isa;
				sa.erase(isa);
			}
        }
    }
    for(int i=1;i<=n;i++)
    {
        printf("%d\n",dis[i]);
    }//输出每个dis即可
}

main()
{
    Input();
    Solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值