Attention:
此题自己只能得到20分,参考的是b站上的大神这里是他的视频解题精讲
本文前半部分是自己的菜菜子代码,后半部分是对大神满分代码的学习。
①注意有些数据的类型需要用long long。
②自己的思路:
map<long long, map<int, id_with_max>> machineMax;
上述结构用来存储第x台计算机在y天时的“通信主要对象”信息(id及max)
map<long long, map<long long, map<int, long long>>> x_y_t;
上述结构用来存储第x台计算机与第y台计算机在z天时的每日可用额度
主要思路是维护上述两个map,在每天的申请时更新他们的信息。
需要查询“通信主要对象”,直接输出machineMax对应的结果。
需要查询“通信孤岛”时,O(n)遍历查询machineMax对应为0的节点个数。
需要查询“通信对”时,O(n)遍历查询符合条件的节点个数。
20分待完善的代码如下:
#include<bits/stdc++.h>
using namespace std;
struct id_with_max
{
long long id = 0, max = 0;
};
map<long long, map<int, id_with_max>> machineMax;
map<long long, map<long long, map<int, long long>>> x_y_t;
long long n, k, a, b, c, x, ans, pair_id;
int m, d, l, p, q;
void query_alone(int day, long long n)
{
ans = 0;
for (long long i = 1; i <= n; ++i)
if (machineMax[i][day].id == 0)
ans++;
cout << ans << endl;
return;
}
void query_pair(int day, long long n)
{
ans = 0, pair_id = 0;
for (long long i = 1; i <= n; ++i)
{
pair_id = machineMax[i][day].id;
if (machineMax[pair_id][day].id == i)
ans++;
}
ans = ans / 2;
cout << ans << endl;
return;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; ++i)//第i天
{
//第i天的k个申请
cin >> k;
for (int j = 1; j <= k; ++j)
{
cin >> a >> b >> c >> d;
for (int ii = i; ii < i + d; ++ii)
{
x_y_t[a][b][ii] += c;
x_y_t[b][a][ii] += c;
//update机器a
if (machineMax[a][ii].id == b)//ii天的“通信主要对象”就是b
machineMax[a][ii].max += c;
if (machineMax[a][ii].id != b && machineMax[a][ii].max < x_y_t[a][b][ii])//非b且已标记的最大额度较小
{
machineMax[a][ii].max = x_y_t[a][b][ii];
machineMax[a][ii].id = b;
}
if (machineMax[a][ii].id > b && machineMax[a][ii].max == x_y_t[a][b][ii])//非b且已存最大额度与当前的相等
machineMax[a][ii].id = b;
//update机器b
if (machineMax[b][ii].id == a)
machineMax[b][ii].max += c;
if (machineMax[b][ii].id != a && machineMax[b][ii].max < x_y_t[b][a][ii])
{
machineMax[b][ii].max = x_y_t[b][a][ii];
machineMax[b][ii].id = a;
}
if (machineMax[b][ii].id > a && machineMax[b][ii].max == x_y_t[b][a][ii])
machineMax[b][ii].id = a;
}
}
//第i天需要查询的“通信主要对象”数量
cin >> l;
for (int j = 1; j <= l; ++j)
{
cin >> x;
cout << machineMax[x][i].id << endl;
}
//第i天是否查询“通信孤岛”、“通信对”
cin >> p >> q;
if (p) query_alone(i, n);
if (q) query_pair(i, n);
}
return 0;
}
一、大神的代码思路:(以下仅个人理解,如有问题欢迎指正)
1.数据结构分析
①采用node类型的set来存放每个点的额度情况
也就是说,把和点i相关的其他点的id及额度都放在了d[i]中,用set来维护就可以使得最前面的是我们需要的“最大通信对象”,从而大大提高了查找的效率。
struct node{
ll value;
int to;
}
set <node> d[maxn];
②采用map来进行额外保存额度值
也就是说,存储int点1和int点2间的ll额度。因为无特殊要求,因此用map,简洁实用。
map <pair<int, int>, ll> save;
③采用vector反向激活
反向激活这个词最初我很不理解。后来发现就是在deActive[day]中存需在day天删除的额度,感觉用额度过期我会更好理解。像王家卫电影中说的,“沙丁鱼会过期,肉罐头会过期,连保鲜纸都会过期,我开始怀疑,在这个世界上,还有什么东西是不会过期的?”此题亦如是,机器间可以相互通信,但给你的额度也是会过期的,当那天day来临时,死神就会根据着死亡笔记deActive[day]大喊一声“机器u、v的额度酱,你的死期到了!”,然后不顾u、v曾多么密切地通信过,直接生硬地拿大镰刀砍掉可怜的额度酱。
vector <info> deActive[maxn];
2.算法思路分析
(1)主函数:对于m天中的第i天,进行如下的操作:
①处理过期的额度
在今天的其余操作前,需要先删除过期的额度,防止影响后续操作。
具体操作是使用work函数,来减去deActive[i]中在第i天过期的额度,注意通信是双向的,因此操作是双向的。
②输入今天的额度
当额度死期在m天及其内时,记录到deActive[i]中,其余都是正常使用work直接存储额度。
③查询主要通讯对象
因为d[i]中存储与i点相关的额度信息,已排好序,因此直接使用。
④查询“通信孤岛”和“通信对”
(2)int check_p(int x)检查一个点 是否为孤岛函数
要么x没有和其余通信,即d[x].begin() == d[x].end();
要么经过变化后额度也为0,即d[x].begin()->value == 0。
(3) int check_q(int x)检查一个点 是否包含一个通讯对函数
如果x是孤岛,那是没有包含通信对的。
如果不是,则看它的最大通信对象的最大通信对象是不是它,需要保证其最大通信对象也不是孤岛。
(4)void work(int u, int v, int x)函数
前提说明:因为随着每日过期、每日申请的同时,p、q会变化,所以每日过期、每日申请的时候就进行p、q的维护。
①删去此时u对p、q的影响
②更新save中u到v的value值
③对d[u],删去之前的旧数据,更新新的
④加上此时u对p、q的影响
最初我不理解①④步骤的意义,后来渐渐有了点认识。我们这个函数的目的是更新save、d[i]以及其改变也会影响的p、q。更新前两个好理解,那怎么想到如何更新pq呢?那就是先删去此时u点已经给p、q带来的影响,就当它此时不存在,处理后再加入它带来的影响。类比成魔方,pq是六个面的九个颜色情况,save等是变动模仿块。当还差一块复原时,先把那一块拔下来(删去对当前情况的影响),再调整角度(更新),最后插进去(加入现在那一块对局面的影响)。
3.未曾设想的道路
①对p、q的维护方式
之前认为q对子数量是变化不定的,毕竟两个才组成一个对子,查询时进行看看有没有对应的有点麻烦。这里的思路是考虑了在更新每个点时就会给pq带来变化,因此在处理每个点时也考虑其对pq的影响,对pq进行实时动态维护。
②使用set组织每个节点与其他节点的联系情况
node中重载了比较符号,set可以自动按红黑树排序好,不用自己进行顺序维护。
二、这篇代码让我学到的新知识
1.为基本数据类型定义新的类型名
typedef long long ll;
优点:简化代码,提高速度。
2.const常量与define宏定义
const int maxn = 100010;
之前我习惯用define来定义,但是发现很多代码使用const定义。
查阅一些资料传送门在此后,结论是:
除非你需要使用表达式或者在条件语句中定义常量,不然的话仅仅是为了代码的简单可读性你都最好要使用const!
3.无穷大常量的定义
const int inf = ~(1u << 31u);
const ll linf = ~(1llu << 63u);
分别得到int和ll类型的最大值。
4.struct中的操作
// 生成构造函数
node(ll value, int to) : value(value), to(to) {}
// 重载运算符,以便在set中排序
// 对set重定义时,后必须加const,否则在比较过程中结构更改导致无法构建红黑树
bool operator < (const node &d) const {
return value == d.value ? to < d.to : value > d.value;
}
5.返回值
return d[x].begin() == d[x].end() || d[x].begin()->value == 0;
如果我写这个逻辑,会写成if(……)return x else return y的形式,而上面的逻辑显然少了判断更加简洁。
6.对vector的操作
vector <info> deActive[maxn];
// 删除 旧的
node org(orgValue, v);
d[u].erase(org);
// 插入 新的
d[u].emplace(save[{u, v}], v);
// 判断 为空
d[x].begin() == d[x].end();
// 得到排序后最前面的数据信息
d[x].begin()->value == 0;
7.提高输入输出速度
ios::sync_with_stdio(false);
cin,cout之所以效率低,是因为先把要输出的东西存入缓冲区,再输出,导致效率降低,而这段语句可以来打消iostream的输入 输出缓存,可以节省许多时间,使效率与scanf与printf相差无几
8.使用“\n”而不是endl
if(check_p(x)) cout << 0 << "\n";
输出时换行用\n,否则要刷新缓冲区会导致速度变慢。
9.输入重定向
freopen("1.txt", "r", stdin);
在cpp文件路径下写一个输入的txt文件,进行输入重定向,从而减少自己手动输入的时间。
一定记得提交时要注释掉!!!
当我照着b站视频讲解手敲后,却发现自己的代码是错误0分,还以为是细节抄错了,最后才发现是没有把这一条注释掉,从而导致答案全是错误的。
最后,拜读学习的代码如下:
#include <bits/stdc++.h>
typedef long long ll;
const int maxn = 100010;
const int inf = ~(1u << 31u);
const ll linf = ~(1llu << 63u);
using namespace std;
struct node{
ll value;
int to;
// 生成构造函数
node(ll value, int to) : value(value), to(to) {}
// 重载运算符,以便在set中排序
// 对set重定义时,后必须加const,否则在比较过程中结构更改导致无法构建红黑树
bool operator < (const node &d) const {
return value == d.value ? to < d.to : value > d.value;
}
};
struct info{
int u, v, x;
info(int u, int v, int x) : u(u), v(v), x(x) {}
};
set <node> d[maxn];
map <pair<int, int>, ll> save;
vector <info> deActive[maxn];
int p_value, q_value;
// 检查一个点 是否为孤岛
int check_p(int x){
return d[x].begin() == d[x].end() || d[x].begin()->value == 0;
}
// 检查一个点 是否包含一个通讯对
int check_q(int x){
if(check_p(x)) return 0;
int y = d[x].begin()->to;
return !check_p(y) && d[y].begin()->to == x;
}
void work(int u, int v, int x){
// 处理 孤岛数量
p_value -= check_p(u);
// 处理 通讯对数量
q_value -= check_q(u);
ll orgValue = save[{u, v}];
save[ {u, v}] += x;
// 删除 旧的
node org(orgValue, v);
d[u].erase(org);
// 插入新的
d[u].emplace(save[{u, v}], v);
// 处理 孤岛数量
p_value += check_p(u);
// 处理 通讯对数量
q_value += check_q(u);
}
int main()
{
ios::sync_with_stdio(false);
//freopen("1.txt", "r", stdin);
int n, m;
cin >> n >> m;
p_value = n;
q_value = 0;
for(int i = 1; i <= m; ++i){
// 处理过期的额度
for(const auto &x : deActive[i]){
work(x.u, x.v, -x.x);
work(x.v, x.u, -x.x);
}
int k;
cin >> k;
// 输入当天的额度
for(int j = 1; j <= k; ++j){
int u, v, x, y;
cin >> u >> v >> x >> y;
if(i + y <= m)
deActive[i + y].emplace_back(u, v, x);//反向激活
work(u, v, x);
work(v, u, x);
}
int l;
cin >> l;
// 查询主要通讯对象
for(int j = 1; j <= l; ++j){
int x;
cin >> x;
if(check_p(x)) cout << 0 << "\n";
else cout << d[x].begin()->to << "\n";
}
int p, q;
cin >> p >> q;
// 孤岛数量
if(p) cout << p_value << "\n";//换行用\n,否则刷新缓冲区会慢
// 通讯对数量
if(q) cout << q_value << "\n";
}
return 0;
}
本次学习中新认识了vector和set,了解了在本篇代码中出现的其相关常用操作,也改进了自己的一些编程细节习惯,受益匪浅。