luogu P3998 [SHOI2013]发微博
Posted ck666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luogu P3998 [SHOI2013]发微博相关的知识,希望对你有一定的参考价值。
题目描述
刚开通的 SH 微博共有n个用户(1Ln标号),在这短短一个月的时间内,
用户们活动频繁,共有m 条按时间顺序的记录:
! x 表示用户x发了一条微博;
+ x y 表示用户x和用户y成为了好友
− x y 表示用户x和用户y解除了好友关系
当一个用户发微博的时候,所有他的好友(直接关系)都会看到他的消息。
假设最开始所有人之间都不是好友关系,记录也都是合法的(即+ x y时x和
y一定不是好友,而− x y时x和y一定是好友)。
问这 m 条记录发生之后,每个用户分别看到了多少条消息。
输入输出格式
输入格式:
第 1行 2个整数n,m。
接下来m 行,按时间顺序读入m 条记录,每条记录的格式如题目所述,用
空格隔开。
输出格式:
输出一行 n 个用空格隔开的数(行末无空格),第i 个数表示用户i 最后看到
了几条消息。
输入输出样例
说明
n<=200000
m<=500000
30分?
暴力模拟。
我们记录在当前x点现在可以产生$cnt_x$的贡献
对于每一次加边,记录它所到达的点,并记录当前这个点出现贡献次数为dis
然后单次删除到x的边
就是找到这个边,答案就会产生$cnt_x-dis$的贡献
这样单次查找到这个点的复杂度是$O(n)$,结合m次的操作
大概就能过30%
100分
对于上面的进行一个优化。
对于每一个x点所到的点都可以看成一个集合,这样上面的做法是暴力查找集合中所有的元素。
显然这一步可以用数据结构暴力搞,比如set,map,使查找维持在logn,但我们不用这样
我们只需要集合中元素单调,就可以二分找到。
于是每一次加点就找到一个满足的位置,使加入这个点,集合中元素依旧单调。
删除就在vector中删,剩下的自动补充。
每一个点开一个vector维护dis和所到达的位置
直接在线搞出来就可以
#include<iostream> #include<algorithm> #include<cstdio> #include<map> #include<vector> using namespace std; const int maxn=200000+233; vector< pair<int,int> >v[maxn]; vector< pair<int,int> >::iterator it; int ans[maxn]; int n,m; char ch; int cnt[maxn]; int x,y; int main(){ ios::sync_with_stdio(0); cin>>n>>m; for(;m;m--){ cin>>ch; if(ch==\'!\'){ cin>>x; cnt[x]++; } else{ cin>>x>>y; if(ch==\'+\'){ v[x].insert(lower_bound(v[x].begin(),v[x].end(),make_pair(y,-2333)),make_pair(y,cnt[y])); v[y].insert(lower_bound(v[y].begin(),v[y].end(),make_pair(x,-2333)),make_pair(x,cnt[x])); } else{ it=lower_bound(v[x].begin(),v[x].end(),make_pair(y,-2333)); ans[x]+=cnt[y]-(it->second); v[x].erase(it); it=lower_bound(v[y].begin(),v[y].end(),make_pair(x,-2333)); ans[y]+=cnt[x]-(it->second); v[y].erase(it); } } } for(int i=1;i<=n;i++){ for(it=v[i].begin();it!=v[i].end();++it) ans[i]+=cnt[it->first]-(it->second); cout<<ans[i]<<" "; } return 0; }
但是其实可以离线从后往前减,这样做是O(m)的
~~应该没有人会在这里卡常数的~~
by:s_a_b_e_r
考虑一个用户x发微博的时候,他会对所有x当前的好友y产生一点答案贡献
于是从xy成为好友,一直到xy解除好友关系,x对y产生的总贡献一共是(解除好友关系时x的微博数-成为好友时x的微博数)
那么我们记录一个cnt[x],表示到目前为止x共发了多少条微博
对于每个点建立一个set记录x的当前好友集合
每次加(减)点的同时,把ans[x]减去(加上)cnt[y](有点像差分?)
但是因为只有在解除好友的时候贡献才会被完整统计,所以最后要手动解除所有人的好友关系,即遍历一遍每个人的set
时间复杂度O(mlogn),具体实现见代码
#include<iostream> #include<cstdio> #include<set> using namespace std; const int N=200009; int n,m,cnt[N],ans[N]; set<int>s[N]; set<int>::iterator it; int main(){ ios::sync_with_stdio(0); cin>>n>>m; for(int i=1;i<=m;++i){ char opt;cin>>opt; if(opt==\'!\'){ int x;cin>>x; ++cnt[x]; } if(opt==\'+\'){ int x,y;cin>>x>>y; ans[x]-=cnt[y];ans[y]-=cnt[x]; s[x].insert(y);s[y].insert(x); } if(opt==\'-\'){ int x,y;cin>>x>>y; ans[x]+=cnt[y];ans[y]+=cnt[x]; s[x].erase(y);s[y].erase(x); } } for(int i=1;i<=n;++i) for(it=s[i].begin();it!=s[i].end();++it){ ans[i]+=cnt[*it]; } for(int i=1;i<=n;++i)cout<<ans[i]<<" "; return 0; }
做完之后一想,总觉得这个set大材小用了,能不能不用啊
于是……开始考虑这个set到底起了什么作用
归根结底就是因为最后需要手动解除一遍所有人的好友关系,才需要用set来维护每个人的好友集合,这样可以知道每个人剩下的好友都是谁
那么能不能让他们到最后所有人都没有好友关系呢?
反着处理所有操作就可以啦,因为一开始所有人都没有好友关系
#include<iostream> #include<cstdio> using namespace std; const int N=200009,M=500009; int n,m,data[M][2],cnt[N],ans[N]; char opt[M]; int main(){ ios::sync_with_stdio(0); cin>>n>>m; for(int i=1;i<=m;++i){ cin>>opt[i]; if(opt[i]==\'!\')cin>>data[i][0]; if(opt[i]==\'+\')cin>>data[i][0]>>data[i][1]; if(opt[i]==\'-\')cin>>data[i][0]>>data[i][1]; } for(int i=m;i;--i){ if(opt[i]==\'!\')++cnt[data[i][0]]; if(opt[i]==\'+\'){ ans[data[i][0]]+=cnt[data[i][1]];ans[data[i][1]]+=cnt[data[i][0]]; } if(opt[i]==\'-\'){ ans[data[i][0]]-=cnt[data[i][1]];ans[data[i][1]]-=cnt[data[i][0]]; } } for(int i=1;i<=n;++i)cout<<ans[i]<<" "; return 0; }
这样只用O(m)就解决了问题 虽然mlogn就能过
by:wypx
s: 我永远喜欢珂朵莉
w:珂教兴国x
以上是关于luogu P3998 [SHOI2013]发微博的主要内容,如果未能解决你的问题,请参考以下文章