暑假模拟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) $
有神奇的树状数组和不确定复杂度的二分做法,这里不展开。