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 最后看到

了几条消息。

 


输入输出样例

输入样例#1: 
2 8
! 1
! 2
+ 1 2
! 1
! 2
- 1 2
! 1
! 2
输出样例#1: 
1 1

说明

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;
}
微博(mlogn)

 

做完之后一想,总觉得这个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;
}
微博(m)

这样只用O(m)就解决了问题 虽然mlogn就能过

by:wypx

 



 

s: 我永远喜欢珂朵莉

w:珂教兴国x

以上是关于luogu P3998 [SHOI2013]发微博的主要内容,如果未能解决你的问题,请参考以下文章

[SHOI2013]发微博

4419. [SHOI2013]发微博乱搞

bzoj4419[SHOI2013]发微博

Luogu P3990 [SHOI2013]超级跳马

luoguP2163 [SHOI2007]园丁的烦恼

Luogu-3829 [SHOI2012]信用卡凸包