day 9 模拟赛 5
模拟赛 5
T1 图染色
给定一张 n 个点 m 条边的无向图,你需要为每个顶点染色,使得:
- 对于任意一种颜色,不存在只由它构成的环;
- 对于任意两种在图中出现过的颜色,存在只由这两种颜色构成的环。
你可以只使用一种颜色,而且染上同一种颜色的所有节点不需要联通。
要求线性。
首先不存在环时,全都染一种颜色即可。-------------------------------20分
先考虑一种绝对正确的染色方法:
任意顺序枚举所有点,一个点不可以染成颜色 x 当且仅当这个点是一个环上的一点,且这个环的其它点的颜色都是 x。这样如果染色 x 就违反了第一个条件。
尝试把这个点染上颜色 1,2,3,...,直到合法为止,如果没有颜色可以,就要新增一个颜色。
新增的颜色为何满足条件 2?因为我们增加一个颜色的时候,必须满足这个点在之前所有颜色的环中,所有对于新颜色和任意的旧颜色,刚好有一个环。-------------------------------40分
考虑如何实现,我们用一个并查集,维护不同颜色的所有连通块,那么一个点所在环的其他颜色颜色相同,等价于这个点与一个颜色的连通块有两条边相连,每当决定一个点的颜色后,遍历这个点的所有出边,合并并查集即可。忽略并查集复杂度,复杂度 \(O(n+m)\)。-------------------------------100分
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int t,n,m;
vector<int>g[N];
int c[N];
map<pair<int,int>,int>cnt;
bool c_ok[N];
vector<int>fa[N],sz[N];
int find(int col,int x){
if(fa[col][x]==x)return x;
return fa[col][x]=find(col,fa[col][x]);
}
void merge(int col,int x,int y){
int rx=find(col,x),ry=find(col,y);
if(rx==ry)return;
if(sz[col][rx]<sz[col][ry])swap(rx,ry);
fa[col][ry]=rx;
sz[col][rx]+=sz[col][ry];
}
void init_color(int col){
fa[col].resize(n+1);
sz[col].resize(n+1);
for(int i=1;i<=n;i++){
fa[col][i]=i;
sz[col][i]=1;
}
}
int main(){
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
cin>>t;
while(t--){
cin>>n>>m;
for(int i=1;i<=n;i++){
g[i].clear();
c[i]=0;
fa[i].clear();
sz[i].clear();
}
cnt.clear();
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
int co_cnt=0;
for(int u=1;u<=n;u++){
for(int i=1;i<=co_cnt;i++)c_ok[i]=1;
vector<pair<int,int>>added;
for(int v:g[u]){
if(c[v]==0)continue;
int rt=find(c[v],v);
pair<int,int>p={c[v],rt};
if(!cnt[p])cnt[p]=0;
cnt[p]++;
added.push_back(p);
if(cnt[p]>=2)c_ok[c[v]]=0;
}
int choose=0;
for(int i=1;i<=co_cnt;i++){
if(c_ok[i]){
choose=i;
break;
}
}
if(!choose){
co_cnt++;
init_color(co_cnt);
c[u]=co_cnt;
}else{
c[u]=choose;
for(int v:g[u]){
if(c[v]==choose){
merge(choose,u,v);
}
}
}
for(auto p:added)cnt[p]--;
}
cout<<co_cnt<<"\n";
for(int i=1;i<=n;i++)cout<<c[i]<<" ";
cout<<"\n";
}
return 0;
}
T2 林波舞
有 n 根横杆,第 i 根横杆一开始的高度为 \(a_i\) 单位,移速为每秒 \(v_i\) 单位(正数代表向上,负数代表向下)。杆子的活动范围为 0~\(10^9\) 单位,当它尝试向这个范围之外移动时会停止不动。
你决定将这些横杆按照某种顺序依次摆放在离起点 1~n 单位处,让表演者依次穿过这些横杆,必须匀速。
找出一个尽可能小的数 h 使得:存在一种摆放方式,使得不论表演者选择怎样的前进速度,他穿过横杆时对应横杆高度的最小值都不大于 h。\(n\le10^5\)
不难想到二分答案,考虑如何 check。
部分分给了一些提示:当 \(v_i\ge 0\) 时,显然舞者越慢越好,对于 \(v_i>0\) 的柱子舞者都可以等无限长时间等过去,答案即为所有 \(v_i=0\) 柱子的高度最小值。-------------------------------5分
当 \(v_i<0\) 时,显然舞者越快越好,所有她在一瞬间移动过去,答案为所有柱子初始高度的最小值。-------------------------------25分
假设我们知道一个柱子的位置,以及高度的最小值 h,那么这可以将舞者的速度锁定在一个范围中。
- 当 \(v>0\) 时,舞者移动的越慢,走到那里时柱子的高度越高,所以能确定一个速度的最大值。而且把柱子放到位置 1 有机会让舞者通过时高度最低。由 \(a+vt=h\),可得 \(t=\frac{h-a}{v}\),由 \(v_{r}=\frac{s}{t}\) 得 \(v_r=\frac{v}{h-a}\)。
- 当 \(v<0\) 时,舞者移动的越快,走到那里时柱子的高度越高,所以能确定一个速度的最小值。而且把柱子放到位置 n 有机会让舞者通过时高度最低。由 \(a-|v|t=h\),可得 \(t=\frac{h-a}{|v|}\),即 \(t=\frac{h-a}{-v}\),由 \(v_{l}=\frac{s}{t}\) 得 \(v_l=\frac{-vn}{a-h}\)。特殊地,如果一个向下移动的杆子的初始高度就小于 h,那么舞者是不可能挑出速度的,直接合法。
- 当 \(v=0\) 时,如果高度 \(a<h\),那么舞者也是不可能挑出速度的,直接合法。
把所有柱子都放到 1/n 试一次,可以求得范围 \(maxl<v'<maxr\)(\(v'\) 是舞者的速度),如果 \(maxl\ge maxr\),覆盖所有实数,说明舞者不能选出一个 \(v'\),让最低高度大于 h。
注意二分的精度问题,时间复杂度 \(O(n\log V)\)。-------------------------------100分
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int t,n;
int a[N],v[N];
bool check(double h){
double maxl=0,minr=1e18;
for(int i=1;i<=n;i++){
if(v[i]>0){
if(h>a[i])minr=min(minr,(double)v[i]/(h-a[i]));
}else if(v[i]==0){
if(a[i]<=h)return 1;
}else{
if(a[i]>h)maxl=max(maxl,(double)(-v[i])*n/(a[i]-h));
else return 1;
}
}
if(maxl>=minr)return 1;
return 0;
}
int main(){
freopen("limbo.in","r",stdin);
freopen("limbo.out","w",stdout);
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i]>>v[i];
double l=0,r=1e9;
for(int i=1;i<=200;i++){
double mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid;
}
printf("%.10f\n",l);
}
return 0;
}
浙公网安备 33010602011771号