HGOI10.25集训题解

题解

我重回战场!


第一题——列队(line)

【题目描述】

  • 给出 n , m n,m ,表示数的个数和条件数目。条件形式为三元组(A,B,C),表示B在A之后C个数(C可能为负数)
  • 要求求出满足条件的数列是否存在,若不存在输出impossible,否则输出最大数和最小数的最小差值。

  • 这个题和AT3882一样的题目意思,但是这个最大值最小值的差值让我只拿了5分orz
  • 正解是带权值并查集,但是我打的是查分约束系统。
  • 很明显,题目上的三元组条件满足了查分约束的条件,然后正向建立边,反向建立负权边,跑一把SPFA就可以了。

#include <bits/stdc++.h>
using namespace std;
inline void fff(){
	freopen("line.in","r",stdin);
	freopen("line.out","w",stdout);
}
const int N=1e5+10;
const int INF=0x3f3f3f3f;
inline int read(){
	int x=0,m=1;char ch=getchar();
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') ch=getchar(),m=-1;
	while(ch<='9'&&ch>='0') 
		x=x*10+ch-'0',ch=getchar();
	return x*m;
}
int n,m;
struct Edge{
	int nxt;
	int to,w;
}edge[N<<2];
int head[N],tot=0;
inline void add(int u,int v,int w){
	edge[++tot].nxt=head[u];
	edge[tot].to=v;
	edge[tot].w=w;
	head[u]=tot;
}
bool vis[N],visited[N];
int dist[N],ans,minn,maxx;
queue<int> q;
inline bool SPFA(int str){
	q.push(str);
	minn=0,maxx=0;
	dist[str]=0;visited[str]=true;
	while(!q.empty()){
		int u=q.front();q.pop();
		visited[u]=false;
		vis[u]=true;
		for(int i=head[u];i;i=edge[i].nxt){
			Edge e=edge[i];
			if(dist[e.to]<INF){
				if(dist[e.to]!=e.w+dist[u]){
					return false;
				}
				minn=min(minn,dist[e.to]);
				maxx=max(maxx,dist[e.to]);
			}else{
				dist[e.to]=dist[u]+e.w;
				minn=min(minn,dist[e.to]);
				maxx=max(maxx,dist[e.to]);
				if(!visited[e.to]){
					visited[e.to]=true;
					q.push(e.to);
				}
			}
		}
	}
	ans=max(ans,maxx-minn);
	return true;
}
int main(){
//	fff();
	n=read();
	m=read();
	ans=0;
	for(int i=1;i<=m;i++){
		int u,v,w;
		u=read(),v=read(),w=read();
		add(u,v,w);
		add(v,u,-w);
	}
	memset(dist,INF,sizeof(dist));
	for(int i=1;i<=n;i++){
		if(vis[i]) continue;
		if(!SPFA(i)){
			printf("impossible");
			return 0;
		}
	}
	printf("%d",ans);
}

第二题——规避(evade)

【题目描述】

  • 给出一张n点m边的正边权图。给出源点S和汇点T。
  • 现两人从S和T同时出发沿着最短路走,问两人互不相碰的方案是多少
  • 两人互不相碰是指两人不在同一个时间在同一个节点或者在同一条边上的同一个点。
  • 方案数是指两两组合有多少种。

  • 这个题的solution写的非常的辣鸡!我看着题解调了一年!!!
  • 显然,你需要做一遍最短路得知最短路的举例,然后知道最短路的方案数。
  • 然后发现,可能会出现的相碰有两种情况,一种是两种走了同一条路,第二种是两个人走的路当中存在公共的路径或者路径当中存在相同时间能够相遇的节点或者边。
  • 那最终的结果就是最短路的方案数-相遇的次数。
  • 然后就是怎么处理出这个方案。
  • 先走一遍最短路,SPFA和dikstra都可以。
  • 首先要解决判断一条路径是不是最短路就是判断左右两点的距离差是不是路径的权值。
  • 然后显然可以发现当他们相遇的时候当且仅当在 t / 2 \lfloor t/2 \rfloor 的时候在同一条路径中或者一个节点才可以相遇。
  • 继续往下推,你会发现一个节点是在 t / 2 \lfloor t/2 \rfloor 相遇仅当 d i s t [ i ] = t / 2 dist[i]=\lfloor t/2 \rfloor ,方案数就是从源出发到当前节点的方案数 * 从当前节点到汇点的方案数。
  • 在一条边上的情况就是路径保证是最短路的情况下,路径两端的 d i s t [ i ] &lt; t / 2 dist[i]&lt;\lfloor t/2 \rfloor , d i s t [ j ] &gt; t / 2 dist[j]&gt;\lfloor t/2 \rfloor 。然后方案数就是从源到 i i 的方案数 * j j 到汇点的方案数。
  • 那么怎么求出从当前节点的到汇点的方案数和从源点到当前的方案数。由于这是一张图,所以dfs和bfs都是不可行的,那么就按照边进行排序,从小到大(大到小)进行处理。分别记为 f [ i ] f[i] , g [ i ] g[i]
  • 然后由于是两个人的方案数组合,所以最短路方案数和相遇次数都要先进行平方在相减。

#include <bits/stdc++.h>
#include <queue>
#define LL long long
using namespace std;
inline void fff(){
	freopen("evade.in","r",stdin);
	freopen("evade.out","w",stdout);
}
const LL INF=0x3f3f3f3f;
const int N=1e5+10;
const int MOD=1e9+7;
inline LL read(){
	LL x=0,m=1;char ch=getchar();
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') ch=getchar(),m=-1;
	while(ch<='9'&&ch>='0') 
		x=x*10+ch-'0',ch=getchar();
	return x*m;
}
struct Edge{
	LL nxt;
	LL to;
	LL w;
}edge[N<<2];
LL head[N],tot=0;
inline void add(LL u,LL v,LL w){
	edge[++tot].nxt=head[u];
	edge[tot].to=v;
	edge[tot].w=w;
	head[u]=tot;
}
LL n,m,S,T;
queue<LL> q;
LL dist[N],f[N],g[N];
bool visited[N];
inline void SPFA(LL start){
	memset(dist,INF,sizeof(dist));
	dist[start]=0;
	visited[start]=true;
	q.push(start);
	while(!q.empty()){
		LL u=q.front();q.pop();
		visited[u]=false;
		for(int i=head[u];i;i=edge[i].nxt){
			Edge e=edge[i];
			if(dist[e.to]>dist[u]+e.w){
				dist[e.to]=dist[u]+e.w;
				if(!visited[e.to]){
					q.push(e.to);
					visited[e.to]=true;
				}
			}
		}
	}
}
LL mindist;
bool cmp(int a,int b){return dist[a]<dist[b];}
int p[N];
int main(){
//	fff();
	n=read(),m=read();
	S=read(),T=read();
	for(int i=1;i<=m;i++){
		int u,v,w;
		u=read(),v=read(),w=read();
		add(u,v,w);
		add(v,u,w);
	}
	SPFA(S);
	mindist=dist[T];
	g[T]=1;
	f[S]=1;
	for(int i=1;i<=n;i++) p[i]=i;
	sort(p+1,p+n+1,cmp);
	for(int j=1;j<=n;j++){
		int u=p[j];
		for(int i=head[u];i;i=edge[i].nxt)
			if(edge[i].w==dist[edge[i].to]-dist[u])
				f[edge[i].to]=(f[u]+f[edge[i].to])%MOD;
	}
	for(int j=1;j<=n;j++){
		int u=p[n-j+1];
		for(int i=head[u];i;i=edge[i].nxt)
			if(edge[i].w==dist[u]-dist[edge[i].to])
				g[edge[i].to]=(g[u]+g[edge[i].to])%MOD;
	}
	LL sum=0;
	for(int u=1;u<=n;u++){
		for(int i=head[u];i;i=edge[i].nxt){
			if(edge[i].w==abs(dist[edge[i].to]-dist[u])){
				LL tmp=dist[T]-dist[edge[i].to];
				if(tmp<dist[edge[i].to]&&tmp>dist[u]-(dist[edge[i].to]-dist[u])){
					sum=(sum+f[u]*g[edge[i].to]%MOD*f[u]%MOD*g[edge[i].to]%MOD)%MOD;
				}
			}
			
		}
	}
	for(int i=1;i<=n;i++){
		if(dist[i]==dist[T]-dist[i])
			sum=(sum+f[i]*g[i]%MOD*f[i]%MOD*g[i])%MOD;
	}
	printf("%lld",((f[T]*f[T]%MOD-sum)%MOD+MOD)%MOD);
	return 0;
}

第三题——相交(access)

【题目描述】

  • 有一棵 n n 个节点, n 1 n-1 条边的树。树上有 m m 条路径,定义两条路径相交仅当这两条路径经过至少一个相同的点。小D 想知道:从这m 条路径中选择两条相交的路径的方案数。

  • 这个我之前好像有做过类似的题,重新打了一遍算法无脑打错一个字wa得只剩下40分orz
  • 具体做法就不讲了。dfs序+树状数组处理树上差分。
  • 出现两种情况自己考虑!

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define LL long long
using namespace std;
inline void fff(){
	freopen("access.in","r",stdin);
	freopen("access.out","w",stdout);
}
const int N=1e6+10;
inline int read(){
	int x=0,m=1;char ch=getchar();
	while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
	if(ch=='-') ch=getchar(),m=-1;
	while(ch<='9'&&ch>='0') 
		x=x*10+ch-'0',ch=getchar();
	return x*m;
}
struct Edge{
	int nxt;
	int to;
}edge[N<<1];
int head[N],tot=0;
inline void add(int u,int v){
	edge[++tot].nxt=head[u];
	edge[tot].to=v;
	head[u]=tot;
}
int n,m;
int depth[N],fa[N][21],L[N],R[N],dfn,tt[N];
LL s1[N],s2[N],ans;
bool visited[N];
inline int lowbit(int x){return x&(-x);}
inline LL sum(LL *sz,int x){LL ss=0;for(;x>0;x-=lowbit(x)) ss+=sz[x]; return ss;}
inline void change(LL *sz,int x,int val){for(;x<=n;x+=lowbit(x)) sz[x]+=val;}
void dfs(int u){
	visited[u]=true;
	L[u]=++dfn;
	for(int i=head[u];i;i=edge[i].nxt){
		Edge e=edge[i];
		if(!visited[e.to]){
			depth[e.to]=depth[u]+1;
			fa[e.to][0]=u;
			dfs(e.to);
		}
	}
	R[u]=dfn;
}
inline int LCA(int x,int y){
	if(depth[y]>depth[x]) swap(x,y);
	for(int i=20;i>=0;i--){
		if(depth[fa[x][i]]>=depth[y]) 
			x=fa[x][i];
	}
	if(x==y) return x;
	for(int i=20;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	if(fa[x][0]!=fa[y][0]){
		x=fa[x][0];
		y=fa[y][0];
	}
	return fa[x][0];
}
inline void cacul(int u,int v,int lca){
	ans+=sum(s1,R[lca])-sum(s1,L[lca]-1);
	ans+=sum(s2,L[u])+sum(s2,L[v])-sum(s2,L[lca])*2;
}
int main(){
//	fff();
	n=read(),m=read();
	for(int i=1;i<n;i++){
		int u,v;
		u=read(),v=read();
		add(u,v),add(v,u);
	}
	depth[1]=1;
	dfs(1);
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
	for(int i=1;i<=m;i++){
		int u,v;
		u=read(),v=read();
		int g=LCA(u,v);
		cacul(u,v,g);
		ans+=tt[g];
		tt[g]++;
		change(s1,L[u],1);
		change(s1,L[v],1);
		change(s1,L[g],-2);
		change(s2,L[g],1);
		change(s2,R[g]+1,-1);
	}
	cout<<ans;
}

猜你喜欢

转载自blog.csdn.net/qq_42037034/article/details/83383301