https://codeforces.com/contest/1153

E. Serval and Snake

 

题意:交互题。给定一个n*n的网格,n小于1001,里面有一条链,可能是弯曲的,不超过2019次询问,每次询问对一个矩形,有几次经过这个矩形边界。求链的两个端点坐标。

二分

一个矩形如果包含了链的几个节点,那就可以看成这几个节点缩点了。原来是一条链,是一个欧拉路径,缩点后必定还是欧拉路径或欧拉回路。

如果矩形包含一个端点,则有奇数个度,如果包含两个或者0个,则有偶数个度。

考虑询问每一行,如果这一行包含一个端点,则有奇数度,确定行后二分确定列坐标。

如果两个端点在同一行,则无法得到结果。但是这两点就必定不在同一列了,所以再询问每一列,确定列后同样二分确定行坐标。因为知道是同一行了,所以只要二分一次就得到两个的行坐标了。

最差情况询问每一行每一列+一次二分,2010次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
const int N = 1e6 + 10;
int n;
int qry(int x1, int y1, int x2, int y2) {
int res;
cout << "? " << x1 << " " << y1 << " " << x2 << " " << y2 << endl;
cin >> res;
return res;
}
void ans(int x1, int y1, int x2, int y2) {
cout << "! " << x1 << " " << y1 << " " << x2 << " " << y2 << endl;
exit(0);
}
int sol(int r) {
int L = 1, R = n;
while (L < R) {
int mid = (L + R) / 2;
if (qry(r, 1, r, mid) & 1)R = mid;
else L = mid + 1;
}
return L;
}
int sol2(int c) {
int L = 1, R = n;
while (L < R) {
int mid = (L + R) / 2;
if (qry(1, c, mid, c) & 1)R = mid;
else L = mid + 1;
}
return L;
}
int main() {
scanf("%d", &n);
int q = 0;
for (int i = 1; i <= n; i++) {
if (qry(i, 1, i, n) & 1) { q = i; break; }
}
if (q != 0) {
int x1 = q, y1 = sol(q);
for (int i = n; i >= 1; i--) {
if (qry(i, 1, i, n) & 1) { q = i; break; }
}
int x2 = q, y2 = sol(q);
ans(x1, y1, x2, y2);
}
else {
for (int i = 1; i <= n; i++) {
if (qry(1, i, n, i) & 1) { q = i; break; }
}
int x1 = sol2(q), y1 = q;
for (int i = n; i >= 1; i--) {
if (qry(1, i, n, i) & 1) { q = i; break; }
}
int x2 = x1, y2 = q;
ans(x1, y1, x2, y2);
}
return 0;
}

 

F. Serval and Bonus Problem

 

题意:给定一个范围 00ll 的 x 轴,要随机选出 n 个线段,它们的 2n 个端点分出 2n+1 个小间隔,求所有被至少 k 个线段覆盖的小间隔的长度和的数学期望。1kn2000,1l1091\leq k \leq n \leq 2000,1\leq l\leq 10^9

dp,数学期望

由于是完全随机的,所以每个小间隔的长度期望都相等,所以都是 l2n+1\frac{l}{2n+1}。那么只要求出每一个小间隔被覆盖 k 次的概率,再遍历所有小间隔,乘上长度再求和就是答案。

假设小间隔标号0到2n,点标号1到2n,则第 i 个间隔在第 i 和第 i+1 个点之间。

对于一个间隔,如果一条线段的左端点是这个间隔的左端点,则这个间隔被覆盖。

dp[i][j]dp[i][j] 表示到前 i 个点,有 j 个点没有闭合的方案数。

对于点 i,它可以不闭合,则由 dp[i1][j1]dp[i-1][j-1] 转移;也可以闭合,则前 i-1 个点有 j+1 个没闭合,点 i 可以和其中任一个闭合,由 dp[i1][j+1](j+1)dp[i-1][j+1]*(j+1) 转移。

对于第 i 个间隔,没有闭合的点数 jj 就是它被覆盖的次数。

前 i 个点中 j 个没闭合的点与后 2n-i 个点中 j 个没闭合的点配对,形成线段,那么还应该再反向求一个dp,再得到反向的 dp[2ni][j]dp[2n-i][j],但是要知道这是非常对称的,反向和正向是一样的,所以直接用正向的dp值即可。

还要注意前 j 个点和后 j 个点可以以任意顺序配对形成线段,所以还应该乘以 j 的全排列。

dp[2n][0]dp[2n][0] 就是2n个点全都闭合的方案数,也就是全部的方案数,也就是分母。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
const int N = 1e6 + 10;
int n, k;
ll l, p[2020];
ll Pow(ll a, ll b) {
ll res = 1ll;
while (b) {
if (b & 1)res = res * a%mod;
a = a * a%mod;
b >>= 1;
}
return res;
}
ll dp[4020][2020], ans;
int main() {
scanf("%d%d%lld", &n, &k, &l);
p[0] = 1ll;
for (int i = 1; i <= n; i++)p[i] = p[i - 1] * i%mod;
dp[1][1] = 1ll;
for (int i = 2; i <= 2 * n; i++) {
for (int j = 0; j <= min(i, n); j++) {
dp[i][j] = dp[i - 1][j + 1] * (j + 1) % mod;
if (j > 0)dp[i][j] = (dp[i][j] + dp[i - 1][j - 1]) % mod;
}
}
for (int i = 1; i < 2 * n; i++) {
for (int j = k; j <= min(i, n); j++) {
ans = (ans + dp[i][j] * dp[2 * n - i][j] % mod*p[j] % mod) % mod;
}
}
ans = ans * l % mod * Pow(2 * n + 1, mod - 2) % mod * Pow(dp[2 * n][0], mod - 2) % mod;
printf("%lld\n", ans);
return 0;
}