202209 CSP认证 | 防疫大数据

防疫大数据
这个题之前零零碎碎做过一次导致还有些印象,第一次做的时候觉得好难啊…主要就是没有理解清楚题目意思。我发现遇到这种关于时间区间,而且时间区间会更新,在输出的时候需要利用到是否在区间内的时候就会很迷茫思绪特别乱。

解题的核心题干内容如下:

形式化地,在d日生成的风险名单中,用户u被列入风险名单,当且仅当:
存在一个日期 d0 ∈ (d-7,d],存在一条d0日收到的漫游数据<d1, u, r>,使得
· d1 ∈ (d-7,d],并且
· 对于任意的D∈ [d1, d],地区r在D日处于风险状态。

对于本题的解决思路就是:
· 在录入每个时刻的风险地区的时候,更新该地区作为风险地区的起始时间和结束时间。 这里其实不需要想的很复杂,“我”需要保留的,是距离当前时刻最近的一段连续时间。比如A地区作为风险的时间有[1,8),[11,18),我不需要考虑所有时间段的存储,因为输出是随着时间的进行的,而时间只会向后推移,因此对于之前的连续数据 后续不会再用到,无需存储。所以在新的可更新区间出现之前,我只需要保留[11,18)即可。

· 然后对于每个时刻,我都要找到在当前时刻的风险用户有哪些。根据题目意思,我需要搜索当前时刻前一周之内的所有消息(这是为了确保条件d0 ∈ (d-7,d]);遍历这些消息,确保每条消息的d1 ∈ (d-7,d];找到这样的消息之后,只要在[d1,d]该地区r都为风险地区,则找到了这样的用户,此时需要利用到上面所保留到的区间信息。

分数进阶如下:
在这里插入图片描述
60分只涉及到一段代码的改变。
先贴上满分代码:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;  //用于记录一个地区为风险地区的首尾时间
const int N = 1010;
int n;
struct msg{
    int d, u, r;
};

unordered_map<int, PII> inState;
vector<msg> msgData[N];
set<int> ans;

void Search(int k)
{
    int endpoint = k - 7 > -1 ? k - 7 : -1;  //避免出现段错误
    for(int i = k;i > endpoint;i --){       //扫描在这一周所收到的所有消息
        for(auto x : msgData[i]){
            if(!inState.count(x.r)) continue;   //该地区没有成为过风险地区
            if(!(x.d > k - 7 && x.d <= k)) continue;   //满足d1 > d - 7 && d1 <= d,也即该用户访问的时间也是在一周之内

            if(ans.count(x.u)) continue;   //可能出现的重复消息

            int left = inState[x.r].first;
            int right = inState[x.r].second;
            if(left <= x.d && right >= k){   //是否连续为风险地区
                ans.insert(x.u);
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;

    int r, m;
    PII temp;
    for(int i = 0;i < n;i ++){
        cout << i << ' ';
        cin >> r >> m;
        while(r --){
            int p; cin >> p;  //风险地区
            if(inState.count(p) && i >= inState[p].first && i <= inState[p].second + 1){
                inState[p].second = i + 6;
            }else{
                temp.first = i;
                temp.second = i + 6;  //将区间设置为双闭区间
                inState[p] = temp;
            }
        }

        for(int j = 0;j < m;j ++){
            int d, u, r;
            cin >> d >> u >> r;
            msgData[i].push_back({d,u,r});
        }
        Search(i);
        for(auto x : ans) cout << x << ' ';
        cout << "\n";
        ans.clear();
    }
    return 0;
}


整段代码的撰写过程,修改了两个地方,分别是17行以及46行

17行修改之前的代码是:for(int i = k;i > k - 7; i --)
此时的错误点是,没有对下区间进行范围限制,如果取到负数,会导致后续msgData[i]的访问出现段错误。因此要对下界做限制,最小取到-1,使得最小访问到msgData[0]

46行修改之前的代码是:
if(inState.count(p) && i >= inState[p].first && i <= inState[p].second)
修改后的代码是:
if(inState.count(p) && i >= inState[p].first && i <= inState[p].second + 1)
区别比对一下还是很明显的,但是直接让代码从40上涨到100满分,感觉这个真的很细很细,比较难考虑到

这里主要就是连续区间。如果采用修改之前的代码:
例如原先的区间是[1,7],第8天该地区仍然为风险地区,则此时会修改为[8,14]. 出现的问题是,若此时在[8,14)之间的d天收到消息,某用户在d1天去过该风险地区,其中d1 ∈ (1,7],例如d0 = 5, d1 = 5, d = 9,也就是收到了延迟消息。那么此时应该判断该地区在[5,9]是否为连续的风险地区,在修改为[8,14]的基础上,此时会判断出false,而其实该地区在[1,14]都是连续的,此时会出现错误答案。
这是由于题目中给的区间是左闭右开,此时若前一段的右端点和后一段的左端点对齐,会形成连续区间。而我在题目中由于判断的方便将区间改成了左闭右闭,对于两段区间连续的判断需要右端点加一。也即修改后的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值