P12862 Miser 题解
完整题目
P12862 [NERC 2020 Online] Miser
题目描述
在某所非传统大学中,食堂将在 nnn 天后举行开业仪式。在尚未开放的食堂门前,有一块标牌显示着距离开业的天数。
对于这 nnn 天中的每一天,食堂主管都知道当天会来学校并看到标牌的所有人员名单。主管需要每天选择一个标牌数字,并确保每个来校人员看到的数字是递减的。主管是个典型的吝啬鬼,希望尽可能少地订购不同数字的标牌。你的任务是帮助主管计算出最少需要订购多少种不同的标牌。
以第一个测试用例为例:人员 111 在第 111、222 和 555 天来校,人员 222 在第 222、333 和 444 天来校。主管可以仅订购四个标牌,数字分别为 111、222、333 和 444:在第 555 和 444 天放置数字 111 的标牌,第 333 天放置数字 222,第 222 天放置数字 333,第 111 天放置数字 444。这样,人员 111 将依次看到 444、222 和 111,人员 222 将依次看到 333、222 和 111。输入格式
输入的第一行包含一个整数 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 存在且入度为零,初始化深度为一然后进队列。拓扑排序部分就比较简单了,取队头,然后更新最大深度,遍历当前节点所有邻接点,临节点入度减一并更新邻接点的深度,如果深度为零,那就继续入队列。
代码段分析
- 变量定义部分
#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 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);// 如果深度为零,进队列
}
}
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;
}
有任何问题请指出,如果对你有帮助点个赞再走吧!
2153

被折叠的 条评论
为什么被折叠?



