2021牛客暑期多校(二)
C: Draw Grids
题目大意
一张 n ∗ m n*m n∗m的点图,两个人轮流选择一条边连接相邻两个点,要求不能连出封闭图形,给出 n n n和 m m m,问第一个人能否赢。
思路
因为不能连出封闭图形,所以将问题转化为将这些点连成一棵树,树的边数是奇还是偶(点数 n ∗ m n*m n∗m是奇还是偶)。赛时没看出来,硬是手膜了一遍 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)
(k2xb–xa)/(k2–1.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/(k2–1.0))∗sqrt((xb−xa)2+(yb−ya)2+(zb−za)2),再算两球心距离。结果分类讨论,球心距离大于两半径之和直接输出
0
0
0(相离),球心距离加一半径小于另一半径输出小球体积(包含),其余相交的情况根据球冠公式
π
3
h
2
(
3
r
−
h
)
\frac{π}{3}h^2(3r - h)
3πh2(3r−h) 算相交体积,
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 1≤k≤n≤5000, 0 ≤ a < b < 1 0 5 0≤a<b<10^5 0≤a<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][j−1]+bk+1−ai),
k
k
k从
1
−
(
i
−
1
)
1-(i-1)
1−(i−1)枚举,复杂度为
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][j−1]+bk+1的最大值,即
k
k
k的枚举是重复的,所以用单调队列维护
d
p
[
k
]
[
j
−
1
]
+
b
k
+
1
dp[k][j-1] + b_{k+1}
dp[k][j−1]+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[i−1][j−1]入队,再通过
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,
0—min(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) (20∗20),玩家同时操控两只企鹅,一只在 ( 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+1−b[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;
}