2021, 9,26 模拟赛
前言
score:
\(80 + 0 + 10 = 90\)
题目很好,没啥好说的。
T1 珠江夜游 (cruise)
题目描述:
小 Z 决定搭乘游艇从西边的广州港沿着珠江夜游到小蛮腰脚下。小 Z 的游艇一路向东,可小 Z 却感觉船动得出奇的慢,一问船家才知道,原来今天珠江上堵船了。
我们可以把供游艇航行的航道看作一条单行道,航道上 \(N+1\) 艘游艇自西向东依次编号为 \(0\dots N\),小 Z 所在的游艇在最西边编号为 \(0\),而编号为 \(N\) 的游艇还要再往东航行一段才是小蛮腰。
由于晚上航行视野不佳,排在后面的船不允许超越前面的船,而当前面的船航行速度太慢时,后面的船只能以相同的速度紧跟着,此时两船之间的距离可以忽略。
已知第 \(i\) 艘游艇船身长为 \(L[i]\),船头与小蛮腰距离为 \(X[i]\),最大航行速度为 \(V[i]\)。
小 Z 好奇,他到底要等多久,才能乘着游艇经过小蛮腰脚下呢?
\(N\leq 100000\)
注意:
题目有些不明确
-
答案求得是最后一条船船头到达终点的时间。
-
一个船到达终点后会继续向前走,不会消失。
solution1: \(O(nlogn)\)
直接二分到达的时间,然后判断每条船是否能够到达就好了。
怎么判断?
计算出每条船在二分出的时间下,离终点的距离,然后判断这个距离是否大于 \(1e-3\) 即可。
具体实现:
直接从最前面的一条船一直推到最后一条船,因为一条船要么是自己到达,要么是和前面的一堆船一起到达,两个时间取个最大值就好了,如果自己到达很显然就是\(x[i] - tim * v[i]\), 如果是和前面的一堆船一起到达就是 \(tmp[i + 1] + l[i + 1]\)。
code
/*
work by:Ariel_
Sorce:模拟赛
Knowledge:二分
Time:O(nlogx)
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int MAXN = 1e5 + 5;
const double eps = 1e-10;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int T, n;
double L[MAXN], X[MAXN], V[MAXN], tmp[MAXN];
bool check(double mid) {
for (int i = 0; i <= n; i++) tmp[i] = X[i];
tmp[n] = tmp[n] - V[n] * mid;
if(tmp[n] > eps) return false;
for (int i = n - 1; i >= 0; i--) {
tmp[i] = max(tmp[i] - mid * V[i], tmp[i + 1] + L[i + 1]);
if(tmp[i] > eps) return false;
}
return true;
}
signed main(){
//freopen("cruise.in", "r", stdin);
//freopen("cruise.out", "w", stdout);
T = read();
while(T--) {
n = read();
for (int i = 0; i <= n; i++) scanf("%lf", &L[i]);
for (int i = 0; i <= n; i++) scanf("%lf", &X[i]);
for (int i = 0; i <= n; i++) scanf("%lf", &V[i]);
double l = 0, r = 1e9;
while(r - l > eps) {
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
printf("%.10lf\n", r);
}
puts("");
return 0;
}
solution2: \(O(n)\)
发现被堵住的一堆船到达时间只和最前面的一条船有关系,所有就枚举所有的船当做第一头,然后计算出到达的时间,所有结果取个最大值就好了。
计算的时候还要加上中间的船长。
code
/*
work by:Ariel_
Sorce:
Knowledge:
Time:O(n)
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 1e5 + 5;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int L[MAXN], X[MAXN], V[MAXN], n, T;
double Ans;
int main(){
T = read();
while(T--) {
n = read();
for (int i = 0; i <= n; i++) L[i] = read();
for (int i = 0; i <= n; i++) X[i] = read();
for (int i = 0; i <= n; i++) V[i] = read();
for (int i = 2; i <= n; i++) L[i] = L[i] + L[i - 1];
Ans = 1.0 * X[0] / V[0];
for (int i = 1; i <= n; i++) Ans = max(Ans, 1.0 * (X[i] + L[i]) / V[i]);
printf("%.10lf", Ans);
puts("");
}
return 0;
}
T2 旅行计划 (Travelling)
题目描述
小 Z 事先准备了一份地图,地图上给出了 \(N\) 个小 Z 心仪的城市,依次编号 \(1\dots N\),以及 \(M\) 条连接两个城市的路,编号 \(1\dots M\)
小 Z 打算把 \(M\) 条路都走一遍且 仅一遍,但他发现这样的路线可能是不存在的。于是他打算,当他走到一个城 市后发现从这个城市出发的道路他都已经走过了,他便会坐飞机到另一个城市, 然后继续他的旅行。 现在小 Z 想知道,在最好的路线计划下,他至少要坐多少趟飞机。
\(n\leq 10^{5}, m \leq 10^5\)
solution
图不连通。
联通块的点数为 \(1\) 显然是没有贡献的。
可以把坐飞机看做连边。
每两个奇度点连边就相当于消掉了这两个奇度点。
把所有的奇度点都消掉之后,跑一遍欧拉回路就好了。
注意跑欧拉回路的时候要从奇度点开始跑,每次到达一个新建的边相当于坐一次飞机,跑完一个连通图坐飞机的次数要 -1,因为每次航行是欧拉路经,而现在求得是欧拉回路。
最后还要把剩下的联通块(联通块内所有点度数为偶数)跑完。
code
/*
work by:Ariel_
Sorce:模拟赛
Knowledge:欧拉图
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <algorithm>
#define ll long long
#define rg register
using namespace std;
const int MAXN = 2e5 + 5, MAXM = MAXN;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int T, n, m, ind[MAXN], tmp, tot;
vector<int>Ans[MAXN];
struct edge{int v, nxt, id, fag;}e[MAXM << 1];
int E = 1, head[MAXN];
void add_edge(int u, int v, int id) {
e[++E] = (edge){v, head[u], id, 0};
head[u] = E;
}
bool vis[MAXN];
void clear() {
memset(vis, 0, sizeof vis);
memset(head, 0, sizeof head);
memset(ind, 0, sizeof ind);
for(int i = 1; i <= tot; i++) Ans[i].clear();
E = 1, tmp = 0, tot = 0;
}
void dfs(int x) {
vis[x] = 1;
for (int i = head[x]; i; i = e[i].nxt) {
int v = e[i].v;
if(!e[i].fag) {
e[i].fag = e[i ^ 1].fag = 1;
dfs(v);
if(e[i].id) Ans[tot].push_back(-e[i].id);
else tot++;
}
}
}
void pre() {
for (int i = 1, u, v; i <= m; i++) {
u = read(), v = read();
add_edge(u, v, i), add_edge(v, u, -i);
ind[u]++, ind[v]++;
}
for(int i = 1; i <= n; i++) {
if(ind[i] & 1) {
if(!tmp) {tmp = i; continue;}
else {
add_edge(i, tmp, 0), add_edge(tmp, i, 0);
tmp = 0;
}
}
}
}
int main(){
//freopen("travelling.in", "r", stdin);
//freopen("travelling.out", "w", stdout);
T = read();
while(T--) {
clear();
n = read(), m = read();
pre();
for (int i = 1; i <= n; i++) {
if(!vis[i] && (ind[i] & 1)) {
tot++, dfs(i), tot--;
}
}
for (int i = 1; i <= n; i++)
if(!vis[i] && ind[i]) tot++, dfs(i);
printf("%d\n", tot - 1);
for (int i = 1; i <= tot; i++) {
printf("%d ", Ans[i].size());
for (int j = 0; j < Ans[i].size(); j++) cout<<Ans[i][j]<<" ";
puts("");
}
}
puts("");
return 0;
}
T3 基站建设 (station)
题目描述
C 城的城市规划做得非常好,整个城市被规整地划分为 8 行 8 列共 64 个街区,现在已知新基站需要建设在哪些街区,用字符“#”表示,而不需要建设基 站的街区用“.”表示。
爸爸告诉小 Z 说,建设基站最耗时的是基站两两之间互相通信的调试,每建设一个新的基站,需要确保其与其他已经建好的基站之间能互相通信,若两个基站的坐标分别为 \((x_1,y_1)\) 和\((x_2,y_2)\),则调试所需时间大概为 \(\max(|x1- x2|,|y1-y2|)\),而一个基站的总调试时间为与其他已经建好的基站的调试时间中的最大值。
现在爸爸想考考小 Z,看小 Z 能否计算出如何设计建设基站的顺序,使得总的调试时间尽量少?
\(n\leq 64\)
30pts
\(n!\) 爆搜
60pts
状压
\(f[S]\) 表示选点的集合为 \(S\) 的最小代价。
void dp() {
memset(f, 0x3f, sizeof f);
f[0] = 0;
for (int S = 0; S < (1 << tot); S++) {
for (int i = 1; i <= tot; i++) {
if(!(S & (1 << (i - 1)))) {
int tmp = 0;
for (int j = 1; j <= tot; j++) if(S & ((1 << (j - 1))))
tmp = max(tmp, dis(i, j));
f[S | (1 << (i - 1))] = min(f[S | (1 << (i - 1))], f[S] + tmp);
}
}
}
printf("%lld", f[(1 << tot) - 1]);
}
100pts
用 \(f[i][j][k][l]\) 来表示当前矩阵左上角为 \(i,j\) 右下角为 \(k\),l的矩阵的最优解.
可以先从最小的一行来看,我们不会分着去取,以为那样的贡献会非常大,然后矩阵上也一样.
我们一定是取一个矩阵,然后不断地加一行或者一列,然后我们枚举四边加一行或一列需要的
贡献,然后取一个最小值,然后加上去,这样 \(dfs\).
/*
work by:Ariel_
Sorce:
Knowledge:
Time:
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int MAXN = 20;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
int f[MAXN][MAXN][MAXN][MAXN];
char s[MAXN][MAXN];
int calc(int x1, int x2, int y1, int y2, int xx1, int xx2, int yy1, int yy2) {
int mnx = 1e9, mxx = 0, mny = 1e9, mxy = 0;
for (int i = xx1; i <= xx2; i++) {
for (int j = yy1; j <= yy2; j++) {
if(s[i][j] == '#') {
mnx = min(mnx, i);
mxx = max(mxx, i);
mny = min(mny, j);
mxy = max(mxy, j);
}
}
}
if(mxx == 0) return 0;
int ans = 0;
for (int i = x1; i <= x2; i++)
for (int j = y1; j <= y2; j++)
if(s[i][j] == '#')
ans += max(abs(abs(i - mnx)), max(abs(i - mxx), max(abs(j - mny), abs(j - mxy))));
return ans;
}
int dfs(int x1, int y1, int x2, int y2) {
if (x1 > x2 || y1 > y2) return 0;
if (f[x1][y1][x2][y2] != -1) return f[x1][y1][x2][y2];
int ret = dfs(x1 + 1, y1, x2, y2) + calc(x1, x1, y1, y2, x1, x2, y1, y2);
ret = min(ret, dfs(x1, y1 + 1, x2, y2) + calc(x1, x2, y1, y1, x1, x2, y1, y2));
ret = min(ret, dfs(x1, y1, x2 - 1, y2) + calc(x2, x2, y1, y2, x1, x2, y1, y2));
ret = min(ret, dfs(x1, y1, x2, y2 - 1) + calc(x1, x2, y2, y2, x1, x2, y1, y2));
return f[x1][y1][x2][y2] = ret;
}
signed main(){
for (int i = 1; i <= 8; i++) scanf("%s", s[i] + 1);
memset(f, -1, sizeof f);
cout << dfs(1, 1, 8, 8);
puts("");
return 0;
}