2021牛客暑期多校(二)

本文总结了2021牛客暑期多校比赛中的五个编程题目,包括DrawGrids的树形构建策略,ErBaGame的二八游戏模拟,Girlfriend中阿氏球体积计算,LeagueofLegends的区间划分优化,以及Penguins的路径规划。展示了关键思路和代码片段,适合参赛者和学习者参考。
摘要由CSDN通过智能技术生成

C: Draw Grids

题目大意

一张 n ∗ m n*m nm的点图,两个人轮流选择一条边连接相邻两个点,要求不能连出封闭图形,给出 n n n m m m,问第一个人能否赢。

思路

因为不能连出封闭图形,所以将问题转化为将这些点连成一棵树,树的边数是奇还是偶(点数 n ∗ m n*m nm是奇还是偶)。赛时没看出来,硬是手膜了一遍 2333 2333 2333

代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
int main()
{
    scanf("%d %d", &n, &m);
    if(n*m%2 == 0)
        printf("YES");
    else
        printf("NO");
    return 0;
}

D: Er Ba Game

题目大意

二八游戏,两个人各有两张牌, 28 28 28最大,其次是对,都是对比小的,都不是比和对 10 10 10取模,一样再比 b b b,问谁赢。

思路

模拟。

代码
#include <bits/stdc++.h>
using namespace std;
int t, a1, b1, a2, b2;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d %d %d %d", &a1, &b1, &a2, &b2);
		if(a1 > b1)
			swap(a1, b1);
		if(a2 > b2)
			swap(a2, b2);
		if(a1==a2 && b1==b2)
			printf("tie\n");
		else if(a1==2 && b1==8)
			printf("first\n");
		else if(a2==2 && b2==8)
			printf("second\n");
		else if(a1==b1 && a2!=b2)
			printf("first\n");
		else if(a2==b2 && a1!=b1)
			printf("second\n");
		else if(a1==b1 && a2==b2)
		{
			if(a1 > a2)
				printf("first\n");
			else
				printf("second\n");
		}
		else
		{
			if((a1+b1)%10 > (a2+b2)%10)
				printf("first\n");
			else if((a1+b1)%10 < (a2+b2)%10)
				printf("second\n");
			else
			{
				if(b1 > b2)
					printf("first\n");
				else
					printf("second\n");
			}
		}
	}
	return 0;
}

F: Girlfriend

题目大意

给定阿氏球的两定点及 k k k,求两球相交部分体积。

思路

先根据阿氏球模型算出球的球心坐标,为 ( k 2 x b – x a ) / ( k 2 – 1.0 ) (k^2x_b – x_a)/(k^2 – 1.0) (k2xbxa)/(k21.0) x x x y y y z z z分别计算,再根据球心和球上一点距离算球的半径,为 ( k / ( k 2 – 1.0 ) ) ∗ s q r t ( ( x b − x a ) 2 + ( y b − y a ) 2 + ( z b − z a ) 2 ) (k/(k^2 – 1.0))*sqrt((x_b - x_a)^2+(y_b - y_a)^2+(z_b - z_a)^2) (k/(k21.0))sqrt((xbxa)2+(ybya)2+(zbza)2),再算两球心距离。结果分类讨论,球心距离大于两半径之和直接输出 0 0 0(相离),球心距离加一半径小于另一半径输出小球体积(包含),其余相交的情况根据球冠公式 π 3 h 2 ( 3 r − h ) \frac{π}{3}h^2(3r - h) 3πh2(3rh) 算相交体积, h h h为公共弦平面到球面的距离。注意: i n t int int类型整数进行浮点计算要 − 1.0 -1.0 1.0或者 ∗ 1.0 *1.0 1.0 d o u b l e double double不能printf(“%.3f\n”, 0),要写成printf(“%.3f\n”, 0.0) l o n g long long d o u b l e double double不能printf(“%Lf\n”, 0.0)

代码
#include <bits/stdc++.h>
using namespace std;
int t, k1, k2;
double xa, ya, za, xb, yb, zb;
double x, y, z, r1, h1;
double xc, yc, zc, xd, yd, zd;
double x2, y2, z2, r2, h2;
double eps = 1e-8, yxj, pi = 3.1415926, ans;
void read()
{
	scanf("%lf %lf %lf", &xa, &ya, &za);
	scanf("%lf %lf %lf", &xb, &yb, &zb);
	scanf("%lf %lf %lf", &xc, &yc, &zc);
	scanf("%lf %lf %lf", &xd, &yd, &zd);
	scanf("%d %d", &k1, &k2);
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		read();
		x = (k1*k1*xb-xa)/(k1*k1-1.0);
		y = (k1*k1*yb-ya)/(k1*k1-1.0);
		z = (k1*k1*zb-za)/(k1*k1-1.0);
		r1 = sqrt((k1/(k1*k1-1.0))*(k1/(k1*k1-1.0))*((xa-xb)*(xa-xb)+(ya-yb)*(ya-yb)+(za-zb)*(za-zb)));
		x2 = (k2*k2*xd-xc)/(k2*k2-1.0);
		y2 = (k2*k2*yd-yc)/(k2*k2-1.0);
		z2 = (k2*k2*zd-zc)/(k2*k2-1.0);
		r2 = sqrt((k2/(k2*k2-1.0))*(k2/(k2*k2-1.0))*((xc-xd)*(xc-xd)+(yc-yd)*(yc-yd)+(zc-zd)*(zc-zd)));
		yxj = sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2)+(z-z2)*(z-z2));
		if(yxj >= r1+r2)
			printf("%.10lf\n", 0.0);
		else if(r1 >= yxj+r2)
			printf("%.10lf\n", 4*pi*r2*r2*r2/3);
		else if(r2 >= yxj+r1)
			printf("%.10lf\n", 4*pi*r1*r1*r1/3);
		else
		{
			h1 = r1*(1-(r1*r1+yxj*yxj-r2*r2)/(2*r1*yxj));
			h2 = r2*(1-(r2*r2+yxj*yxj-r1*r1)/(2*r2*yxj));
			ans = (h1*h1*(3*r1-h1)+h2*h2*(3*r2-h2))*pi/3;
			printf("%.10lf\n", ans);
		}	
	}
	return 0;
}

G: League of Legends

题目大意

给定 n n n个区间,要求将它们分成 k k k组,每组内有交,最大化每组交长度之和。 1 ≤ k ≤ n ≤ 5000 1≤k≤n≤5000 1kn5000 0 ≤ a < b < 1 0 5 0≤a<b<10^5 0a<b<105

思路

首先考虑简化问题,对于每一个包含其它区间的大区间,其最优决策有两种: 1 、 1、 1归属到一个被它包含的区间所在的组,不影响答案; 2 、 2、 2独自一组,长度直接算入答案。剩下的部分均是独立的小区间,不妨提取出来为 ( a i , b i ) (a_i,b_i) (ai,bi),排序之后进行 d p dp dp
d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个,分了 j j j组的最优解,转移为 d p [ i ] [ j ] = m a x ( d p [ k ] [ j − 1 ] + b k + 1 − a i ) dp[i][j] = max(dp[k][j-1] + b_{k+1} - a_i) dp[i][j]=max(dp[k][j1]+bk+1ai) k k k 1 − ( i − 1 ) 1-(i-1) 1(i1)枚举,复杂度为 O ( n 3 ) O(n^3) O(n3)。注意到每一次计算 d p [ i ] [ j ] dp[i][j] dp[i][j]都需要取 d p [ k ] [ j − 1 ] + b k + 1 dp[k][j-1] + b_{k+1} dp[k][j1]+bk+1的最大值,即 k k k的枚举是重复的,所以用单调队列维护 d p [ k ] [ j − 1 ] + b k + 1 dp[k][j-1] + b_{k+1} dp[k][j1]+bk+1,队列里存储的是 k k k。操作时将组数 j j j提到最外层循环,内层循环表示前 i i i个,同时对每一个 j j j维护一个单调队列,复杂度为 O ( n 2 ) O(n^2) O(n2) d p dp dp数组的初值全部设为 − 1 -1 1 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]设为 0 0 0。每次计算 d p [ i ] [ j ] dp[i][j] dp[i][j],先将 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]入队,再通过 b k + 1 > a i b_{k+1} > a_i bk+1>ai将队首不符合条件的元素弹掉,如果队列内还有元素再更新 d p [ i ] [ j ] dp[i][j] dp[i][j]
综合大区间时,将大区间按区间长度从大到小排序, a n s ans ans取大区间放入 0 — m i n ( k , 0—min(k, 0min(k, 大区间个数 ) ) )个的最大值。

代码
#include <bits/stdc++.h>
using namespace std;
int n, k;
struct interval
{
	int a, b, bh;
}in[5010], ins[5010];
int ctb, cts, len[5010];
int dp[5010][5010];
int ck[5010], hd, tl;
int ans, sum;
bool cmp(interval x, interval y)
{
	if(x.a < y.a)
		return 1;
	else if(x.a > y.a)
		return 0;
	else
		return x.b < y.b;
}
int main()
{
	scanf("%d %d", &n, &k);
	for(int i = 1; i <= n; i++)
		scanf("%d %d", &in[i].a, &in[i].b);
	sort(in+1, in+n+1, cmp);
	int now = 19260817;
	for(int i = n; i >= 1; i--)
	{
		if(now <= in[i].b)
			len[++ctb] = in[i].b - in[i].a;
		else
		{
			now = in[i].b;
			ins[++cts] = in[i];
		}
	}
	sort(len+1, len+ctb+1, greater<int>());
	reverse(ins+1, ins+cts+1);
	for(int i = 0; i <= n; i++)
		for(int j = 0; j <= n; j++)
			dp[i][j] = -1;
	dp[0][0] = 0;
	for(int i = 1; i <= k; i++)
	{
		hd = 1, tl = 0;
		for(int j = i; j <= cts; j++)
		{
			if(dp[i-1][j-1] != -1)
			{
				while(hd <= tl && dp[i-1][ck[tl]]+ins[ck[tl]+1].b <= dp[i-1][j-1]+ins[j].b)
					tl--;
				ck[++tl] = j-1;
			}
			while(hd <= tl && ins[ck[hd]+1].b < ins[j].a)
				hd++;
			if(hd <= tl)
				dp[i][j] = dp[i-1][ck[hd]] + ins[ck[hd]+1].b - ins[j].a;
		}
	}
	for(int i = 0; i <= min(k, ctb); i++)
	{
		sum += len[i];
		if(dp[k-i][cts] != -1)
			ans = max(ans, dp[k-i][cts]+sum);
	}
	printf("%d\n", ans);
	return 0;
}

最后还是面向 s t d std std编程了,:(

I: Penguins

题目大意

有两张不同的地图 ( 20 ∗ 20 ) (20*20) (2020),玩家同时操控两只企鹅,一只在 ( 20 , 20 ) (20, 20) (20,20),一只在 ( 20 , 1 ) (20, 1) (20,1),目标是最终使一只在 ( 1 , 20 ) (1, 20) (1,20),一只在 ( 1 , 1 ) (1, 1) (1,1)。企鹅会从目标地点移出,所以必须同时到达。企鹅的运动同上下反左右。最终输出最短步数,左边企鹅的路径以及标出企鹅路径的地图。

思路

最优步数问题 B F S BFS BFS,用结构体存储状态,队列维护,只有两只企鹅都撞墙才是不可行的状态。记录当前格子是从哪个方向转移来的,最后逆向记录路径。记录路径时如果左边企鹅这一步是撞墙,就要记录另一只企鹅的路径。

代码
#include <bits/stdc++.h>
using namespace std;
int ans, road[25][25][25][25], prd[500];
//上-4 下-1 左-2 右-3 
char f[10] = "ODLRU";
char t[5] = ".#A";
int lt[25][25], rt[25][25];
int flx[5] = {0, 1, 0, 0, -1};
int fly[5] = {0, 0, -1, 1, 0};
int frx[5] = {0, 1, 0, 0, -1};
int fry[5] = {0, 0, 1, -1, 0};
int vis[25][25][25][25];
void read()
{
	for(int i = 1; i <= 20; i++)
	{
		char c;
		for(int j = 1; j <= 20; j++)
		{
			scanf("%c", &c);
			if(c == '#')
				lt[i][j] = 1;
		}
		scanf("%c", &c);
		for(int j = 1; j <= 20; j++)
		{
			scanf("%c", &c);
			if(c == '#')
				rt[i][j] = 1;
		}
		getchar();
	}
	for(int i = 0; i <= 21; i++)
	{
		lt[0][i] = 1;
		lt[21][i] = 1;
		lt[i][0] = 1;
		lt[i][21] = 1;
		rt[0][i] = 1;
		rt[21][i] = 1;
		rt[i][0] = 1;
		rt[i][21] = 1;
	}	
}
struct node
{
	int lx, ly, rx, ry;
};
void bfs()
{
	queue<node> q;
	node st;
	st.lx = 20, st.ly = 20, st.rx = 20, st.ry = 1;
	vis[20][20][20][1] = 1;
	q.push(st);
	while(!q.empty())
	{
		node now = q.front();
		q.pop();
		int lx = now.lx, ly = now.ly;
		int rx = now.rx, ry = now.ry;
		if(lx==1 && ly==20 && rx==1 && ry==1)
			return;
		for(int i = 1; i <= 4; i++)
		{
			int llx = lx+flx[i], lly = ly+fly[i];
			int rrx = rx+frx[i], rry = ry+fry[i];
			if(lt[llx][lly]==1 && rt[rrx][rry]==1)
				continue;
			int lk = i, rk = i;
			if(lt[llx][lly] == 1)
				llx = lx, lly = ly, lk = 5;
			if(rt[rrx][rry] == 1)
				rrx = rx, rry = ry, rk = 5;
			if(vis[llx][lly][rrx][rry] == 1)
				continue;
			vis[llx][lly][rrx][rry] = 1;
			road[llx][lly][rrx][rry] = lk*5+rk;
			node nxt;
			nxt.lx = llx, nxt.ly = lly, nxt.rx = rrx, nxt.ry = rry;
			q.push(nxt);
		}
	}
}
void shuchu()
{
	int lx = 1, ly = 20, rx = 1, ry = 1;
	ans = 1, lt[1][20] = rt[1][1] = 2;
	while(1)
	{
		if(lx==20 && ly==20 && rx==20 && ry==1)
			break;
		int lk = road[lx][ly][rx][ry]/5;
		int rk = road[lx][ly][rx][ry]%5;
		if(rk == 0)
			rk = 5, lk--;
		if(lk != 5)
			prd[ans++] = lk;
		else
			prd[ans++] = rk;
		int li = 5 - lk;
		int ri = 5 - rk;
		lx = lx+flx[li], ly = ly+fly[li];
		rx = rx+frx[ri], ry = ry+fry[ri];
		lt[lx][ly] = rt[rx][ry] = 2;
	}
	ans--;
	printf("%d\n", ans);
	for(int i = ans; i >= 1; i--)
		printf("%c", f[prd[i]]);
	printf("\n");
	for(int i = 1; i <= 20; i++)
	{
		for(int j = 1; j <= 20; j++)
			printf("%c", t[lt[i][j]]);
		printf(" ");
		for(int j = 1; j <= 20; j++)
			printf("%c", t[rt[i][j]]);
		printf("\n");
	}
}
int main()
{
	read();
	bfs();
	shuchu();
	return 0;
}

K: Stack

题目大意

维护一个单调栈,给出加入 p p p个数时栈的大小,求一个合法序列。 N < = 1 0 6 N <= 10^6 N<=106

思路

拓扑排序
题目相当于给出数的相对大小关系,每次弹出的数一定比新加的数要大,新加的数要比前面的数要大,在两数之间连一条由大指向小的边,跑一遍拓扑排序即可。一定是由大指向小,因为由小指向大在弹出数后会更改自己的连边(新加入了一个比它大的数),影响到入度的计算,而由大指向小则不会,每次只会新加边。如果当前的弹出数比之前的弹出数要小,则不合法,输出 − 1 -1 1。操作的时候维护一个栈,每次向里面加入一个数,由大指向小连一条边,如果弹出了数,则向前找 s i z e + 1 − b [ i ] size+1-b[i] size+1b[i]个,由弹出数指向新加数连边。然后计算入度,有连边即入度 + 1 +1 +1,跑拓扑排序。注意拓扑排序得到的顺序是反着的,需要逆着输出。

代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6+10;
int b[maxn], n, k, p, dlt;
int nxt[maxn], s[maxn], siz, du[maxn];
void bfs()
{
	queue<int> q;
	for(int i = 1; i <= n; i++)
		if(du[i] == 0)
			q.push(i);
	int cnt = n;
	while(!q.empty())
	{
		int now = q.front();
		q.pop();
		s[now] = cnt--;
		if(--du[nxt[now]] == 0)
			q.push(nxt[now]);
	}
}
int main()
{
	scanf("%d %d", &n, &k);
	for(int i = 1; i <= k; i++)
	{
		scanf("%d", &p);
		scanf("%d", &b[p]);	
	}
	for(int i = 1; i <= n; i++)
	{
		if(b[i] != 0)
		{
			if(i - dlt < b[i])
			{
				cout << -1 << endl;
				exit(0);
			}
			dlt = i - b[i];
			if(b[i] <= siz)
			{
				siz -= siz - b[i] + 1;
				nxt[s[siz+1]] = i;
			}
		}
		nxt[i] = s[siz];
		s[++siz] = i;
	}
	for(int i = 1; i <= n; i++)
		du[nxt[i]]++;
	bfs();
	for(int i = 1; i <= n; i++)
		printf("%d ", s[i]);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值