「考试总结」ZROI-21-CSP7连-DAY4 总结

#T1 扫雷

#题意简述

一个 \(n\times m(n,m\leq200)\) 的网格,每个格子中的数字表示以该格子为中心的 \(3\times3\) 的方格中地雷的数量,问总共有多少个地雷。

#大体思路

我们不需要知道地雷具体怎么摆放,所以只要找到一组点各自的势力范围能恰好覆盖整张地图即可,于是我们可以直接上下、左右各相隔三个进行选点。

#Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const int N = 210;

int n, m, a[N][N], ans;

int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
scanf("%d", &a[i][j]);
/*特别注意填充开始的边界*/
for (int i = n % 3 ? 1 : 2; i <= n; i += 3)
for (int j = m % 3 ? 1 : 2; j <= m; j += 3)
ans += a[i][j];
printf("%d", ans);
return 0;
}

#T2 翻转

#题意简述

有一个 \(n\times m(n,m\leq100)\) 的 01 矩阵,现在需要将所有位置变为 \(1\),改变的规则如下:

  • 对于 \((a,b)\),若 \(\exists x\in[1,n],y\in[1,m]\),其中 \((a,y),(x,b),(x,y)\) 均为 \(1\),那么修改 \((a,b)\) 的代价为 \(3\),否则为 \(4\).

问最小代价。

#大体思路

考虑如何判断是否 \(\exists x\in[1,n],y\in[1,m]\),其中 \((a,y),(x,b),(x,y)\) 均为 \(1\),不难发现,如果将 \((i,j)=1\) 看作将第 \(i\) 行(\(L.i\))和第 \(j\) 列(\(A.j\))连接的话,那么意味着 \(L.a,L.x,A.b,A.y\) 应当在同一连通块内,而最终的状态一定有全图为一个连通块,于是我们一定需要将所有初始连通块连成一块,这需要以 \(4\) 的代价修改连通块数量减 \(1\) 个点,最后剩下的点的修改代价都为 \(3\).

#Code

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
const int N = 100010;
const int INF = 0x3fffffff;

/*用并查集维护连通块信息*/
struct DSU {
int siz[N], f[N], tot;

inline void init(int x) {
tot = x;
for (int i = 0; i <= x; ++ i)
f[i] = i, siz[i] = 1;
}

inline int find(int x) {
while (x != f[x]) x = f[x] = f[f[x]];
return x;
}

inline void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
if (siz[x] > siz[y]) swap(x, y);
f[x] = y, siz[y] += siz[x], -- tot;
}
} dsu;

int n, m, tcnt, ans; char c;

int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> m; dsu.init(n + m);
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j) {
cin >> c; if (c == '1') dsu.merge(i, j + n), ++ tcnt;
}
ans = (dsu.tot - 1) * 4 + (n * m - tcnt - dsu.tot + 1) * 3;
cout << ans; return 0;
}

#T3 斜率

#题意简述

平面直角坐标系上 \(n(n\leq10^6)\) 个点,可任选 \(2\) 个点连直线,找到斜率最接近 \(\frac P Q\) 的直线的斜率。

#大体思路

我们考虑有一条斜率为 \(\frac PQ\) 的直线在坐标系上平移,我们可以得到所有点按此方向在 \(x\) 轴上的投影,不难发现,斜率最接近 \(\frac PQ\) 的两个点的投影一定相邻,这一点简单画图不难发现,于是我们直接按照投影位置进行排序即可。

#Code

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
#define db double
#define ll long long

const int N = 1000010;
const db INF = 1e10;

template <typename T> inline T ABS(const T &x) {return x < 0 ? -x : x;}

double tana, ans_gap = INF;
int n; ll p, q, nx, ny;

struct Point {
ll x, y;
bool operator < (const Point & b) const {
return -x * p + y * q < -b.x * p + b.y * q;
}
} a[N];

ll gcd(ll x, ll y) {return y ? gcd(y, x % y) : x;}

db calc(Point x, Point y) {
return x.x == y.x ? INF : (db)(x.y - y.y) / (db)(x.x - y.x);
}

int main() {
scanf("%d%lld%lld", &n, &p, &q);
tana = (db)p / (db)q;
for (int i = 1; i <= n; ++ i)
scanf("%lld%lld", &a[i].x, &a[i].y);
sort(a + 1, a + n + 1);
for (int i = 1; i < n; ++ i) {
db now_gap = ABS(calc(a[i], a[i + 1]) - tana);
if (now_gap < ans_gap) {
ans_gap = now_gap;
nx = ABS(a[i].x - a[i + 1].x);
ny = ABS(a[i].y - a[i + 1].y);
}
}
ll g = gcd(ny, nx);
printf("%lld/%lld", ny / g, nx / g);
return 0;
}

#T4 任务

#题意简述

\(n(n\leq 350)\) 个人和 \(n\)不同的任务,第 \(i\) 个人高兴当且仅当他被分到 \(i\) 个任务,问将所有任务都分配下去后至少有一个人高兴的方案数(\(\bmod10^9+7\))。

#大体思路

这种计数问题还是要去尝试 DP,设 \(f_{i,j}\) 表示前 \(i\) 个人分 \(j\) 个任务,至少有 \(1\) 个人高兴的方案数,考虑第 \(i\) 个人是否高兴。

\(i\) 个人高兴,则可以挑出 \(i\) 个直接给第 \(i\) 个人,剩下的 \(j-i\) 个可以随意的给 \(i-1\) 个人,于是方案总数为 \(\dbinom ji\cdot(i-1)^{j-i}\)

如果第 \(i\) 个人不高兴,那么设给了第 \(i\) 个人 \(k(k\ne i)\) 个任务,那么此时的方案数为前 \(i-1\) 个人分 \(j-k\) 个的方案数,于是该情况的总方案数为 \[ \sum\limits_{k=1}^n[k\ne i]\dbinom jkf_{i-1,j-k}, \]

#Code

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
#define ll long long

const int N = 510;
const int MOD = 1e9 + 7;
const int INF = 0x3fffffff;

int n; ll C[N][N], f[N][N];

inline void init_C(int x) {
for (int i = 0; i <= x; ++ i) C[i][0] = C[i][i] = 1;
for (int i = 1; i <= x; ++ i)
for (int j = 1; j < i; ++ j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
}

ll fpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) (res *= a) %= MOD;
(a *= a) %= MOD, b >>= 1;
}
return res;
}

int main() {
scanf("%d", &n); init_C(n);
for (int i = 1; i <= n; ++ i)
for (int j = 0; j <= n; ++ j) {
if (j >= i) f[i][j] = fpow(i - 1, j - i) * C[j][i] % MOD;
for (int k = 0; k <= j; ++ k) {
if (k == i) continue;
(f[i][j] += f[i - 1][j - k] * C[j][k] % MOD) %= MOD;
}
}
printf("%lld", f[n][n]);
return 0;
}