暑假模擬19

Abnormal123發表於2024-08-14

暑假模擬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) $

有神奇的樹狀陣列和不確定複雜度的二分做法,這裡不展開。

相關文章