【202203-4】通信管理系统

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,了解了在本篇代码中出现的其相关常用操作,也改进了自己的一些编程细节习惯,受益匪浅。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值