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})\)

然后我看到的第一瞬间——打表

然后就是除了欧拉谁都想不到的调和级数:

\[\sum^{n}_{i = 1} \frac{1}{i}=\ln n+\gamma+\varepsilon_n \]

其中 \(\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);
}
posted @ 2023-08-09 21:51  ZnPdCo  阅读(20)  评论(0编辑  收藏  举报