题解----牛半仙的妹子图

前言:

  来源于NOIP模拟27。
  考场上想出正解思路了,但是差个优化,所以没A。

基本思路:

  考虑牛半仙能不能到达一个妹子处取决于路径上最困难的那条路的值。
  又由于他会到达所有能到达的妹子,所以一定会选最大值最小的路径。
  考虑最小生成树,我用的kruscal。
  从x开始dfs记录到达每个妹子经过的最大的路径值。
  然后根据这个排序。
  从小到大统计妹子中数,那么就可以在排序后的序列上二分,对于给定权值找到可以去到的妹子的种数。
  朴素做法是枚举\(l\)\(r\)的权然后一个个二分。
  会\(T\)
  战神给了个优化:

\[sum[i]=sum[i-1]+(val_{i}-val_{i-1})*num[i-1] \]

  前缀优化。
  对于权值在\([val_{i-1},val_{i})\)的二分结果,一定是\(num[i-1]\),num是妹子种数。
  那么对这些结果前缀。
  记\(l\)二分的结果(下标)为\(x\)\(r\)的为\(y\),答案就是

\[sum[y]+num[y]*(r-val_{i})-(sum[x]+num[x]*(l-val_{x})) \]

  将\(O(n)\)优化为了\(O(1)\)
  代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
	#define rr register 
	#define inf INT_MAX
	typedef long long ll;
	const int N=5e5+4;
	const int M=5e5+4;
	int n,m,q,root,opt;
	int mod;
	ll num[N],sum[N];
	bool vis[604];
	struct edge
	{
		int u,v,w;
		bool operator<(const edge b_) const
		{
			return w<b_.w;
		}
	}a[M];
	int to[N<<1],dire[N<<1],w[N<<1],head[N];
	struct girls
	{
		int c,maxn;
		bool operator<(const girls b_) const
		{
			return maxn<b_.maxn;
		}
	}g[N];
	inline void add(int f,int t,int val)
	{
		static int num1=0;
		to[++num1]=t;
		dire[num1]=head[f];
		head[f]=num1;
		w[num1]=val;
	}
	int read()
	{
		rr int x_read=0,y_read=1;
		rr char c_read=getchar();
		while(c_read<'0'||c_read>'9')
		{
			if(c_read=='-') y_read=-1;
			c_read=getchar();
		}
		while(c_read<='9'&&c_read>='0')
		{
			x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
			c_read=getchar();
		}
		return x_read*y_read;
	}
	int f[N];
	int find(int x)
	{
		if(f[x]==x) return x;
		return f[x]=find(f[x]);
	}
	void join(int x,int y)
	{
		x=find(x),y=find(y);
		if(x!=y)
			f[y]=x;
	}
	void kruscal()
	{
		for(rr int i=1;i<=n;i++)
			f[i]=i;
		sort(a+1,a+1+m);
		int t=0;
		for(rr int i=1;i<=m;i++)
		{
			int x=a[i].u,y=a[i].v;
			if(find(x)!=find(y))
			{
				join(x,y);
				t++;
				add(x,y,a[i].w),add(y,x,a[i].w);
				if(t==n-1) break;
			}
		}
	}
	void dfs(int x,int f,int ma)
	{
		g[x].maxn=ma;
		for(rr int i=head[x];i;i=dire[i])
		{
			if(to[i]==f) continue;
			dfs(to[i],x,max(w[i],ma));
		}
	}
	int find_pos(int val)
	{
		int l=1,r=n;
		while(l<r)
		{
			int mid=(l+r+1)>>1;
			if(g[mid].maxn<=val) l=mid;
			else r=mid-1;
		}
		return l;
	}
};
using namespace STD;
int main()
{
	n=read(),m=read(),q=read(),root=read(),opt=read();
	if(opt) mod=read();
	for(rr int i=1;i<=n;i++)
		g[i].c=read();
	for(rr int i=1;i<=m;i++)
	{
		a[i].u=read();
		a[i].v=read();
		a[i].w=read();
	}
	kruscal();
	dfs(root,root,0);
	sort(g+1,g+1+n);
	for(rr int i=1;i<=n;i++)
	{
		if(!vis[g[i].c])
		{
			num[i]=num[i-1]+1;
			vis[g[i].c]=1;
		}
		else num[i]=num[i-1];
	}
	//有意思的优化:
	for(rr int i=1;i<=n;i++)
		sum[i]=sum[i-1]+(g[i].maxn-g[i-1].maxn)*num[i-1];
	ll lastans=0;
	while(q--)
	{
		int l=read(),r=read();
		if(opt)
		{
			l=(l^lastans)%mod+1;
			r=(r^lastans)%mod+1;
			if(l>r) swap(l,r);
		}
		ll ans=0;
		int x=find_pos(l),y=find_pos(r);
		ans=sum[y]+num[y]*(r-g[y].maxn+1);
		ans-=(sum[x]+num[x]*(l-g[x].maxn));
		printf("%lld\n",ans);
		lastans=ans;
	}
}

一些思考:

  可以发现,战神的做法是将结果预处理出来了。
  因为答案是严格的区间内一致的。
  并且区间是已经确定已知的。
  所以是可以预处理的。
  如果不预处理,即使枚举区间也会\(T\)
  而且不好操作。
  这的确是一种很明智的做法。

posted @ 2021-07-29 19:59  Geek_kay  阅读(82)  评论(0编辑  收藏  举报