2021牛客暑期多校(一)
A: Alice and Bob
题目大意
两人博弈,每次一个人从一堆中拿 k k k 个,同时从另一堆拿 k ∗ s ( s > = 0 ) k * s(s >= 0) k∗s(s>=0) 个,问谁先不能拿。 10000 10000 10000 组数据, N < = 5000 N <= 5000 N<=5000。
思路
暴力
S
G
SG
SG 博弈问题
结论:如果某堆石子数量是
i
i
i,另一堆石子最多只有一种数量满足后手胜。
直接记录所有后手胜的
p
a
i
r
pair
pair,每次对于一个
i
i
i,根据之前的
p
a
i
r
pair
pair 去推导是否有一个满足后手胜的搭配
j
j
j。复杂度是
O
(
N
2
l
o
g
N
)
O(N^2 log N)
O(N2logN),实际速度近似
O
(
N
)
O(N)
O(N)。(不会)
或类似筛法,对于一个后手状态
(
i
,
j
)
(i,j)
(i,j),它所转移出来的所有状态均为先手状态,起始
(
0
,
0
)
(0,0)
(0,0)为后手状态。且先手状态必被比它小的后手状态转移到。(只要能转移到后手状态即为先手状态,否则为后手状态。)
代码
#include <bits/stdc++.h>
using namespace std;
int t, n, m;
//先手-1, 后手-0
bool pr[5010][5010];
int main()
{
for(int i = 0; i <= 5000; i++)
for(int j = 0; j <= 5000; j++)
{
if(pr[i][j] == 0)
{
for(int k = 1; i+k <= 5000; k++)
for(int l = 0; j+k*l <= 5000; l++)
pr[i+k][j+k*l] = 1;
for(int k = 1; j+k <= 5000; k++)
for(int l = 0; i+k*l <= 5000; l++)
pr[i+k*l][j+k] = 1;
break;//每个i只有一个j
}
}
scanf("%d", &t);
while(t--)
{
scanf("%d %d", &n, &m);
if(pr[n][m] == 0)
printf("Bob\n");
else
printf("Alice\n");
}
return 0;
}
B: Ball Dropping
题目大意
一个球卡在一个直角等腰梯形内部,求卡着的高度。
思路
简单平面几何 浮点数
代码
#include <bits/stdc++.h>
using namespace std;
long double a, b, r, h, x, t;
int main()
{
cin >> r >> a >> b >> h;
if(b > 2*r)
cout << "Drop";
else
{
cout << "Stuck" << endl;
x = b*h/(a-b);
t = 2*r*sqrt(a*a/4 + (x+h)*(x+h))/a - x;
cout << setprecision(10) << t;
}
return 0;
}
D: Determine the Photo Position
题目大意
给出一个 n ∗ n n*n n∗n 的 01 01 01 矩阵,要用一个 1 ∗ m 1*m 1∗m 的矩阵去覆盖一段 0 0 0,问方案数。
思路
签到,数每行连续 0 0 0 的个数。
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, ans;
int main()
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; i++)
{
int num = 0;
string a;
cin >> a;
a += '1';
for(int j = 0; j <= n; j++)
{
//cout << a[j] << endl;
if(j == 0)
{
if(a[j] == '0')
num = 1;
}
else
{
if(a[j] == '0' && a[j-1] == '0')
num++;
else if(a[j] == '0' && a[j-1] != '0')
num = 1;
else
{
if(num >= m)
ans += num-m+1;
num = 0;
}
}
}
}
printf("%d\n", ans);
return 0;
}
F: Find 3-friendly Integers
题目大意
定义一个自然数是 3 − f r i e n d l y 3-friendly 3−friendly 的,如果它存在一个子串(允许前导 0 0 0)是 3 3 3 的倍数。多组数据,求 L − R L-R L−R 中 3 − f r i e n d l y 3-friendly 3−friendly 的数的个数。
思路
鸽笼原理
打表找规律?暴力
100
100
100 以内,
100
100
100 以外全部都是。
代码
#include <bits/stdc++.h>
using namespace std;
long long t, l, r;
int work(int z, int y)
{
int ans = 0;
for(int i = z; i <= y; i++)
{
int bj = 0;
int a[10], x = i, cnt = 1;
while(x > 0)
{
a[cnt++] = x%10;
x /= 10;
}
cnt--;
for(int j = 1; j <= cnt; j++)
{
int sum = 0;
for(int k = j; k <= cnt; k++)
{
sum += a[k];
if(sum % 3 == 0)
{
bj = 1;
ans++;
break;
}
}
if(bj == 1)
break;
}
}
return ans;
}
int main()
{
cin >> t;
while(t--)
{
cin >> l >> r;
if(l > 100)
cout << r-l+1 << endl;
if(r < 100)
cout << work(l, r) << endl;
else
cout << r-100+work(l, 100) << endl;
}
return 0;
}
G: Game of Swapping Numbers
题目大意
给定序列 A , B A,B A,B,需要交换恰好 k k k 次 A A A 中两个不同的数,使得 A , B A,B A,B 每个位置的绝对差值和最大。 N < = 100000 N <= 100000 N<=100000。
思路
贪心 结论
由于是绝对值的差值,所以相当于根据相对大小给
A
,
B
A,B
A,B序列正负号,每次交换
A
A
A中的数对答案的影响来自对对应正负号的影响。所以
A
,
B
A,B
A,B正负号可以任意分配,不考虑步数的最优情况为大的
n
n
n个数分配正号,小的
n
n
n个数分配负号。由于步数限制,应该尽可能先交换对答案影响最大的一对数
A
i
A_i
Ai,
A
j
A_j
Aj。我们希望一次交换使答案更优,则应该选取的两个区间
[
A
i
,
B
i
]
[A_i,B_i]
[Ai,Bi]与
[
A
j
,
B
j
]
[A_j,B_j]
[Aj,Bj]是没有相交的,即在数轴上没有重叠,不妨设数轴上
[
A
j
,
B
j
]
[A_j,B_j]
[Aj,Bj]在
[
A
i
,
B
i
]
[A_i,B_i]
[Ai,Bi]的右面,则交换带来的影响为
2
∗
[
m
i
n
(
A
j
,
B
j
)
−
m
a
x
(
A
i
,
B
i
)
]
2*[min(A_j,B_j)-max(A_i,B_i)]
2∗[min(Aj,Bj)−max(Ai,Bi)]。为使结果更优,将
m
i
n
(
A
i
,
B
i
)
min(A_i,B_i)
min(Ai,Bi),
m
a
x
(
A
j
,
B
j
)
max(A_j,B_j)
max(Aj,Bj)排序,选取差值前
k
k
k 大的数对相减取和。
结论:
n
>
2
n>2
n>2 时,恰好
k
k
k 步与至多
k
k
k 步是等价的。当
n
>
2
n>2
n>2 时,
A
A
A 中一定至少存在两个
+
+
+ 号或两个
−
-
− 号,此时如果我们交换这两个符号对应的数,则并不会使得原问题的解变得更劣。
n
=
2
n=2
n=2 时需要特殊判断,因为强制交换
k
k
k 次可能使结果更差。
代码
#include <bits/stdc++.h>
using namespace std;
int n, k, a[500010], b[500010], minn[500010], maxx[500010];
long long sum, eff;
bool cmp(int a, int b)
{
return a > b;
}
int main()
{
scanf("%d %d", &n, &k);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 1; i <= n; i++)
scanf("%d", &b[i]);
for(int i = 1; i <= n; i++)
{
sum += abs(a[i] - b[i]);
if(a[i] > b[i])
{
minn[i] = b[i];
maxx[i] = a[i];
}
else
{
minn[i] = a[i];
maxx[i] = b[i];
}
}
sort(minn+1, minn+n+1, cmp);
sort(maxx+1, maxx+n+1);
if(n == 2)
{
if(k%2 == 0)
cout << abs(a[1]-b[1]) + abs(a[2]-b[2]) << endl;
else
cout << abs(a[1]-b[2]) + abs(a[2]-b[1]) << endl;
}
else
{
int cnt = 1;
while(cnt <= n && cnt <= k)
{
if(minn[cnt] <= maxx[cnt])
break;
eff += 2*(minn[cnt] - maxx[cnt]);
cnt++;
}
cout << sum + eff << endl;
}
return 0;
}
H: Hash Function
题目大意
题目大意:给定 n n n 个互不相同的数,找一个最小的模域,使得它们在这个模域下互不相同。 n = 500000 n=500000 n=500000。
思路
假了。。。
答案一定是从
n
n
n 到
m
a
x
(
a
i
)
max(a_i)
max(ai),对a数列排序后从大到小取模看是否符合条件。答案遍历越多,每次取模操作会越少,所以直接莽。数据过于水,没判断取模后会不会都取到一个位置也过了。。。
正解貌似是
F
F
T
FFT
FFT。。。
数据加强了,代码已更新。
代码
#include <bits/stdc++.h>
using namespace std;
int n, a[500010], wz[500010];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
wz[a[i]] = -1;
}
sort(a+1, a+n+1);
for(int i = n; i <= a[n]+1; i++)
{
int bj = 1;
for(int j = n; a[j] >= i; j--)
{
int cnt = a[j] - i;
while(cnt >= 0)
{
if(wz[cnt] == -1 || wz[cnt] == i)
{
bj = 0;
break;
}
else
wz[cnt] = i;
cnt -= i;
}
if(bj == 0)
break;
}
if(bj == 1)
{
cout << i;
break;
}
}
return 0;
}
K: Knowledge Test about Match
题目大意
随机生成一个权值范围为 0 − ( n − 1 ) 0-(n-1) 0−(n−1) 的序列,你要用 0 − ( n − 1 ) 0-(n-1) 0−(n−1) 去和它匹配,匹配函数是 s q r t sqrt sqrt 。要求平均情况下和标准值偏差不能超过 4 4 4% 。
思路
根号的凹凸性和常见函数相反,所以这题很难不通过匹配去求最优解。
主要要强调的是
s
o
r
t
sort
sort ,直接
s
o
r
t
sort
sort 其实是一个很糟糕的举动,因为
s
q
r
t
sqrt
sqrt 的导函数随着
x
x
x 的增大值越来越小,而
s
o
r
t
sort
sort 后很可能是层次不齐的情况,其实反而增大了函数和。举一个例子:{
1
,
2
,
3
1,2,3
1,2,3}, {
0
,
1
,
2
0,1,2
0,1,2},
(
1
,
1
)
,
(
2
,
2
)
,
(
0
,
3
)
(1,1), (2,2), (0, 3)
(1,1),(2,2),(0,3) 会比
s
o
r
t
sort
sort 的结果好很多。
从小到大枚举
d
d
d,每次贪心去看是否存在两数差
=
d
=d
=d的数对,如果存在就暴力匹配上去。
代码
#include <bits/stdc++.h>
using namespace std;
bool cmp(int a, int b)
{
return a > b;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
int n;
scanf("%d", &n);
int a[1010], b[1010], c[1010];
for(int i = 0; i < n; i++)
{
a[i] = -1;
scanf("%d", &b[i]);
}
int cnt = 0;
for(int k = 0; ; k++)
{
for(int i = 0; i < n; i++)
{
if(b[i] == -1)
continue;
for(int j = 0; j < n; j++)
{
if(a[j] != -1)
continue;
if(abs(b[i]-j) == k)
{
a[j] = b[i];
b[i] = -1;
cnt++;
break;
}
}
}
if(cnt == n)
break;
}
for(int i = 0; i < n; i++)
printf("%d ", a[i]);
printf("\n");
}
return 0;
}
还是没补期望 D P DP DP,呜呜。。。