2025-8-28模拟测验
自我评价:还是 Tang 完了。
题解
题解中包含题面描述,但不包含大样例。
T1 偷懒的小X
水题(~
描述
话说3008年的Orz教主节,全民狂欢,传递教主圣火,以致万人空巷,股票飞涨。真乃锣鼓喧天,鞭炮齐鸣,红旗招展,人山人海呐。可是小X为了准备NOIP3008,不得不待在家里好好Coding。小X希望早点结束当天的任务,加入圣火传递队伍中去。
在这个不亚于狂欢节的日子里,小X的老师却“公然违抗”休假法令,布置小X写一个小根堆,但是小X不会堆的操作,所以想了一个偷懒的办法:
堆是一棵完全二叉树,每个结点有一个权。小根堆的根的权最小,且根的两个子树也是一个堆。可以用一个数组 \(a\) 来记录一棵完全二叉树,\(a[1]\) 为根结点,若结点 \(a[j]\) 不是根结点,那么它的父亲为 \(a[j/2]\)(取下整);若结点 \(a[k]\) 不是叶子结点,那么它的左儿子为 \(a[2k]\),它的右儿子为 \(a[2k+1]\)。
他希望一组数据按一定顺序依次插入数组中(即第 \(i\) 个数为 \(a[i]\)),最后得出来就已经是一个堆,即不需要任何交换操作,若有多种方法,输出字典序最大的一组,显得这个数据更乱。
输入
输入的第 \(1\) 行为一个正整数 \(n\),为插入的数字的个数。
第 \(2\) 行包含 \(n\) 个正整数,为所有需要插入的数字,数字之间用空格隔开。
为了简化题目,数据保证 \(n=2^k-1\),即保证最后的堆是一棵满二叉树。
输出
输出包括 \(1\) 行,为插入的序列,数字之间用空格隔开,行末换行并没有空格。
样例
共 \(1\) 组,不包含大样例。
样例 \(1\) 输入
3
10 2 1
样例 \(1\) 输出
1 10 2
数据范围
-
对于 \(20\%\) 的数据,\(n \leq 7\)
-
对于 \(30\%\) 的数据,\(n \leq 15\)
-
对于 \(50\%\) 的数据,\(n \leq 1023\)
-
对于 \(100\%\) 的数据,\(n \leq 65535\),所有数字不超过 \(10^8\),且各不相同。
题解
显然,只需要尽可能把更大的数放到左边。具体实现看代码。
#include<bits/stdc++.h>
using namespace std;
int n, a[65536], ans[65536], tot;
inline void solve(int x){
if(x > n) return;
ans[x] = a[++tot];
solve(x<<1|1); solve(x<<1);
return;
}
signed main(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a+1, a+1+n);
solve(1);
for(int i = 1; i <= n; i++) cout << ans[i] << (i==n?'\n':' ');
return 0;
}
T2 渡河
水题(~
描述
传说中教主乃世外高人,不屑于参加OI竞赛,于是云游四方,威风八面。只不过教主行踪不定,就像传说中的神兽一样可遇而不可求。小L和小H为了求得教主签名的Orz教主T-Shirt,打算碰碰运气展开了冒险。在冒险中,他们不幸被突来的洪水冲到了一个神秘丛林中,他们想尽快逃出这个地方。小L找到了一张看似为曾经的冒险者遗弃的地图,但经过探查,地图所示的确实是这片丛林。小L从地图上看到,有众多河流穿过这片丛林,等到他接近一条最近的河流时,发现水流较急,且河水很深,小H不擅长游泳,所以他们决定利用丛林中的树木做一只竹筏渡河。
虽然竹筏做好后可以在这一条河所连通的水域任意行进,但是竹筏在上岸后必须抛弃,若想再次渡河必须再做一次竹筏,但这毕竟是十分辛苦的,他们希望做竹筏也就是渡河的次数尽量少,就求助于你。
地图上的陆地和河流可以抽象冲一个 \(n \times n\) 由数字 \(0\) 和 \(1\) 组成的矩阵,其中 \(0\) 代表陆地,\(1\) 代表河流。无论在陆地上还还是河流上,他们都可以向相邻 \(8\) 格(边相邻或角相邻)移动,但是若要从陆地进入河流(也就是从 \(0\) 到 \(1\)),则必须制作竹筏。若到达地图边界则顺利逃脱。但是小T和小K有可能迷路,所以会多次询问你,对于每次询问,只要输出到达地图边界需要的最少渡河次数即可,保证每次询问都是指向陆地。
小L和小H翻到地图的反面,赫然发现六个大字:“教主到此一游”!两人无法抑制自己激动的心情,将这张地图珍藏起来。据说后来这张图成为无价之宝。
输入
第 \(1\) 行包括 \(2\) 个正整数 \(N\)、\(K\),分别描述了地图的长宽以及询问的次数。
下面 \(N\) 行,每行 \(N\) 个数字 \(0\) 或者 \(1\),数字之间没有空格,描述了这张地图。
接下来 \(K\) 行,每行 \(2\) 个正整数 \(x_i\)、\(y_i\),询问在第 \(x_i\) 行第 \(y_i\) 列最少需要渡河几次。
输出
输出仅包括 \(1\) 行,按输入顺序每行对于一个询问输出最少需要渡河的次数,数字间用空格隔开,行末换行并没有空格。
样例
共 \(1\) 组,不包含大样例。
样例 \(1\) 输入
9 3
000000000
011111110
010101010
011000110
010000010
010111010
010101010
011111110
000000000
1 3
3 3
4 6
样例 \(1\) 输出
0 1 1
数据范围
不管部分分。
对于 \(100\%\) 的数据,有 \(n \leq 1000,k \leq 40000\)。
题解
显然,我们可以依题意建图。如果从 \(0\) 走到 \(1\),则边权为 \(1\),否则为 \(0\)。
但每次询问都跑一遍 Dijkstra 肯定会超时。
我们发现,所有询问的共同点在于:路径终点是边界。
因此,我们建立一个新的节点 \(end\),所有边界向这个点连一条权值为 \(0\) 的边。
此时,所有路径的终点就一样了。于是,我们考虑把刚才所有的边反着建,然后以 \(end\) 作为 Dijkstra 的起点跑最短路。此时 \(dis[x]\) 就是 \(x\) 的答案。
#include<bits/stdc++.h>
#define ID(x, y) ((x-1) * n + y)
using namespace std;
inline int read(){
int x = 0;
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = (x<<1) + (x<<3) + (ch^48), ch = getchar();
return x;
}
inline void write(int x){
if(x > 9) write(x/10);
putchar(x%10+'0');
return;
}
int n, mp[1005][1005];
vector<pair<int, int> > e[1000005];
inline void add(int u, int v, int w){
// e[u].push_back({v, w});
e[v].push_back({u, w});
return;
}
const int dx[] = {0, 1, 1, 1, 0, -1, -1, -1};
const int dy[] = {1, 1, 0, -1, -1, -1, 0, 1};
inline void Build(){
// for(int i = 1; i <= n; i++){
// for(int j = 1; j <= n; j++){
// cout << ID(i, j) << " ";
// }
// cout << endl;
// }
int out = n * n + 1;
for(int i = 1; i < n; i++) add(ID(1, i), out, 0);
for(int i = 1; i < n; i++) add(ID(i, n), out, 0);
for(int i = 2; i <= n; i++) add(ID(n, i), out, 0);
for(int i = 2; i <= n; i++) add(ID(i, 1), out, 0);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
for(int k = 0; k < 8; k++){
int xx = i + dx[k], yy = j + dy[k];
if(xx >= 1 && xx <= n && yy >= 1 && yy <= n){
add(ID(i, j), ID(xx, yy), !mp[i][j] && mp[xx][yy]);
}
}
}
}
return;
}
struct Node{
int u, d;
bool operator > (const Node x) const {
return d > x.d;
}
};
int dis[1000005], vis[1000005];
priority_queue<Node, vector<Node>, greater<Node> > q;
inline void Dijkstra(int start){
for(int i = 1; i <= n * n + 1; i++) dis[i] = 1e9;
q.push({start, 0}); dis[start] = 0;
while(!q.empty()){
int u = q.top().u; q.pop();
if(vis[u]) continue;
vis[u] = true;
for(auto p : e[u]){
int v = p.first, w = p.second;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
q.push({v, dis[v]});
}
}
}
return;
}
signed main(){
// freopen("river.in", "r", stdin);
// freopen("river.out", "w", stdout);
n = read(); int q = read();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
char ch = getchar();
while(!isdigit(ch)) ch = getchar();
mp[i][j] = ch == '1';
}
}
// for(int i = 1; i <= n; i++){
// for(int j = 1; j <= n; j++){
// cout << mp[i][j] << " ";
// }
// cout << endl;
// }
Build();
Dijkstra(n * n + 1);
for(int i = 1; i <= q; i++){
int x = read(); int y = read();
write(dis[ID(x, y)]);
if(i == q) putchar('\n');
else putchar(' ');
}
return 0;
}
T3 菱形内的计数
水题(~
描述
教主上电视了!这个消息绝对是一个爆炸性的新闻。一经传开,大街上瞬间就没人了(都回家看电视去了),商店打烊,工厂停业。大家都把电视机的音量开到最大,教主的声音回响在大街小巷。
小L给小X慌乱地打开自己家的电视机,发现所有频道都播放的是教主的采访节目(-_-bbb)。只见电视屏幕上的教主笑意吟吟,给大家出了一道难题:
一个边长为n的大菱形被均匀地划分成了 \(n \times n\) 个边长为 \(1\) 的小菱形组成的网格,但是网格中部分边被抹去了,小L想知道,大菱形内有多少个平行四边形,这些平行四边形内不存在边。
教主说,如果谁写出了程序,移动用户请将程序发送到xxxx,联通用户请将程序发送到xxxx……如果答对这个题,将有机会参加抽奖,大奖将是教主签名的Orz教主T-Shirt一件!这个奖品太具有诱惑力了。于是你需要编一个程序完成这么一道题。
输入
输入的第 \(1\) 行为一个正整数 \(n\),为大菱形的边长。
以下 \(2n\) 行,每行 \(2n\) 个字符,字符为空格,“/”,“\”中的一个。
前 \(n\) 行,第 \(i\) 行中居中有 \(2i\) 个字符,这 \(2i\) 个字符中位置为奇数的字符只可能为“/”或者空格,位置为偶数的字符只可能为“\”或空格,若为空格表示这样一条边不存在,其余字符均为空格,描述了大菱形的上半部分。
后 \(n\) 行,第 \(i\) 行居中有有 \(2(n-i+1)\) 个字符,与上半部分类似地描述了菱形的下半部分
输入文件保证大菱形的轮廓上没有边被抹去。
输出
输出仅包括一个整数,为满足要求的平行四边形个数。
样例
共 \(1\) 组,不包含大样例。
样例 \(1\) 输入
3
/\
/\/\
/ /\ \
\/\ /
\ \/
\/
样例 \(1\) 输出
3
数据范围
不管部分分。
对于 \(100\%\) 的数据,有 \(n \leq 888\)。
题解
考虑把菱形变成正方形:
1 1 1 1 1 1 1
1 0 0 0 1 0 1
1 1 1 1 1 1 1
1 0 1 0 1 0 1
1 0 1 0 1 0 1
1 0 1 0 0 0 1
1 1 1 1 1 1 1
如上图,样例可以这样转化。转化的具体实现直接看代码(找规律
转化完后,显然我们可以对每个位置 \(0\) 进行一次 dfs。
类似于洪水填充,我们计算出联通块内 \(0\) 的个数。然后我们找到这个联通块横纵坐标的最大最小值,计算理论面积。比较个数与面积即可。
#include<bits/stdc++.h>
using namespace std;
const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, -1, 0, 1};
int n, a[2000][2000], vis[2000][2000], maxx, minx, maxy, miny, cnt0 = 0;
inline pair<int, int> trans(pair<int, int> x){
int i = x.first, j = x.second;
return {i+j-n, j+n-i+1};
}
inline void dfs(int x, int y){
if(x >= 1 && x <= (n << 1 | 1) && y >= 1 && y <= (n << 1 | 1)){
if(!a[x][y] && !vis[x][y]){
vis[x][y] = true; cnt0++;
maxx = max(maxx, x), minx = min(minx, x);
maxy = max(maxy, y), miny = min(miny, y);
for(int i = 0; i < 4; i++) dfs(x+dx[i], y+dy[i]);
}
}
return;
}
signed main(){
// freopen("diamond.in", "r", stdin);
// freopen("diamond.out", "w", stdout);
cin >> n; string s; getline(cin, s);
for(int i = 1; i <= 2 * n; i++){
getline(cin, s);
for(int j = 1; j <= 2 * n; j++){
if(s[j-1] != ' '){
a[trans({i, j}).first][trans({i, j}).second] = 1;
}
}
}
for(int i = 1; i <= (n << 1 | 1); i++){
for(int j = 1; j <= (n << 1 | 1); j++){
a[1][j] = a[j][1] = 1;
a[n<<1|1][j] = a[j][n<<1|1] = 1;
if(a[i][j]){
if(i % 2 == 0) a[i+1][j] = 1;
if(j % 2 == 0) a[i][j+1] = 1;
}
}
}
// for(int i = 1; i <= (n << 1 | 1); i++){
// for(int j = 1; j <= (n << 1 | 1); j++){
// cout << a[i][j] << " ";
// }
// cout << endl;
// }
int cnt = 0;
for(int i = 1; i <= (n << 1 | 1); i++){
for(int j = 1; j <= (n << 1 | 1); j++){
if(!a[i][j] && !vis[i][j]){
maxx = 0, minx = 1e9;
maxy = 0, miny = 1e9;
cnt0 = 0; dfs(i, j);
if((maxx-minx+1) * (maxy-miny+1) == cnt0) cnt++;
}
}
}
cout << cnt << endl;
return 0;
}
T4 电缆建设
水题(~
描述
教主上电视了,但是蔚蓝城郊区沿河的村庄却因电缆线路老化而在直播的时候停电,这让市长SP先生相当的愤怒,他决定重修所有电缆,并改日播放录像,杜绝此类情况再次发生。
河流两旁各有n,m个村庄,每个村庄可以用二维坐标表示,其中河流一旁的村庄横坐标均为x1,河流另一旁的村庄横坐标均为x2。由于地势十分开阔,任意两个村庄可以沿坐标系直线修建一条电缆连接,长度即为两村庄的距离。要修建若干条电缆,使得任意两个村庄都可以通过若干个有电缆连接的村庄相连。
因为修建的经费与长度成正比,SP市长当然希望所花的钱越少越好,所以他希望你来帮助他设计一套方案,使得电缆总长度最小,并告诉所需要的电缆总长度。
输入
输入的第1行为四个正整数,n,m,x1,x2,表示河流两旁的村庄数以及横坐标。
第2行有n个正整数y1[1], y1[2]... y1[n],描述了横坐标为x1的村庄的纵坐标。第1个整数为纵坐标最小的那个村庄的纵坐标,从第2个整数开始,第i个整数代表当前村庄与前一个村庄的纵坐标差,即y[i]-y[i-1]。
第3行有m个正整数y2[1], y2[2]... y2[n],用同样的方法描述了横坐标为x2的村庄的纵坐标。
输出
输出仅包括一个实数,为最小的总长度,答案保留两位小数。
样例
共 \(1\) 组,不包含大样例。
样例 \(1\) 输入
2 3 1 3
1 2
2 2 1
样例 \(1\) 输出
7.24
数据范围
不管部分分。
对于100%的数据,n,m≤600000,所有村庄纵坐标不超过10^8,x1<x2<2000,输入文件不超过4M。
题解
显然连出所有可能的边,求最小生成树就可以了。但数据范围不允许这样做。
考虑对于河的两边,相邻的两个连边即可。
而跨河的边,我们考虑每个点都与对面离他最近的连边。
但这样是错的,随便就能拍出反例:
7 4 1 3
1 2 1 3 3 9 2
8 5 1 2
答案是 \(23.28\)。
但是,如果我们在最近的点左右多连两条,就不会错了。
场上花 3h 拍了 1.3w 组也没错,然后就自信的交了。然后过了。。
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct Edge{
int u, v;
double w;
};
int n, m, x1, x2, a[600005], b[600005];
vector<Edge> edge;
inline double dis(int ax, int by){
// (x1, a[ax]) --> (x2, b[by])
return sqrt(1ll*(x1-x2)*(x1-x2)+1ll*(a[ax]-b[by])*(a[ax]-b[by]));
}
inline void Build(){
for(int i = 1; i < n; i++) edge.push_back({i, i+1, (double)a[i+1]-a[i]});
for(int i = 1; i < m; i++) edge.push_back({i+n, i+1+n, (double)b[i+1]-b[i]});
int pos = 1;
for(int i = 1; i <= n; i++){
while(pos < m && abs(a[i]-b[pos]) > abs(a[i]-b[pos+1])) pos++;
edge.push_back({i, pos+n, dis(i, pos)});
if(pos - 1 >= 1) edge.push_back({i, pos-1+n, dis(i, pos-1)});
if(pos + 1 <= m) edge.push_back({i, pos+1+n, dis(i, pos+1)});
}
return;
}
inline bool cmp(Edge x, Edge y){
return x.w < y.w;
}
int f[1200005];
inline int find(int k){
if(f[k] == k) return k;
return f[k] = find(f[k]);
}
inline void merge(int u, int v){
int uu = find(u), vv = find(v);
if(uu != vv) f[uu] = vv;
return;
}
signed main(){
// freopen("cable.in", "r", stdin);
// freopen("cable.out", "w", stdout);
cin >> n >> m >> x1 >> x2;
for(int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i-1];
for(int i = 1; i <= m; i++) cin >> b[i], b[i] += b[i-1];
// for(int i = 1; i <= n; i++) cout << a[i] << " ";
// cout << endl;
// for(int i = 1; i <= m; i++) cout << b[i] << " ";
// cout << endl;
Build();
sort(edge.begin(), edge.end(), cmp);
for(int i = 1; i <= n + m; i++) f[i] = i;
int remain = n + m - 1; double ans = 0;
for(int i = 0; i < (int)edge.size(); i++){
if(!remain) break;
int u = edge[i].u, v = edge[i].v;
if(find(u) != find(v)){
merge(u, v);
ans += edge[i].w;
}
}
cout << fixed << setprecision(2) << ans << endl;
return 0;
}

浙公网安备 33010602011771号