题目大意
给出一个有
n
n
n个点、
m
m
m条无向边,可能有重边、自环的图,经过图中的每条边都要花费一些钱,但是允许花
p
p
p(
p
p
p为任意正整数)的价格购买一张优惠券,使得可以免费通过
花
费
≤
p
花费 \leq p
花费≤p的所有边。现在有
q
q
q个询问,每个询问给出
x
,
p
x,p
x,p,问从
x
x
x号点出发,花费不超过
p
p
p时,最多能访问的节点数。
对于
20
%
20\%
20%的数据,
n
,
m
,
q
≤
5000
n,m,q \leq 5000
n,m,q≤5000;
对于
50
%
50\%
50%的数据,
n
,
q
≤
5000
n,q \leq 5000
n,q≤5000,
m
≤
4
×
1
0
5
m \leq 4 \times 10^5
m≤4×105;
对于
100
%
100\%
100%的数据,
n
,
q
≤
2
×
1
0
5
n,q \leq 2 \times 10^5
n,q≤2×105,
m
≤
4
×
1
0
5
m \leq 4 \times 10^5
m≤4×105,
0
≤
经
过
一
条
边
的
花
费
≤
1
0
9
0 \leq 经过一条边的花费 \leq 10^9
0≤经过一条边的花费≤109。
分析
这是一道用贪心的思想解决的题。
首先,我们得发现一个事实:当我们把每次询问的
p
p
p全部拿去买一张优惠券时,得到的答案是最优的。因为当我们花
a
a
a买一张优惠券后还要花
b
b
b(
b
>
a
b>a
b>a)再走一条边时,我们就可以直接花
b
b
b买一张优惠券。
于是,我们可以将所有询问离线,并将所有边和所有询问按花费或
p
p
p从小到大排序。然后我们顺序扫描排完序的序列,并用并查集维护当前每个连通块的大小。如果我们扫描到一条边,那么我们就合并这条边所连接的两个连通块;如果我们扫描到一个询问,我们就记录当前询问的
x
x
x所在连通块的大小作为该询问的答案。最后输出所有答案即可。
代码
根据我们的思路,可以写出如下代码:
#include<cstdio>
#include<algorithm>
using namespace std;
char buf[16777216];
inline int Read() //快速读入
{
static char* c = buf;
while (*c < '0' || *c>'9')
{
++c;
}
int ans = *c ^ 48;
while (*(++c) >= '0' && *c <= '9')
{
ans = ans * 10 + (*c ^ 48);
}
return ans;
}
struct Action //将边和询问排序用,若存的是边,则compare_val为花费,other_val1和other_val2为连接的两个点;若存的是询问,则compare_val为q,other_val1为询问编号的相反数,other_val2为x
{
int compare_val, other_val1, other_val2;
struct Compare
{
inline bool operator()(const Action a, const Action b) //比较函数(注意要让compare_val相同的询问排在边后)
{
return a.compare_val < b.compare_val || (a.compare_val == b.compare_val && (a.other_val1 > 0 && b.other_val1 < 0));
}
};
}act[600006];
int group[200002]; //并查集
inline int Group(const int pos)
{
if (group[pos] == pos)
{
return pos;
}
group[pos] = Group(group[pos]);
return group[pos];
}
int node[200002]/*每个连通块的大小*/, ans[200002]/*答案*/;
int main()
{
static_cast<void>(freopen("travel.in", "r", stdin)); //定义文件输入输出
static_cast<void>(freopen("travel.out", "w", stdout));
fread(buf, 1, 16777216, stdin); //读入输入
const int n = Read(), m = Read(); //读入n和m
for (int i = 1; i <= m; ++i) //读入每一条边
{
act[i].other_val1 = Read();
act[i].other_val2 = Read();
act[i].compare_val = Read();
}
const int q = Read(); //读入q
for (int i = 1; i <= q; ++i) //读入每一个询问
{
const int pos = m + i;
act[pos].other_val1 = -i;
act[pos].other_val2 = Read();
act[pos].compare_val = Read();
}
const int size = m + q;
sort(act + 1, act + (size + 1ll), Action::Compare()); //排序
for (int i = 1; i <= n; ++i) //初始化并查集
{
group[i] = i;
node[i] = 1;
}
int q_count = 0; //对已处理的询问的计数
for (int i = 1; i <= size; ++i) //顺序扫描
{
if (act[i].other_val1 < 0) //扫描到一个询问
{
ans[-act[i].other_val1] = node[Group(act[i].other_val2)]; //记录答案
if ((++q_count) == q) //若处理完所有询问则退出
{
break;
}
}
else //扫描到一条边
{
const int a = Group(act[i].other_val1), b = Group(act[i].other_val2); //合并连通块
if (a != b)
{
group[a] = b;
node[b] += node[a];
}
}
}
for (int i = 1; i <= q; ++i) //输出答案
{
printf("%d\n", ans[i]);
}
return 0;
}
总结
这道题结合了贪心思想和并查集,是一道不错的题目。