C220818C 城市游历
C220818 城市游历
【题目描述】
Alice 将在城市中旅游 q 天,城市中景点的数目为 n,每一个景点有一个特征值 𝑐𝑖,有 m 条双向道路连接这 n 个景点使得从任意一个景点出发都可以到达其他的景点。
每一条道路都有其困难系数𝑘𝑖,Alice 每天会从起点 x 出发做若干次出行。第 i 天接受程度初始为𝑙𝑖,每次出行结束后会增加 1,直到接受程度大于𝑟𝑖后不再出行。每一次出行Alice 可以经过𝑘𝑖 ≤当前接受程度的道路,Alice 也会去到所有能去的景点。
一次出行的愉悦值为到达的景点种类数,相同特征值的景点视作同一种,一天的愉悦值为当天所有出行的愉悦值之和。请你帮助 Alice 计算第 1 到 q 天每天她所能获得的愉悦值。
【输入格式】
第一行三个整数 n,m,q,x,代表景点数目、道路数目、游历天数与起点。
第二行 n 个整数,代表每一个景点的特征值𝑐𝑖。
接下来 m 行每行两个整数 i,j,k,代表一条连接(i,j),困难系数为 k 的边。
接下来 q 行每行两个整数 l,r,代表这一天的最小与最大接受程度。
【输出格式】
对于每个询问输出一行,代表每天所有出行的愉悦值总和。
【样例输入】(tour.in)
3 2 2 1
1 2 3
1 2 2
1 3 3
1 2
1 3
【样例输出】(tour.out)
3
6
【样例说明】
第一天第一次出行只能到 1,第二次可以到 1,2,愉悦值为 3
第二天第一次可以到 1,第二次可以到 1,2,第三次可以到 1,2,3,愉悦值为 6
【数据范围】
对于 30%的数据,𝑛, 𝑚, 𝑞, 𝑘𝑖, 𝑐𝑖, 𝑙𝑖, 𝑟𝑖 ≤ 100
对于另 20%的数据,𝑚 = 𝑛 − 1
对于另 20%的数据,𝑙𝑖, 𝑟𝑖, 𝑘𝑖 ≤
对于 100%的数据,𝑛, 𝑚 ≤
Solution
膜你赛的 T3,因为忘了把 #define int long long
的注释去掉然后怒丢
看到数据范围,图是
观察样例,不难发现询问的区间
因为每一次都是从起点出发,然后每次出行的接受程度
void prework()
{
int cnt[605],totans=1,curk=0;mem(cnt,0);
//cnt是桶,用于统计走过的不同点,totans内存储走过的不同点数,curk表示要到达当前点最少需要的接受程度
cnt[c[s]]=1;vis[s]=1;//无论如何起点都是可以到达的,所以将起点加入贡献
queue<int> q;//搜索队列
q.push(s);//加入起点
vec.push_back(make_pair(0,1));//表示当接受程度为0的时候可以走到一个不同的点
while (!eq.empty() || !q.empty())//eq是用来记录边的小根堆,两个任意有一个非空就需要进行搜索
{
if (!q.empty())
{
int x=q.front();q.pop();//取出队头
for (int i=head[x];i;i=edge[i].nxt)
{
if (vis[edge[i].to]) continue;//如果前进的点已经去过了就不需要再次去了
eq.push(make_pair(-edge[i].len,i));//priority_queue默认大根堆,将k值取负数就成了小根堆
}
}
if (!eq.empty())//如果还有没拓展的边
{
int curedge=eq.top().second;//取出当前k值最小的边的编号
eq.pop();
curk=max(curk,edge[curedge].len);//如果要经过这条边到达另一端至少需要有的接受程度,以此更新curk
if (!cnt[c[edge[curedge].to]]) totans++;//如果这个新的点的类型不同,累加进totans内
cnt[c[edge[curedge].to]]=1;//标记
vec.push_back(make_pair(curk,totans));//接受程度为curk的时候的答案为totans,具体为什么不需要去重,在后面询问的时候会处理的
if (!vis[edge[curedge].to])//如果新的点还没去过就加入搜索队列
q.push(edge[curedge].to),vis[edge[curedge].to]=1;
}
}
}
关于时间复杂度,很明显,因为每个点最多进队出队一次,每个边进队出队一次,所以预处理的时间复杂度是
PS. 其实这好像就是用 Prim 算法跑了个最小生成树,但是我在膜你赛的时候完全都没往最小生成树方面去想(
继续看题,会发现只求出一个
假设有一个接受程度
解释一下这个式子的含义,前缀和的求解仍然建立在递推的基础上,相邻两个特殊的
然后就是询问部分。刚才提到了
int Get(int x)//这个函数的作用就是查找到最靠右的那个小于等于x的位置
{
int l=0,r=sum.size()-1;//定义左右边界
while (l<=r)
{
int mid=l+r>>1;
if (sum[mid].first<=x) l=mid+1;
else r=mid-1;//需要知道,进入此部分分支的时候mid的值一定是在x右侧的,当循环结束的时候,mid将会停留在最靠左的大于x的位置上,此时r=mid-1,就是恰好最靠右的那个小于等于x的位置
}
return sum[r].second+(x-sum[r].first)*vec[r].second;//先是获得前r的前缀和,然后[r,x]之间的数之和就是这部分的vec值乘上这部分值的个数
}
这时候就可以来说说为什么不用去重了。我们二分到的最终位置是最靠右的小于等于
询问要求区间
询问部分时间复杂度是
综合来看,这种解法的时间复杂度是与 (而且我人傻常数大,说不定还跑得更慢),不过如果加大
Code
最主要的两个函数 prework
和 Get
如果明白了那主函数里面应该都不需要要提了吧(
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
k=0;T flag=1;char b=getchar();
while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
while (isdigit(b)) {k=k*10+b-48;b=getchar();}
k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
vector<pair<int,int> > vec,sum;//f[i]=j -> pair,sum of vec
priority_queue<pair<int,int> > eq;//edge to go,first is len of edge,second is num of edge
const int _SIZE=5e5;
struct EDGE{
int nxt,to,len;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y,int len)
{
edge[++tot]=(EDGE){head[x],y,len};
head[x]=tot;
}
int n,m,q,s,c[_SIZE+5];
bool vis[_SIZE+5];
void prework()
{
int cnt[605],totans=1,curk=0;mem(cnt,0);
//cnt是桶,用于统计走过的不同点,totans内存储走过的不同点数,curk表示要到达当前点最少需要的接受程度
cnt[c[s]]=1;vis[s]=1;//无论如何起点都是可以到达的,所以将起点加入贡献
queue<int> q;//搜索队列
q.push(s);//加入起点
vec.push_back(make_pair(0,1));//表示当接受程度为0的时候可以走到一个不同的点
while (!eq.empty() || !q.empty())//eq是用来记录边的小根堆,两个任意有一个非空就需要进行搜索
{
if (!q.empty())
{
int x=q.front();q.pop();//取出队头
for (int i=head[x];i;i=edge[i].nxt)
{
if (vis[edge[i].to]) continue;//如果前进的点已经去过了就不需要再次去了
eq.push(make_pair(-edge[i].len,i));//priority_queue默认大根堆,将k值取负数就成了小根堆
}
}
if (!eq.empty())//如果还有没拓展的边
{
int curedge=eq.top().second;//取出当前k值最小的边的编号
eq.pop();
curk=max(curk,edge[curedge].len);//如果要经过这条边到达另一端至少需要有的接受程度,以此更新curk
if (!cnt[c[edge[curedge].to]]) totans++;//如果这个新的点的类型不同,累加进totans内
cnt[c[edge[curedge].to]]=1;//标记
vec.push_back(make_pair(curk,totans));//接受程度为curk的时候的答案为totans,具体为什么不需要去重,在后面询问的时候会处理的
if (!vis[edge[curedge].to])//如果新的点还没去过就加入搜索队列
q.push(edge[curedge].to),vis[edge[curedge].to]=1;
}
}
}
int Get(int x)//这个函数的作用就是查找到最靠右的那个小于等于x的位置
{
int l=0,r=sum.size()-1;//定义左右边界
while (l<=r)
{
int mid=l+r>>1;
if (sum[mid].first<=x) l=mid+1;
else r=mid-1;//需要知道,进入此部分分支的时候mid的值一定是在x右侧的,当循环结束的时候,mid将会停留在最靠左的大于x的位置上,此时r=mid-1,就是恰好最靠右的那个小于等于x的位置
}
return sum[r].second+(x-sum[r].first)*vec[r].second;//先是获得前r的前缀和,然后[r,x]之间的数之和就是这部分的vec值乘上这部分值的个数
}
signed main()
{
read(n),read(m),read(q),read(s);
for (int i=1;i<=n;i++) read(c[i]);
for (int i=1;i<=m;i++)
{
int u,v,c;read(u),read(v),read(c);
AddEdge(u,v,c);AddEdge(v,u,c);
}
prework();
sum.push_back(vec[0]);
for (int i=1;i<vec.size();i++) sum.push_back(make_pair(vec[i].first,sum[i-1].second+vec[i].second+vec[i-1].second*(vec[i].first-vec[i-1].first-1)));
for (int i=1;i<=q;i++)
{
int l,r;read(l),read(r);
int ans1=Get(l-1);
int ans2=Get(r);
writewith(ans2-ans1,'\n');
}
return 0;
}
数组版:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof a)
#define int long long
using namespace std;
template<typename T> void read(T &k)
{
k=0;T flag=1;char b=getchar();
while (!isdigit(b)) {flag=(b=='-')?-1:1;b=getchar();}
while (isdigit(b)) {k=k*10+b-48;b=getchar();}
k*=flag;
}
template<typename T> void write(T k) {if (k<0) {putchar('-'),write(-k);return;}if (k>9) write(k/10);putchar(k%10+48);}
template<typename T> void writewith(T k,char c) {write(k);putchar(c);}
const int _SIZE=5e5;
pair<int,int> vec[_SIZE+5],sum[_SIZE+5];//f[i]=j -> pair,sum of vec
int tv=0,ts=0;
priority_queue<pair<int,int> > eq;//edge to go,first is len of edge,second is num of edge
struct EDGE{
int nxt,to,len;
}edge[(_SIZE<<1)+5];
int tot,head[_SIZE+5];
void AddEdge(int x,int y,int len)
{
edge[++tot]=(EDGE){head[x],y,len};
head[x]=tot;
}
int n,m,q,s,c[_SIZE+5];
bool vis[_SIZE+5];
void prework()
{
int cnt[605],totans=1,curk=0;mem(cnt,0);
cnt[c[s]]=1;vis[s]=1;
queue<int> q;
q.push(s);
vec[++tv]=make_pair(0,1);
while (!eq.empty() || !q.empty())
{
if (!q.empty())
{
int x=q.front();q.pop();
for (int i=head[x];i;i=edge[i].nxt)
{
if (vis[edge[i].to]) continue;
eq.push(make_pair(-edge[i].len,i));
}
}
if (!eq.empty())
{
int curedge=eq.top().second;
eq.pop();
curk=max(curk,edge[curedge].len);
if (!cnt[c[edge[curedge].to]]) totans++;
cnt[c[edge[curedge].to]]=1;
vec[++tv]=make_pair(curk,totans);
if (!vis[edge[curedge].to])
q.push(edge[curedge].to),vis[edge[curedge].to]=1;
}
}
}
int Get(int x)
{
int l=1,r=ts;
while (l<=r)
{
int mid=l+r>>1;
if (sum[mid].first<=x) l=mid+1;
else r=mid-1;
}
return sum[r].second+(x-sum[r].first)*vec[r].second;
}
signed main()
{
read(n),read(m),read(q),read(s);
for (int i=1;i<=n;i++) read(c[i]);
for (int i=1;i<=m;i++)
{
int u,v,c;read(u),read(v),read(c);
AddEdge(u,v,c);AddEdge(v,u,c);
}
prework();
sum[++ts]=vec[1];
for (int i=2;i<=tv;i++) sum[++ts]=make_pair(vec[i].first,sum[i-1].second+vec[i].second+vec[i-1].second*(vec[i].first-vec[i-1].first-1));
for (int i=1;i<=q;i++)
{
int l,r;read(l),read(r);
int ans1=Get(l-1);
int ans2=Get(r);
writewith(ans2-ans1,'\n');
}
return 0;
}
代码实现的时候遇到的一些问题: 在预处理的时候需要取出 unsigned int
类型,直接溢出飞到
又在想该放到什么标签下面了……
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步