20230809比赛
T1 电缆建设
Description
教主上电视了,但是蔚蓝城郊区沿河的村庄却因电缆线路老化而在直播的时候停电,这让市长SP先生相当的愤怒,他决定重修所有电缆,并改日播放录像,杜绝此类情况再次发生。
河流两旁各有n,m个村庄,每个村庄可以用二维坐标表示,其中河流一旁的村庄横坐标均为x1,河流另一旁的村庄横坐标均为x2。由于地势十分开阔,任意两个村庄可以沿坐标系直线修建一条电缆连接,长度即为两村庄的距离。要修建若干条电缆,使得任意两个村庄都可以通过若干个有电缆连接的村庄相连。
因为修建的经费与长度成正比,SP市长当然希望所花的钱越少越好,所以他希望你来帮助他设计一套方案,使得电缆总长度最小,并告诉所需要的电缆总长度。Input
输入的第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的村庄的纵坐标。Output
输出仅包括一个实数,为最小的总长度,答案保留两位小数。
Sample Input
2 3 1 3 1 2 2 2 1 Sample Output
7.24 Data Constraint
Hint
【样例解释】
按如下方案建设电缆,括号内代表村庄的坐标,“-”代表有电缆连接。
(1,1)-(1,3)
(1,3)-(3,4)
(3,4)-(3,2)
(3,4)-(3,5)【数据规模】
对于20%的数据,n,m≤10;
对于40%的数据,n,m≤1000;
对于70%的数据,n,m≤100000;
对于100%的数据,n,m≤600000,所有村庄纵坐标不超过10^8,x1<x2<2000,输入文件不超过4M。
最开始认为可以通过最小生成树求最短距离。然后后面看到 \(n,m\le600000\) ,以为两两相乘不行,复杂度会爆炸,所以就放弃了。没想到就是正解啊ヽ(≧□≦)ノ
其实对于在同一条直线上的就相邻连接,不在同一条直线上的就选择离它最近的两个点连接(为什么是两个,因为你根本不知道哪个近哪个远~)
然后就可以快乐的跑克鲁斯卡尔了。
#include <cstdio> #include <cmath> #include <algorithm> #define ll int using namespace std; ll n, m, X1, X2; ll Y1[600010], Y2[600010]; ll s1[600010], s2[600010]; struct node { ll x, y; long long w; } side[2400010]; ll cnt; ll fa[1200010]; double ans; inline long long dis(long long ax, long long ay, long long bx, long long by) { return (ax - bx) * (ax - bx) + (ay - by) * (ay - by) * 1LL; } ll find(ll x) { if(fa[x] == x) return x; return fa[x] = find(fa[x]); } void solve(ll l, ll r) { if(l < r) { ll i = l, j = r; node x = side[l]; while(i < j) { while(i < j && side[j].w >= x.w) j--; if(i < j) swap(side[i++], side[j]); while(i < j && side[i].w <= x.w) i++; if(i < j) swap(side[i], side[j--]); } solve(l, i - 1); solve(i + 1, r); } } bool cmp(node x, node y) { return x.w < y.w; } int main() { freopen("data10.in", "r", stdin); scanf("%lld %lld %lld %lld", &n, &m, &X1, &X2); for(ll i = 1; i <= n + m; i ++) fa[i] = i; for(ll i = 1; i <= n; i++) { scanf("%lld", &Y1[i]); s1[i] = s1[i - 1] + Y1[i]; if(i > 1) { side[++cnt].x = i; side[cnt].y = i - 1; side[cnt].w = Y1[i] * 1LL * Y1[i] * 1LL; } } for(ll i = 1; i <= m; i++) { scanf("%lld", &Y2[i]); s2[i] = s2[i - 1] + Y2[i]; if(i > 1) { side[++cnt].x = n + i; side[cnt].y = n + i - 1; side[cnt].w = Y2[i] * 1LL * Y2[i] * 1LL; } } ll j = 1; for(ll i = 1; i <= n; i++) { while(s1[i] > s2[j] && j <= m) { j++; } side[++cnt].x = i; side[cnt].y = n + j; side[cnt].w = dis(X1 * 1LL, s1[i] * 1LL, X2 * 1LL, s2[j] * 1LL); if(j > 1) { side[++cnt].x = i; side[cnt].y = n + j - 1; side[cnt].w = dis(X1 * 1LL, s1[i] * 1LL, X2 * 1LL, s2[j - 1] * 1LL); } } // for(ll i = 1; i <= cnt; i++) { // printf("%.2lf ", side[i].w); // } // printf("\n"); sort(side + 1, side + 1 + cnt, cmp); // for(ll i = 1; i <= cnt; i++) { // printf("%lld ", side[i].w); // } // printf("\n"); ll tot = 0; for(ll i = 1; i <= cnt; i++) { ll x = find(side[i].x); ll y = find(side[i].y); if(x != y) { fa[x] = y; tot++; ans+=sqrt((double)side[i].w); } if(tot == n + m - 1) break; } printf("%.2lf", ans); return 0; }
T2 菱形内的计数
Description
教主上电视了!这个消息绝对是一个爆炸性的新闻。一经传开,大街上瞬间就没人了(都回家看电视去了),商店打烊,工厂停业。大家都把电视机的音量开到最大,教主的声音回响在大街小巷。
小L给小X慌乱地打开自己家的电视机,发现所有频道都播放的是教主的采访节目(-_-bbb)。只见电视屏幕上的教主笑意吟吟,给大家出了一道难题:
一个边长为n的大菱形被均匀地划分成了n*n个边长为1的小菱形组成的网格,但是网格中部分边被抹去了,小L想知道,大菱形内有多少个平行四边形,这些平行四边形内不存在边。
教主说,如果谁写出了程序,移动用户请将程序发送到xxxx,联通用户请将程序发送到xxxx……如果答对这个题,将有机会参加抽奖,大奖将是教主签名的Orz教主T-Shirt一件!这个奖品太具有诱惑力了。于是你需要编一个程序完成这么一道题。Input
输入的第1行为一个正整数n,为大菱形的边长。
以下2n行,每行2n个字符,字符为空格,“/”,“\”中的一个。
前n行,第i行中居中有2i个字符,这2i个字符中位置为奇数的字符只可能为“/”或者空格,位置为偶数的字符只可能为“\”或空格,若为空格表示这样一条边不存在,其余字符均为空格,描述了大菱形的上半部分。
后n行,第i行居中有有2(n-i+1)个字符,与上半部分类似地描述了菱形的下半部分
输入文件保证大菱形的轮廓上没有边被抹去。Output
输出仅包括一个整数,为满足要求的平行四边形个数。
Sample Input
3 /\ /\/\ / /\ \ \/\ / \ \/ \/ Sample Output
3 Data Constraint
Hint
【数据规模】
对于20%的数据,n≤10;
对于40%的数据,n≤60;
对于60%的数据,n≤200;
对于100%的数据,n≤888。
想复杂了,真的想复杂了。
其实只需要把菱形转换成正方形,然后跑dfs就行了。同时求出这块地方的总面积,长与宽。
怎么判断是否是一个合格的矩形?-》长*宽==总面积
#include <cstdio> #include <algorithm> #include <climits> using namespace std; #define ll int #define NONE 0 #define L 1 #define R 2 #define T 4 #define B 8 const ll dx[4]={1,0,-1,0}; const ll dy[4]={0,1,0,-1}; ll n; char a[2000][2000]; ll t[2000][2000]; bool m[2000][2000]; ll x, y, xx, yy; ll ans; ll sum; ll mxL, mxR, mxT, mxB; void dfs(ll x, ll y) { mxL = min(mxL, y); mxR = max(mxR, y); mxT = min(mxT, x); mxB = max(mxB, x); for(int i = 0; i < 4; i++) { ll xx = x + dx[i]; ll yy = y + dy[i]; if(m[xx][yy] == 0) { m[xx][yy]=1; sum++; dfs(xx, yy); } } } int main() { freopen("data10 (1).in", "r", stdin); // freopen("data.txt", "w", stdout); scanf("%d", &n); for(ll i = 1; i <= 2*n; i++) { char c=' '; ll cnt = 0; ll mx = (i <= n ? 2 * i : 2*(2 * n - i + 1)); while(c != '/' && c != '\\') c = getchar(); a[i][++cnt]=c; while(cnt < mx) a[i][++cnt]=getchar(); } for(ll i = 1; i < n; i++) { ll cnt = 0; for(ll j = 1; j <= 2*i; j += 2) { xx = i, yy = ++cnt; if(xx <= n) x = xx - yy + 1; else x = n - yy + 1; if(xx <= n) y = yy; else y = yy + (xx - n); if(a[i][j]=='/') t[x][y] |= L; if(a[i+1][j+2]=='/') t[x][y] |= R; if(a[i][j+1]=='\\') t[x][y] |= T; if(a[i+1][j+1]=='\\') t[x][y] |= B; } } { ll i = n; ll cnt = 0; for(ll j = 1; j <= 2*i; j += 2) { xx = i, yy = ++cnt; if(xx <= n) x = xx - yy + 1; else x = n - yy + 1; if(xx <= n) y = yy; else y = yy + (xx - n); if(a[i][j]=='/') t[x][y] |= L; if(a[i+1][j+1]=='/') t[x][y] |= R; if(a[i][j+1]=='\\') t[x][y] |= T; if(a[i+1][j]=='\\') t[x][y] |= B; } } for(ll i = n + 1; i < 2 * n; i++) { ll cnt = 0; for(ll j = 2; j < 2*(2 * n - i + 1); j += 2) { xx = i, yy = ++cnt; if(xx <= n) x = xx - yy + 1; else x = n - yy + 1; if(xx <= n) y = yy; else y = yy + (xx - n); if(a[i][j]=='/') t[x][y] |= L; if(a[i+1][j]=='/') t[x][y] |= R; if(a[i][j+1]=='\\') t[x][y] |= T; if(a[i+1][j-1]=='\\') t[x][y] |= B; } } for(ll i = 1; i <= n; i++) { for(ll j = 1; j <= n; j++) { if(t[i][j] & T) { m[2 * i - 1][2 * j - 1] = 1; m[2 * i - 1][2 * j] = 1; m[2 * i - 1][2 * j + 1] = 1; } if(t[i][j] & B) { m[2 * i + 1][2 * j - 1] = 1; m[2 * i + 1][2 * j] = 1; m[2 * i + 1][2 * j + 1] = 1; } if(t[i][j] & L) { m[2 * i - 1][2 * j - 1] = 1; m[2 * i][2 * j - 1] = 1; m[2 * i + 1][2 * j - 1] = 1; } if(t[i][j] & R) { m[2 * i - 1][2 * j + 1] = 1; m[2 * i][2 * j + 1] = 1; m[2 * i + 1][2 * j + 1] = 1; } } } for(ll i = 1; i <= 2 * n + 1; i ++) { for(ll j = 1; j <= 2 * n + 1; j++) { // printf("%d ", m[i][j]); if(m[i][j] == 0) { sum = 1; mxR = mxB = 0; mxL = mxT = 100000; m[i][j] = 1; dfs(i, j); if((mxR - mxL + 1) * (mxB - mxT + 1) == sum) { ans ++; } } } // printf("\n"); } printf("%lld", ans); }
T3 渡河
Description
传说中教主乃世外高人,不屑于参加OI竞赛,于是云游四方,威风八面。只不过教主行踪不定,就像传说中的神兽一样可遇而不可求。小L和小H为了求得教主签名的Orz教主T-Shirt,打算碰碰运气展开了冒险。在冒险中,他们不幸被突来的洪水冲到了一个神秘丛林中,他们想尽快逃出这个地方。小L找到了一张看似为曾经的冒险者遗弃的地图,但经过探查,地图所示的确实是这片丛林。小L从地图上看到,有众多河流穿过这片丛林,等到他接近一条最近的河流时,发现水流较急,且河水很深,小H不擅长游泳,所以他们决定利用丛林中的树木做一只竹筏渡河。
虽然竹筏做好后可以在这一条河所连通的水域任意行进,但是竹筏在上岸后必须抛弃,若想再次渡河必须再做一次竹筏,但这毕竟是十分辛苦的,他们希望做竹筏也就是渡河的次数尽量少,就求助于你。
地图上的陆地和河流可以抽象冲一个n*n由数字0和1组成的矩阵,其中0代表陆地,1代表河流。无论在陆地上还还是河流上,他们都可以向相邻8格(边相邻或角相邻)移动,但是若要从陆地进入河流(也就是从0到1),则必须制作竹筏。若到达地图边界则顺利逃脱。但是小T和小K有可能迷路,所以会多次询问你,对于每次询问,只要输出到达地图边界需要的最少渡河次数即可,保证每次询问都是指向陆地。
小L和小H翻到地图的反面,赫然发现六个大字:“教主到此一游”!两人无法抑制自己激动的心情,将这张地图珍藏起来。据说后来这张图成为无价之宝。Input
第1行包括2个正整数N,K,分别描述了地图的长宽以及询问的次数。
下面N行,每行N个数字0或者1,数字之间没有空格,描述了这张地图。
接下来K行,每行2个正整数xi,yi,询问在第xi行第yi列最少需要渡河几次。Output
输出仅包括1行,按输入顺序每行对于一个询问输出最少需要渡河的次数,数字间用空格隔开,行末换行并没有空格。
Sample Input
9 3 000000000 011111110 010101010 011000110 010000010 010111010 010101010 011111110 000000000 1 3 3 3 4 6 Sample Output
0 1 1 Data Constraint
Hint
【样例说明】
第1次询问由于已经处于边界所以答案为0。
第2次询问不断向左或向上走都只要渡河1次。
第3次询问不断向四个方向中的一个方向走同样只需要1次渡河。【数据规模】
对于20%的数据,有n≤10;
对于40%的数据,有n≤100,k≤10;
对于60%的数据,有n≤1000,k≤100;
对于100%的数据,有n≤1000,k≤40000。
为什么不用dij?
让时光倒流,从四条边回到起点,跑一个堆优化迪杰斯特拉!
开始时把四条边都加进去,然后就是跑最短路(注意:是过河的最短路)
同时,因为时光倒流,所以记得从河上到陆地才花费1。
#include <cstdio> #include <cstring> #include <algorithm> #include <climits> #define ll long long using namespace std; const ll dx[8]={1,0,-1,0,1,1,-1,-1}; const ll dy[8]={0,1,0,-1,-1,1,-1,1}; ll n, k; ll dis[2000010]; char map[1010][1010]; bool vis[2000010]; struct node { ll u, w; } q[2000010]; ll tot; void up(ll x) { while(x > 1 && q[x / 2].w > q[x].w) { swap(q[x], q[x / 2]); x = x / 2; } } void down(ll x) { while(2 * x <= tot && ((2 * x + 1 <= tot && q[2 * x + 1].w < q[x].w) || q[2 * x].w < q[x].w)) { if(2 * x + 1 <= tot && q[2 * x + 1].w < q[2 * x].w) { swap(q[x], q[2 * x + 1]); x = 2 * x + 1; } else { swap(q[x], q[2 * x]); x = 2 * x; } } } void insert(node x) { q[++tot] = x; up(tot); } void pop() { swap(q[1], q[tot]); tot--; down(1); } int main() { // freopen("data.in", "r", stdin); // freopen("data.out", "w", stdout); scanf("%lld %lld", &n, &k); for(ll i = 1; i <= n; i++) { scanf("%s", map[i] + 1); } for(ll i = 1; i <= n * n + n; i++) { dis[i] = 1e15; } for(ll i = 1; i <= n; i++) { dis[n + i] = 0; insert((node){n + i, 0}); } for(ll i = 2; i < n; i++) { dis[i * n + 1] = 0; insert((node){i * n + 1, 0}); dis[i * n + n] = 0; insert((node){i * n + n, 0}); } for(ll i = 1; i <= n; i++) { dis[n * n + i] = 0; insert((node){n * n + i, 0}); } while(tot) { ll u = q[1].u; pop(); if(vis[u]) continue; vis[u] = true; // 逆计算 ll x = u / n; ll y = u % n; if(y == 0) { x++, y = n; } for(int i = 0; i < 8; i++) { ll xx = x + dx[i]; ll yy = y + dy[i]; if(xx > 0 && yy > 0 && xx <= n && yy <= n) { ll v = xx * n + yy; if(!vis[v]) { ll cost = map[x][y] == '1' && map[xx][yy] == '0'; if(dis[v] > dis[u] + cost) { dis[v] = dis[u] + cost; insert((node){v, dis[v]}); } } } } } for(ll i = 1; i <= k; i++) { ll x, y; scanf("%lld %lld", &x, &y); ll u = x * n + y; if(i < k) printf("%lld ", dis[u]); else printf("%lld", dis[u]); } return 0; }
T4 搞笑的代码
Description
在OI界存在着一位传奇选手——QQ,他总是以风格迥异的搞笑代码受世人围观
某次某道题目的输入是一个排列,他使用了以下伪代码来生成数据
while 序列长度<n do
{
随机生成一个整数属亍[1,n]
如果这个数没有出现过则加入序列尾
}
聪明的同学一定发现了,这样生成数据是徆慢的,那么请你告诉QQ,生成一个n排列的期望随机次数Input
一个正整数n,表示需要生成一个n排列
Output
一个数表示期望随机次数,保留整数
Sample Input
4 Sample Output
8(.333333…) 【友情提示】 输出样例的括号里表示答案的小数部分,但实际丌要求输出 数学期望=sigma(概率×权值),本题中为期望随机次数=sigma(概率×随机次数) Data Constraint
30%数据满足n≤3
80%数据满足n≤10^7
100%数据满足n≤2^31
搞笑,完全不会。
生成第 \(i\) 个数只需取一次的概率为 \(\frac{n-i+1}{n}\)(前面已选 \(i-1\) 个数,还剩 \(n-(i-1)=n-i+1\) 个数是合法的,每次取数有 \(n\) 种可能,取到合法的数的概率为 \(\frac{n-i+1}{n}\))
那么生成第 \(i\) 个数的期望次数为 \(\frac{n}{n-i+1}\) (可以理解成这个倒霉蛋把所有不合法的情况都抽完了,只剩下合法的了,所以我们抽 \(\frac{n}{n-i+1}\) 次就可以把 \(\frac{n-i+1}{n}\) 的概率给抽完了,除非你真的倒霉)
那么以此类推,取出 \(n\) 个数的期望次数为 \(\sum^{n}_{i=1} \frac{n}{n-i+1} = \frac{n}{n} + \frac{n}{n - 1} + \frac{n}{n - 2} + \cdots + \frac{n}{1} = n(\frac{1}{n} + \frac{1}{n - 1} + \frac{1}{n - 2} + \cdots + \frac{1}{1})\)
然后我看到的第一瞬间——打表。
然后就是除了欧拉谁都想不到的调和级数:
其中 \(\gamma\) gamma 是欧拉常数(约等于0.57721566490153286060651209,写少了只有90pts),\(\varepsilon_n\) varepsilon 约等于 \(\frac{1}{2n}\) 。
调和级数发散率证明|欧拉常数|ln n+gamma+varepsilon_k证明|sigma(1/i) - ZnPdCo - 博客园 (cnblogs.com)
#include <cstdio> #include <cmath> #define ll long long ll n; int main() { scanf("%lld", &n); double ans = log(n) + 0.57721566490153286060651209 + (1.0 / 2 / n); printf("%.lf", n * ans); }
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现