洛谷 P12862 [NERC 2020 Online] Miser 题解

P12862 Miser 题解

完整题目

P12862 [NERC 2020 Online] Miser

题目描述

在某所非传统大学中,食堂将在 nnn 天后举行开业仪式。在尚未开放的食堂门前,有一块标牌显示着距离开业的天数。
对于这 nnn 天中的每一天,食堂主管都知道当天会来学校并看到标牌的所有人员名单。主管需要每天选择一个标牌数字,并确保每个来校人员看到的数字是递减的。主管是个典型的吝啬鬼,希望尽可能少地订购不同数字的标牌。你的任务是帮助主管计算出最少需要订购多少种不同的标牌。
以第一个测试用例为例:人员 111 在第 111222555 天来校,人员 222 在第 222333444 天来校。主管可以仅订购四个标牌,数字分别为 111222333444:在第 555444 天放置数字 111 的标牌,第 333 天放置数字 222,第 222 天放置数字 333,第 111 天放置数字 444。这样,人员 111 将依次看到 444222111,人员 222 将依次看到 333222111

输入格式

输入的第一行包含一个整数 nnn —— 食堂开业前的总天数。接下来的 nnn 行描述每一天的情况。每行以一个正整数 kkk 开头,表示当天来校的人数,随后是 kkk 个不同的整数 —— 来校人员的编号。
所有 kkk 的总和不超过 10510^5105。人员编号为正整数且不超过 10510^5105

输出格式

输出一个整数 —— 最少需要订购的不同标牌数量。

输入输出样例 #1

输入 #1

5
1 1
2 1 2
1 2
1 2
1 1

输出 #1

4

输入输出样例 #2

输入 #2

5
1 1
1 1
1 1
1 1
1 1

输出 #2

5

说明/提示

一共有 nnn 天,每天有 kkk 个学生路过食堂窗口,求至少购买多少个数字牌才能使来过校的每名同学分别看到的数字是严格递减的。

算法思路

这道题可以用拓扑加贪心实现,用邻接表建图,然后每一次输入都标记这个节点存在,如果这个数字第一次出现,就记录最后一次出现是 iii 如果这个数之前出现过,那就建边,之前的这个节点入度加一并更新这个数最后一次出现。然后进行拓扑排序的初始化:循环判断,如果这个节点 iii 存在且入度为零,初始化深度为一然后进队列。拓扑排序部分就比较简单了,取队头,然后更新最大深度,遍历当前节点所有邻接点,临节点入度减一并更新邻接点的深度,如果深度为零,那就继续入队列。

代码段分析

  1. 变量定义部分
#include<bits/stdc++.h>
using namespace std;
const int N =1e4+5;
int n,in[N],d[N],nex[N],ans;
// n:节点数。
// in:入度。
// d:深度。
// nex:每个数字最后出现的节点。
bool v[N];// 用来标记节点是否存在。
vector<int>p[N];
queue<int>q;
  1. 数据读入、准备部分
	int k;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>k;
		int x;
		for(int j=1;j<=k;j++){
			cin>>x;
			v[i]=1;// 标记节点 i 存在。	
			if(nex[x]==0){// 如果 x 第一次出现。
				nex[x]=i;// 记录最后一次出现是。
				continue;
			}
			// 如果 x 之前出现过,建边。
			p[i].push_back(nex[x]);// 从当前节点i向之前最后一次出现 x 的节点建立边
			in[nex[x]]++;// 之前的这个节点入度加一。
			nex[x]=i;// 更新 x 最后一次出现。
		}
	}
  1. 拓扑排序初始化部分
	for(int i=1;i<=100000;i++){//拓扑初始化,节点数最多不超过十万。
		if(v[i]&&in[i]==0){// 如果这个节点 i 存在且入度为零.
			d[i]=1;// 初始化深度为一。
			q.push(i);// 进队列。
		}
	}
  1. 拓扑排序部分
while(!q.empty()){// 拓扑排序部分
		int x=q.front();// 取出队头
		q.pop();
		ans=max(ans,d[x]);// 更新最大深度
		for(size_t i=0;i<p[x].size();i++){// 遍历当前节点所有邻接点
			int y=p[x][i];
			in[y]--;// 临节点入度减一。
			d[y]=max(d[y],d[x]+1);// 更新邻接点的深度
			if(in[y]==0) q.push(y);// 如果深度为零,进队列
		}
	}

AC代码


#include<bits/stdc++.h>
using namespace std;
const int N =1e4+5;
int n,in[N],d[N],nex[N],ans;
// n:节点数。
// in:入度。
// d:深度。
// nex:每个数字最后出现的节点。
bool v[N];// 用来标记节点是否存在。
vector<int>p[N];
queue<int>q;
int main(){
	int k;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>k;
		int x;
		for(int j=1;j<=k;j++){
			cin>>x;
			v[i]=1;// 标记节点 i 存在。	
			if(nex[x]==0){// 如果 x 第一次出现。
				nex[x]=i;// 记录最后一次出现是。
				continue;
			}
			// 如果 x 之前出现过,建边。
			p[i].push_back(nex[x]);// 从当前节点i向之前最后一次出现 x 的节点建立边
			in[nex[x]]++;// 之前的这个节点入度加一。
			nex[x]=i;// 更新 x 最后一次出现。
		}
	}
	for(int i=1;i<=100000;i++){//拓扑初始化,节点数最多不超过十万。
		if(v[i]&&in[i]==0){// 如果这个节点 i 存在且入度为零.
			d[i]=1;// 初始化深度为一。
			q.push(i);// 进队列。
		}
	}
	while(!q.empty()){// 拓扑排序部分
		int x=q.front();// 取出队头
		q.pop();
		ans=max(ans,d[x]);// 更新最大深度
		for(size_t i=0;i<p[x].size();i++){// 遍历当前节点所有邻接点
			int y=p[x][i];
			in[y]--;// 临节点入度减一。
			d[y]=max(d[y],d[x]+1);// 更新邻接点的深度
			if(in[y]==0) q.push(y);// 如果深度为零,进队列
		}
	}
	cout<<ans;
	return 0;
}

有任何问题请指出,如果对你有帮助点个赞再走吧!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

—海燕—

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值