暑假模拟19

暑假模拟19

\(T_A\) 数字三角形

简单模拟,做法众多。

多数人都是紧贴一边填充,但我是每次每个数只填充一位,每次有一个数填够,刚好填满。

\(T_B\) 那一天她离我而去

找最小环模板题。

做法一

神奇的二进制分组。考虑与1相连的所有节点,一个是起点,一个是终点。一个暴力想法就是,枚举起点,断开这条边,跑 dijkstra,复杂度 $ O( n^2 \ \log n ) $ 。优化,枚举二进制位数,按二进制位分成两组,建立超级源点,一组与超级源点连边,并断开与1相邻的边,因为不同整数在二进制下至少有一位不同,所以任取一对节点,一定会被分到不同组,正确性显然,复杂度 $ O( n\ \log^2 n ) $

CODE
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+100;
const int inf=1e8;
int T,n,m,a,b,c,cnt,head[N],d[N];
struct edge{
	int to,nxt,val;
}e[N<<5];
vector<int>son,sec;
int dis[N],ans;
bool vis[N<<5];
inline void add_edge(int u,int v,int w){
	cnt++;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	e[cnt].val=w;
	head[u]=cnt;
}
struct node{
	int id,dis;
	bool operator<(node x)const{
		return dis>x.dis;
	}
};
priority_queue<node>q;
void dijkstra(){
	q.push({n+1,0});
	dis[n+1]=0;
	while(!q.empty()){
		node x=q.top();
		q.pop();
		for(int i=head[x.id];i;i=e[i].nxt){
			if(vis[i])continue;
			int v=e[i].to;
			if(dis[v]>dis[x.id]+e[i].val){
				dis[v]=dis[x.id]+e[i].val;
				q.push({v,dis[v]});
			}
		}
	}
}
int main()
{
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		ans=inf;
		memset(d,0x3f,sizeof(d));
		son.clear();
		cnt=0;
		memset(head,0,sizeof(head));
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&a,&b,&c);
			add_edge(a,b,c);
			add_edge(b,a,c);
			if(a==1){
				d[b]=min(d[b],c);
				vis[cnt]=1;
				vis[cnt-1]=1;
				son.push_back(b);
			}
			if(b==1){
				vis[cnt]=1;
				vis[cnt-1]=1;
				d[a]=min(d[a],c);
				son.push_back(a);
			}
		}
		for(int i=0;i<=15;i++){
			int num=cnt;
			for(auto j:son){
				if(j&(1<<i)){
					add_edge(n+1,j,d[j]);
				}
				else {
					sec.push_back(j);
				}
			}
			memset(dis,0x3f,sizeof(dis));
			dijkstra();
			for(auto j:sec){
				ans=min(ans,dis[j]+d[j]);
			}
			sec.clear();
			for(int j=num+1;j<=cnt;j++){
				vis[j]=1;
			}
		}
		if(ans<inf)printf("%d\n",ans);
		else puts("-1");
	}
}

做法二

使用贪心的思想。先预处理跑 dijkstra ,并处理出最短路树,记录在树上每个节点属于哪个1的直接子节点的子树。枚举每条边,如果边连接的两点不在同一子树,那么它们就可能是最小环的一部分,统计答案。复杂度 $ O( n \ \log n ) $

CODE
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+100;
const int inf=1e8;
int T,n,m,a,b,c,cnt,head[N],d[N],fa[N],root[N],fir[N],sec[N],wei[N];
struct edge{
	int to,nxt,val;
}e[N<<5];
vector<int>son[N];
int dis[N],ans;
bool vis[N<<5];
inline void add_edge(int u,int v,int w){
	cnt++;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	e[cnt].val=w;
	head[u]=cnt;
}
struct node{
	int id,dis;
	bool operator<(node x)const{
		return dis>x.dis;
	}
};
priority_queue<node>q;
void dijkstra(){
	q.push({1,0});
	dis[1]=0;
	while(!q.empty()){
		node x=q.top();
		q.pop();
		for(int i=head[x.id];i;i=e[i].nxt){
			int v=e[i].to;
			if(dis[v]>dis[x.id]+e[i].val){
				fa[v]=x.id;
				dis[v]=dis[x.id]+e[i].val;
				q.push({v,dis[v]});
			}
		}
	}
}
void init(){
	cnt=0;
	ans=inf;
	memset(head,0,sizeof(head));
	memset(fa,0,sizeof(fa));
	memset(dis,0x3f,sizeof(dis));
	memset(root,0,sizeof(root));
	for(int i=1;i<=n;i++){
		son[i].clear();
	}
}
void dfs(int rt,int u){
	root[u]=rt;
	for(auto v:son[u]){
		dfs(rt,v);
	}
}
int main()
{
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		init();
		for(int i=1;i<=m;i++){
			scanf("%d%d%d",&a,&b,&c);
			fir[i]=a;
			sec[i]=b;
			wei[i]=c;
			add_edge(a,b,c);
			add_edge(b,a,c);
		}
		dijkstra();
		for(int i=2;i<=n;i++){
			son[fa[i]].push_back(i);
		}
		for(auto i:son[1]){
			dfs(i,i);
		}
		for(int i=1;i<=m;i++){
			if(root[fir[i]]!=root[sec[i]]&&fa[fir[i]]!=sec[i]&&fa[sec[i]]!=fir[i]){
				ans=min(ans,dis[fir[i]]+dis[sec[i]]+wei[i]);
			}
		}
		if(ans<inf)printf("%d\n",ans);
		else puts("-1");
	}
}

\(T_C\) 哪一天她能重回我身边

很容易想到建双向边,并记录这条边初始时的方向,那么图会被分为若干个连通块,分开处理。认为边的起点是卡牌朝上的那面,要想合法,当且仅当每个点的入度不超过1。设该连通块中 \(n\) 为点数, \(m\) 为边数。若 $ m>n $ ,显然无解;若 $ m=n $ ,是个基环树,环上节点一定首尾相连,只有两种情况;若 $ m=n+1 $ ,是棵树,一个节点入度为 \(0\) ,其余入度为 \(1\) ,考虑哪个节点入度是 \(0\) ,换根DP做即可。

\(T_D\) 单调区间

DP做法。

设 $ dp[i][0] $ 为第 \(i\) 位属于递增区间,递减区间末尾元素的最大值, $ dp[i][1] $ 同理。

考虑暴力,枚举左端点,转移显然,复杂度 $ O( n^2 ) $

神奇优化。考虑一个神奇性质: $ dp[i][0/1] $ 的取值只有 \(4\) 个。

证明:
以 $ dp[i][0] $ 为例。对于第 \(i\) 位最大化一个 $ j<i $ 使得 $ p_j>p_{ j+1 } $ ,那么 $ p_j $ 和 $ p_{ j+1 } $ 不能同时在递增序列中,同时根据 $ dp[i][0] $ 的定义,可以得出,可能取值有 $ p_j,p_{ j+1 },-\infty $ ,当不存在这个 $ j $ 时,取值为 $ +\infty $。 $ dp[i][1] $ 同理。

由于可能的取值很少,我们可以记忆化,复杂度做到 $ O(n) $

有神奇的树状数组和不确定复杂度的二分做法,这里不展开。

posted @ 2024-08-14 07:18  Abnormal123  阅读(10)  评论(1编辑  收藏  举报