题目链接
题意:
给出 m 个人,n 个群组,s 个操作;
(
1
≤
n
≤
100000
,
1
≤
m
≤
200000
,
1
≤
s
≤
1000000
)
(1≤n≤100000,1≤m≤200000,1≤s≤1000000)
(1≤n≤100000,1≤m≤200000,1≤s≤1000000)
每个操作有三种类型:
- 1 x y:表示 人x 进入 群组y;
- 2 x y:表示 人x 退出 群组y;
- 3 x y:表示 人x 在 群组y 中发送了一条消息,群组y 中除了 x 的所有人都会收到。
问,最终每个人收到了多少条消息?
思路:
场上怎么想都不能做,模拟也不行,将集合中的所有人求前缀和也不行。。
归结于陷入了一个思想中:要把集合中的所有人的消息数+1 !
其实这题关键在于思维的转变,和之前做的一些不寻常的前缀和题目类似,我称之为 “流动前缀和”。
将每个群组看作一条延伸的线段,线段的长度随着消息数增长。
如果一个人在某个时刻 x 加入群组了,那么在时刻 y 退出群组后,其在这个群组中收到的消息数就为:时刻 y 时的总消息数 - 时刻 x 时的总消息数。
这个时间差中的消息数就是这个群组对此人答案的贡献。
思路完全相反于之前的思路!
之前的思路是考虑每条消息求群中所有人的影响。
而现在是反过来考虑每个人加入一个群组所得的贡献。
太妙了!
此外有些细节要注意:
1、最后有人没有退出群组怎么办呢?
所以要开一个set记录每个人所在的群组,退出就消除该记录。最后遍历set中剩余的没有退出的人和群组。
2、因为每个人发的消息自己对自己没有贡献,所以最终每个人的贡献要减去自己发的消息数。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], ans[N], sum[N];
set<PII> st;
signed main(){
Ios;
int k;
cin>>m>>n>>k;
while(k--)
{
int op, x, y;
cin>>op>>x>>y;
if(op==1)
{
ans[x] -= sum[y];
st.insert({x ,y});
}
else if(op == 2){
ans[x] += sum[y];
st.erase({x, y});
}
else{
ans[x]--;
sum[y]++;
}
}
for(auto it:st)
{
int x = it.fi, y = it.se;
ans[x] += sum[y];
}
for(int i=1;i<=n;i++) cout<<ans[i]<<"\n";
return 0;
}
太妙了啊!