洛谷 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复制

输出 #1复制

说明/提示

n<=200000

m<=500000

题解:

2019双十模拟赛(蒟蒻正在文化课月考的考场上瑟瑟发抖)

后来7哥@littleseven带我做了这道题...

当时只想出了50分的暴力思路,非常容易想,就是开一个数组\(f[i] [j]\)(开bool数组,可以节约一些空间,让其能开到\(10^8\)),表示\(i\),\(j\)是不是朋友。然后每次有人发消息,就把它的所有朋友的答案\(+1\)。

50pts代码如下:

#include<cstdio>
#include<iostream>
using namespace std;
int n,m;
bool f[10000][10000];
int ans[10000];
int main()
{
// freopen("qq.in","r",stdin);
// freopen("qq.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
char ch;
cin>>ch;
if(ch=='+')
{
scanf("%d%d",&x,&y);
f[x][y]=1,f[y][x]=1;
}
else if(ch=='-')
{
scanf("%d%d",&x,&y);
f[x][y]=0,f[y][x]=0;
}
else
{
scanf("%d",&x);
for(int j=1;j<=n;j++)
if(f[x][j])
ans[j]++;
}
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}

然后介绍一下正解。

其实我上面的暴力思路如果再深入一下也就是正解了,思路都是:记录朋友,累加答案。那么,为什么上面的代码叫暴力,下面的代码叫正解,就是因为复杂度不一样。正解可以跑更大的数据。所以通过这道题,还能教会我们在考场上的一个答题技巧:当我们只能想出暴力思路的时候,想一想怎么通过已知手段优化暴力算法,说不定搞一搞正解就出来了。

闲话少叙,直奔主题:

我们已经得出了暴力的思路:记录朋友,累加答案。我们刚刚采用的记录朋友的办法是开矩阵数组。但这样肯定开不下空间(\(n\le 2\times 10^5\))。于是我们开始想:什么东西可以使得我们对\(1-n\)每个人都记录一个序列,同时做到方便地加入和删除呢?

\(STL\)大法:\(set\)容器。

使用了\(set\)的代码就变成了80分,剩下的两个点\(TLE\)了。

80pts代码如下:

#include<cstdio>
#include<iostream>
#include<set>
using namespace std;
const int maxn=2*1e5+1;
int n,m;
set<int> s[maxn];
set<int>::iterator it;
int ans[maxn];
char *p1,*p2,buf[100000];
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0,f=1;
char ch=nc();
while(ch<48){if(ch=='-')f=-1;ch=nc();}
while(ch>47) x=x*10+ch-'0',ch=nc();
return x*f;
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int x,y;
char ch=nc();
if(ch=='+')
{
x=read();y=read();
s[x].insert(y);
s[y].insert(x);
}
else if(ch=='-')
{
x=read();y=read();
s[x].erase(y);
s[y].erase(x);
}
else
{
x=read();
for(it=s[x].begin();it!=s[x].end();it++)
ans[*it]++;
}
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}

加了读入优化,然而并没有用......

那我们继续去想......

因为\(TLE\)了,那么显然是统计答案时出的问题。80分的思路统计答案的时候使用迭代器从头到尾给\(x\)的每个答案\(+1\),这种复杂度差不多是\(O(n\times m)\)显然是不行的。

那么我们这样考虑:既然一次加一个不行,我们就一次加一堆。

具体实现的原理是这样的:

设想一下,现在你是个包工头,有一个人要给你打工,工钱每天一块钱。然后有一天这个人不干了,于是你把他这些天的工钱一起结算给他,你们愉快地一拍两散~

迁移到这道题:当你每次更博的时候,就相当于工地干了一天活,你要给你的工人们(朋友)发一块钱的工资。你觉得一天一结算太麻烦,于是你记录下来了每个工人第一天来上班的日子和退休的日子,然后一起把钱结算给它们。

这是个差分思想么!

我们开一个数组\(cnt[i]\),表示截止到目前,\(i\)已经发了多少条微博。当一个人加了\(i\)的好友,那么就记录这个人什么时候加的\(i\),具体实现方法是把\(cnt[i]\)减掉,什么时候删除了\(i\)的好友,再把删除时的\(cnt[i]\)加回来。这样,\(cnt[i_1]\)和\(cnt[i_2]\)的差就代表了这个人成为\(i\)的好友的这一段时间内接收的微博数量。

当然,这样处理完\(m\)个询问的时候,还会有一些人仍然是其他人的好友。而我们的这个算法只在删除好友的时候统计答案。这就相当于工地有一天倒闭了,但是有一些工人仍在跟着你,你就算当裤子也必须得给他们发钱。

这道题不需要我们当裤子,只需要在处理完\(m\)个操作之后再加双层\(for\)循环强制统计答案即可。

(个人认为这个比喻好形象啊,如果你认为这对你的理解有所帮助,那我顺手求个推荐和好评~)

100pts代码如下:

#include<cstdio>
#include<set>
#include<iostream>
using namespace std;
const int maxn=2*1e5+1;
int n,m;
int cnt[maxn],ans[maxn];
set<int> s[maxn];
set<int>::iterator it;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
char opt;
cin>>opt;
int x,y;
if(opt=='!')
{
scanf("%d",&x);
cnt[x]++;
}
else if(opt=='+')
{
scanf("%d%d",&x,&y);
ans[x]-=cnt[y];
ans[y]-=cnt[x];
s[x].insert(y);
s[y].insert(x);
}
else
{
scanf("%d%d",&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++)
printf("%d ",ans[i]);
return 0;
}

洛谷 P3998 [SHOI2013]发微博的更多相关文章

  1. P3998 [SHOI2013]发微博 方法记录

    原题链接 [SHOI2013]发微博 题目描述 刚开通的 SH 微博共有 \(n\) 个用户(\(1\sim n\) 标号),在这短短一个月的时间内,用户们活动频繁,共有 \(m\) 条按时间顺序的记 ...

  2. 【BZOJ4419】[SHOI2013]发微博(???)

    [BZOJ4419][SHOI2013]发微博(???) 题面 BZOJ 洛谷 题解 一道\(easy\),每个点维护一下要给周围的点加上多上,如果额外连了一个点进来就给他把标记减掉,如果删掉了一条边 ...

  3. [BZOJ 4419][Shoi2013]发微博

    4419: [Shoi2013]发微博 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 665  Solved: 364[Submit][Status] ...

  4. BZOJ 4419: [Shoi2013]发微博 set模拟

    4419: [Shoi2013]发微博 题目连接: http://www.lydsy.com/JudgeOnline/problem.php?id=4419 Description 刚开通的SH微博共 ...

  5. bzoj4419[SHOI2013]发微博

    题意:给你一个初始没有边,点权均为0的无向图,三种操作:加边,删边,选择一个点将当前与之相邻的点(不包括自身)的点权+1,询问最后所有点的点权. 据说正解是set维护每个人的朋友,然后考虑每次加边.删 ...

  6. BZOJ4419: [Shoi2013]发微博 暴力

    Description 刚开通的SH微博共有n个用户(1..n标号),在短短一个月的时间内,用户们活动频繁,共有m条按时间顺序的记录: ! x   表示用户x发了一条微博: + x y 表示用户x和用 ...

  7. BZOJ4419:[SHOI2013]发微博(乱搞)

    Description 刚开通的SH微博共有n个用户(1..n标号),在短短一个月的时间内,用户们活动频繁,共有m条按时间顺序的记录: ! x   表示用户x发了一条微博: + x y 表示用户x和用 ...

  8. [SHOI2013]发微博

    Description 刚开通的SH微博共有n个用户(1..n标号),在短短一个月的时间内,用户们活动频繁,共有m条按时间顺序的记录: ​ ! x 表示用户x发了一条微博: ​ + x y 表示用户x ...

  9. 【bzoj4419】[Shoi2013]发微博 STL-set

    题目描述 刚开通的SH微博共有n个用户(1..n标号),在短短一个月的时间内,用户们活动频繁,共有m条按时间顺序的记录: ! x   表示用户x发了一条微博: + x y 表示用户x和用户y成为了好友 ...

随机推荐

  1. Codeforces Round #596 (Div. 2, based on Technocup 2020 Elimination Round 2) B. TV Subscriptions 尺取法

    B2. TV Subscriptions (Hard Version) The only difference between easy and hard versions is constraint ...

  2. PHP常用数字函数以及排序函数

    一:数字函数 .ceil() 进一取整 示例:ceil(0.9) 结果为1 .abs() 绝对值 示例:abs(-1) 结果为1 .rand() 随机数 示例:rand(1. 100) 1到100 以 ...

  3. 解惑:在Ubuntu18.04.2的idea上运行Scala支持的spark程序遇到的问题

    解惑:在Ubuntu18.04.2的idea上运行Scala支持的spark程序遇到的问题 一.前言 最近在做一点小的实验,用到了Scala,spark这些东西,于是在Linux平台上来完成,结果一个 ...

  4. nginx nginx_upstream_check_module自动踢除后端机器

    nginx 1.14.0 描述: nginx自带的upstream配置,如果后端挂了,接口会慢,原因不讲述,故接入第三方的自动检测与自动踢除模式 nginx_upstream_check_module ...

  5. Python 将numpy array由浮点型转换为整型

    Python 将numpy array由浮点型转换为整型 ——使用numpy中的astype()方法可以实现,如:

  6. 一文读懂前端技术演进:盘点Web前端20年的技术变迁史

    本文原文由作者“司徒正美”发布于公众号“前端你别闹”,即时通讯网收录时有改动,感谢原作者的分享. 1.引言 1990 年,第一个Web浏览器的诞生:1991 年,WWW诞生,这标志着前端技术的开始. ...

  7. PHP自动加载-spl_autoload_register

    spl_autoload_register 自动加载spl : Standard PHP library (标准PHP库) 首先来了解 __autoload print.class.php <? ...

  8. ​LeetCode 26:删除排序数组中的重复项 Remove Duplicates from Sorted Array

    给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成. Give ...

  9. 坑爹的京东E卡(京东E卡的正确使用方式)

      前言    今年中秋公司发了200的京东E卡(下面简称礼品卡,京东简称jd)这让喜欢在jd自营购买商品的我很是开心,    兴致勃勃打开官网,当我选好商品准备结算时却发现礼品卡无法使用.    后 ...

  10. python 自定义Iterator对象

    from collections.abc import Iterator class Company(object): def __init__(self, employee_list): self. ...