Baltic Olympiad in Informatics 2016

Bosses Park Spiral Cities Maze Swap
$\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{red}{\texttt{-}}$ $\color{red}{\texttt{-}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$

链接 题面链接 中文题面

A. Bosses

可以发现一个方案的总代价是所有点的深度之和。考虑枚举根,贪心的选一定让每个点往深度最小的点上挂。

所以连反边 bfs 一遍即可。复杂度 $O(n+m)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define N 5010
#define ll long long
using namespace std;
int dis[N],n;
vector<int>g[N];
ll ans=-1;
void bfs(int s)
{
for(int i=1;i<=n;i++) dis[i]=0;
dis[s]=1;queue<int>q;q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int v:g[u]) if(!dis[v]) dis[v]=dis[u]+1,q.push(v);
}
for(int i=1;i<=n;i++) if(!dis[i]) return;
ll res=0;
for(int i=1;i<=n;i++) res+=dis[i];
if(ans==-1 || ans>res) ans=res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int k,x;scanf("%d",&k);
while(k --> 0) scanf("%d",&x),g[x].push_back(i);
}
for(int i=1;i<=n;i++) bfs(i);
printf("%lld\n",ans);
return 0;
}

B. Park

定义两个圆之间的边为它们的距离(圆心距离减去半径之和)认为四个边界是四个特殊的圆。那么要使 $1,2$ 不连通,需要存在从 $1,2$ 的边界到其他边界只通过 $\leq 2r$ 的边的路径。换句话说就是把 $1,2$ 割开了。

其他同理。可以发现这就是最小瓶颈路,直接暴力枚举每对边界,暴力求瓶颈路,就可以做到 $O(1)$ 查询。

复杂度 $O(16n^2+n^2\log n+q)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 2010
#define db double
#define inf 1e18
#define eps 1e-9
using namespace std;
int X,Y;
struct node{
int x,y;
node(int x=0,int y=0):x(x),y(y){}
node operator +(node a){return node(x+a.x,y+a.y);}
node operator -(node a){return node(x-a.x,y-a.y);}
}p[N];int r[N];
db dis(node a){return sqrt(1ll*a.x*a.x+1ll*a.y*a.y);}
db dis(int x,int y){return dis(p[x]-p[y])-r[x]-r[y];}
/*
4
---
| |
1 | | 3
| |
---
2
*/
db dis_edge(int a,int op)
{
if(op==1) return p[a].x-r[a];
if(op==2) return p[a].y-r[a];
if(op==3) return X-p[a].x-r[a];
if(op==4) return Y-p[a].y-r[a];
return inf;
}
int f[N],n;
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
struct road{
int x,y;db w;
}e[N*N];int et;
db get_dis(int r1,int r2)
{
for(int i=1;i<=n+4;i++) f[i]=i;
for(int i=1;i<=et;i++)
{
int x=find(e[i].x),y=find(e[i].y);
if(x!=y) f[x]=y;
if(find(r1)==find(r2)) return e[i].w;
}
return inf;
}
db w[5][5],q[5][5];
int main()
{
int m;scanf("%d%d%d%d",&n,&m,&X,&Y);
for(int i=1;i<=n;i++) scanf("%d%d%d",&p[i].x,&p[i].y,&r[i]);
for(int j=1;j<=4;j++)
for(int i=1;i<=n;i++) e[++et]={i,j+n,dis_edge(i,j)};
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++) e[++et]={i,j,dis(i,j)};
sort(e+1,e+et+1,[&](road a,road b){return a.w<b.w;});
for(int i=1;i<=4;i++)
for(int j=i+1;j<=4;j++) w[i][j]=w[j][i]=get_dis(n+i,n+j);
// for(int i=1;i<=4;i++,puts(""))
// for(int j=1;j<=4;j++) printf("%.6lf ",w[i][j]);
/*
4-4-3
| |
1 3
| |
1-2-2
*/
q[1][2]=q[2][1]=min({w[2][4],w[2][3],w[2][1]});
q[2][3]=q[3][2]=min({w[3][4],w[3][1],w[3][2]});
q[3][4]=q[4][3]=min({w[4][1],w[4][2],w[4][3]});
q[4][1]=q[1][4]=min({w[1][2],w[1][3],w[1][4]});
q[1][3]=q[3][1]=min({w[1][3],w[2][4],w[1][2],w[3][4]});
q[2][4]=q[4][2]=min({w[1][3],w[2][4],w[1][4],w[2][3]});
// for(int i=1;i<=4;i++,puts(""))
// for(int j=1;j<=4;j++) printf("%.6lf ",q[i][j]);
for(int i=1;i<=4;i++) q[i][i]=inf;
while(m --> 0)
{
int r,e;scanf("%d%d",&r,&e);
for(int i=1;i<=4;i++) if(q[i][e]+eps>=r*2) printf("%d",i);
puts("");
}
return 0;
}

D. Cities

最小斯坦纳树板子题。

用 dijkstra 增广,复杂度 $O(n2^k\log n+n3^k)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define ll long long
#define N 100010
#define inf 100000000000000000
#pragma GCC optimize(3)
using namespace std;
typedef pair<ll,int> P;
priority_queue<P,vector<P>,greater<P>>q;
bool vis[N];int n;
int nxt[N<<2],to[N<<2],w[N<<2],head[N],cnt;
void add(int u,int v,int w0)
{
nxt[++cnt]=head[u];to[cnt]=v;
w[cnt]=w0;head[u]=cnt;
}
void dij(ll f[])
{
for(int i=1;i<=n;i++) vis[i]=false;
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];i;i=nxt[i])
if(f[to[i]]>f[u]+w[i]) f[to[i]]=f[u]+w[i],q.emplace(f[to[i]],to[i]);
}
}
ll f[33][N];
int main()
{
memset(f,0x3f,sizeof(f));
int m,k;scanf("%d%d%d",&n,&k,&m);
for(int i=0,x;i<k;i++) scanf("%d",&x),f[1<<i][x]=0;
for(int i=1,u,v,w;i<=m;i++) scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
for(int s=1;s<1<<k;s++)
{
for(int u=1;u<=n;u++)
for(int t=(s-1)&s;t;t=(t-1)&s) f[s][u]=min(f[s][u],f[t][u]+f[s^t][u]);
for(int u=1;u<=n;u++) if(f[s][u]<=inf) q.emplace(f[s][u],u);
dij(f[s]);
}
printf("%lld\n",*min_element(f[(1<<k)-1]+1,f[(1<<k)-1]+n));
return 0;
}

F. Swap

题目给的序列可以看成一棵完全二叉树,其中一个节点可以依次和左右儿子交换。

不妨假设根,左儿子,右儿子分别是 $a,b,c$。

  • $a < \min(b,c)$,显然什么都不干。
  • $b< a$,必然只能交换 $a,b$。
  • $c< \min(a,b)$,根必然是 $c$,左右儿子可以是 $a,b$ 也可以是 $b,a$。

问题在于 $a,b$ 优还是 $b,a$ 更优不能直接判断。

不妨假设 $a< b$,如果 $a$ 放到左子树后最后的位置是 $p$,那么 $b$ 放到左子树后 $p$ 位置的值一定会变大。右子树同理。所以只需要看 $a$ 放到左子树后的位置与放到右子树的位置哪一个靠前即可。

所以假设 $a$ 被放入左子树,利用类似的策略可以确定 $a$ 最终位置。于是就可以推出 $a$ 被放入左子树优还是右子树优。

考虑分析时间复杂度:注意到一个事情,就是假设 $v$ 放入 $u$ 时,必然有 $v$ 是 $u$ 的祖先或者祖先的兄弟,换句话说合法 $v$ 取值只有 $2\log n$ 个。直接用 map 记忆化搜索,这样一个点只会被访问 $O(\log n)$ 次。

总复杂度 $O(n\log^2 n)$,并且跑不太满。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define N 400010
using namespace std;
int p[N],n;
map<pair<int,int>,int>mp;
int find(int u,int v)//if p_u becomes v
{
if(u>n) return 0;
if(mp.count({u,v})) return mp[{u,v}];
int &b=p[u<<1],&c=p[u<<1|1],&w=mp[{u,v}];
if(v<min(b,c)) return w=0;
if(b<c) return w=find(u<<1,v)+1;
else if(b<v)
{
if(find(u<<1,b)>find(u<<1|1,b)) return w=find(u<<1,v)+1;
else return w=find(u<<1|1,v)+1;
}
else return w=min(find(u<<1,v),find(u<<1|1,v))+1;
}
void solve(int u)
{
if(u>n) return;
int &a=p[u],&b=p[u<<1],&c=p[u<<1|1];
if(a<min(b,c)){solve(u<<1);solve(u<<1|1);return;}
if(b<c){swap(a,b);solve(u<<1);solve(u<<1|1);return;}
// printf("%d %d: %d, ",u<<1,min(a,b),find(u<<1,min(a,b)));
// printf("%d %d: %d\n",u<<1|1,min(a,b),find(u<<1|1,min(a,b)));
swap(a,c);if(b>c) swap(b,c);
if(find(u<<1,b)>find(u<<1|1,b)) swap(b,c);
solve(u<<1);solve(u<<1|1);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=n+1;i<=n*2+1;i++) p[i]=n+1;
solve(1);
for(int i=1;i<=n;i++) printf("%d ",p[i]);
return 0;
}