比赛题目训练系列11 (2020 ICPC 南京)
A. Ah, It’s Yesterday Once More
- 对于给定的 n × m n \times m n×m 的方格, 0 0 0 代表障碍, 1 1 1 代表袋鼠。有一串随机生成的长为 5 × 1 0 4 5 \times 10^4 5×104 的指令,仅包含 LRUD \text{LRUD} LRUD 字符,分别表示将所有袋鼠同时向某个方向移动(若能移动,即不经过障碍、不超出方格范围)。现要求构造一个 n × m n \times m n×m 方格图,使得对于随机生成的 500 500 500 串指令,至少有 125 125 125 个满足执行后,所有袋鼠不都在同一个方格。要求构造的袋鼠方格连通、且不含环.
- 实际上就是斜着画蛇形,同时尽可能地利用格子,构建各种分差和死胡同。
- 看看出题人构造出来的这个吧。。。我也不知道说些什么了
D. Degree of Spanning Tree
- 从图中找到一棵生成树,每一个顶点的度的大小不超过 n 2 \frac{n}{2} 2n.
E. Evil Coordinate
- 题意: 在一个图中,一个人从(0,0)进行上下左右行走。有一个地雷点(mx,my)。询问可不可以通过改变行走上下左右的顺序(不改变上下左右走的次数),可以避开雷。如果有多个,输出任意一种,如果没有,输出Impossible!
- 地雷在起点和终点都是无解的。而我们发现,构造一个 “LRUD” 的全排列,可以尽可能地避免重复。因为这个题有一个特点,如果能避开,就很容易避开。如果避不开,就无论如何都避不开。
- 而如果按照全排列的方式去走的话,可以发现,如果在某个路径上遇到地雷,如果在原理上可以避开地雷的话,那么在另一条路径上一定不会经过地雷这个点。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int maxn = 100010;
char s[maxn], ans[maxn];
char a[10] = {
'U', 'D', 'L', 'R'};
int x, y;
unordered_map<char, int> cnt;
bool check(){
int nx = 0, ny = 0, sz = 0;
for(int i = 0; i < 4; i++){
for(int j = 0; j < cnt[a[i]]; j++){
ans[sz++] = a[i];
if(a[i] == 'U') ny++;
else if(a[i] == 'D') ny--;
else if(a[i] == 'L') nx--;
else nx++;
if(nx == x && ny == y) return false;
}
}
ans[sz] = 0;
return true;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
cnt.clear();
sort(a, a + 4);
scanf("%d%d", &x, &y);
scanf("%s", &s);
for(int i = 0; s[i]; i++){
++cnt[s[i]];
}
if(cnt['R'] - cnt['L'] == x && cnt['U'] - cnt['D'] == y){
printf("Impossible\n");
continue;
}
if(x == 0 && y == 0){
printf("Impossible\n");
continue;
}
bool ok = false;
do{
if(check()){
ok = true;
printf("%s\n", ans);
break;
}
}while(next_permutation(a, a+4));
if(ok == false){
printf("Impossible\n");
}
}
return 0;
}
F. Fireworks
- 几何分布的期望 1 p \frac{1}{p} p1
- 你每做一个烟花要n分钟,释放已做好的所有烟花需要m分钟,每只烟花成功释放的概率为p。问你在采取最优策略的前提下,直到成功释放第一个烟花时最小的期望时间花费。
- 这个题的最优策略指的是,每做k个烟花释放一次,可以选择一个k,使得花费时间的期望最小。
- 既答案是求 f ( k ) = k n + m 1 − ( 1 − p ) k f(k) = \frac{kn+m}{1-(1-p)^k} f(k)=1−(1−p)kkn+m 的最小值。
- 题意:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
double n, m, p;
double f(int k) {
return (k * n + m) / (1 - pow(1 - p, k));
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%lf%lf%lf", &n, &m, &p);
p *= 1e-4;
int lb = 1, ub = 1e8;
while (ub - lb > 1) {
int m1 = (lb + ub) / 2;
int m2 = (m1 + ub) / 2;
if (f(m1) > f(m2)) lb = m1;
else ub = m2;
}
printf("%.10f\n", min(f(lb), f(ub)));
}
return 0;
}
H. Harmonious Rectangle
- 题意:给你一个n*m的矩阵,每个点都可以取三种颜色。问你有多少种染色方法可以使得矩阵中至少一对(x1,y1)与(x2,y2)满足下列式子。
(a[y1][x1] == a[y2][x1] && a[y1][x2] == a[y2][x2]) ||
(a[y1][x1] == a[y1][x2] && a[y2][x1] == a[y2][x2]))
- 考虑在矩阵中选择任意两行a和b
a1 a2 a3 a4 a5 a6 ......
b1 b2 b3 b4 b5 b6 ......
- 那么 a i , b i a_i, b_i ai,bi 共有九种组合方式。如果超过九列的话,由鸽巢原理知一定会有 ( a i , b i ) (a_i, b_i) (ai,bi) 和 ( a j , b j ) (a_j, b_j) (aj,bj) 相同。超过九行也是同理。
- 因此,9行9列之内可以dfs暴搜。但是情况会达到 3 81 3^{81} 381 多种。那么如何剪枝了。如果当前已经铺好了前 i - 1 层和第 i 层的第 j - 1 个,都没有出现符合条件的情况,但是铺完第 j 个之后符合了条件,设没有铺好的数字有 c n t cnt cnt 个,那么就把方案数加上 3 c n t 3^{cnt} 3cnt,并且不用继续搜了。这样子就不重不漏地计算。
打表代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 15;
int n, m;
typedef long long ll;
const ll mod = 1e9 + 7;
int a[maxn][maxn];
ll mod_pow(ll x, ll n) {
ll res = 1;
while (n) {
if (n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
ll ans = 0;
void dfs(int x, int y) {
for (int color = 1; color <= 3; color++) {
bool flag = true;
a[x][y] = color;
for (int i = 1; i < x && flag; i++) {
for (int j = 1; j < y && flag; j++) {
if ((a[i][j] == a[i][y] && a[x][j] == a[x][y]) ||
(a[i][j] == a[x][j] && a[i][y] == a[x][y])) {
flag = false;
ans = (ans + mod_pow(3, (n - x) * m + m - y)) % mod;
}
}
}
if (flag) {
int xx = x, yy = y;
yy++;
if (yy > m) {
yy = 1;
xx++;
}
if (xx > n) continue;
dfs(xx, yy);
}
}
a[x][y] = 0;
}
int main() {
printf("{\n");
for (n = 1; n <= 9; n++) {
printf("{");
for (m = 1; m <= 9; m++) {
memset(a, 0, sizeof a);
ans = 0;
dfs(1, 1);
printf("%lld%s",ans, m == 9 ? "" : ", ");
}
printf("}\n");
}
printf("}");
}
提交代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 15;
typedef long long ll;
const ll mod = 1e9 + 7;
ll mod_pow(ll x, ll n) {
ll res = 1;
while (n) {
if (n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
ll ans[maxn][maxn] = {
{
0, 0, 0, 0, 0, 0, 0, 0, 0},
{
0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489},
{
0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612},
{
0, 4761, 518265, 43022385, 486780060, 429534507, 792294829, 175880701, 246336683},
{
0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190, 644897553},
{
0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848},
{
0, 4767849, 460338013, 792294829, 748778899, 579440654, 236701429, 666021604, 589237756},
{
0, 43046721, 429534507, 175880701, 953271190, 412233812, 666021604, 767713261, 966670169},
{
0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245}
};
int main() {
int T;
scanf("%d", &T);
while (T--) {
ll n, m;
scanf("%lld%lld", &n, &m);
if (n == 1 || m == 1) {
//这个不能省略。因为比如1*100时,答案仍是0.
printf("0\n");
}
else if (n <= 9 && m <= 9) {
printf("%lld\n", ans[n - 1][m - 1]);
}
else {
printf("%lld\n", mod_pow(3, n * m));
}
}
return 0;
}
I. Interested in Skiing
-
题意:二维平面 [ − m , m ] × R [-m, m] \times \mathbb{R} [−m,m]×R 上有 n n n 条线段作为障碍,以端点坐标 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) 和 ( x 2 , y 2 ) (x_2, y_2) (x2,y2) 表示,起点为 ( 0 , − inf ) (0, -\inf) (0,−inf) 的一个点以 v y v_y vy 的速度向 y \text{y} y 轴正方向移动,终点为 ( 0 , inf ) (0, \inf) (0,inf). 求最小的 v x ∗ v_x^* vx∗,使得当水平速度 v x > v x ∗ v_x \gt v_x^* vx>vx∗ 时,能从起点移动到终点,无解则输出 − 1 -1 −1 。
-
标解
可以证明,最优路线一定是走线段端点的折线。考虑端点和 ( x , − i n f ) , ( x , i n f ) (x,-inf), (x, inf) (x,−inf),(x,inf) 这些起点和终点,先 n 3 n^3 n3 预处理点之间能不能走,然后二分 v x v_x vx ,斜率大于 v y v x \frac{v_y}{v_x} vxvy 的都能走,dfs/bfs 看能不能从起点走到终点即可
- 思路很简单,但是计算几何嘛,细节还是蛮多的。好长时间才过掉这个题目。而且,一定好好理解题目的意思。计算几何一定要看清题目的数据范围,以及好好体会一下题目的意思!这次理解错了题意qwq。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#define x first
#define y second
using namespace std;
typedef pair<double, double> P;
const double eps = 1e-8, INF = 1e18;
const int maxn = 110;
int sign(double x){
if (fabs(x) < eps) return 0;
if (x < 0) return -1;
return 1;
}
P operator + (P a, P b) {
return {
a.x + b.x, a.y + b.y };
}
P operator - (P a, P b) {
return {
a.x - b.x, a.y - b.y };
}
double cross(P a, P b) {
return a.x * b.y - b.x * a.y;
}
double operator * (P a, P b) {
return a.x * b.x + a.y * b.y;
}
bool connected[2 * maxn][2 * maxn];
int N, sz;
double vy, M;
P st[maxn], ed[maxn];
double tangent[2 * maxn][2 * maxn];
vector<P> points;
P Start = {
0, -INF }, End = {
0, INF };
bool segment_intersection(P a1, P a2, P b1, P b2) {
//if (a1 == b1 && a2 == b2) return true;
double c1 = cross(a2 - a1, b1 - a1), c2 = cross(a2 - a1, b2 - a1);
double c3 = cross(b2 - b1, a2 - b1), c4 = cross(b2 - b1, a1 - b1);
//小心这个地方不要写成 <= , 要写成 < 。因为只要没有顶到边界,如果一个线段的端点在另一个线段上,我们认为也是可以通过的。
return sign(c1) * sign(c2) < 0 && sign(c3) * sign(c4) < 0;
}
double calc(P a, P b) {
if (!sign(a.x - b.x)) return INF;
return fabs(a.y - b.y) / fabs(a.x - b.x);
}
bool bfs(double m) {
queue<int> que;
static bool vis[maxn * 2];
memset(vis, false, sizeof vis);
que.push(0);
vis[0] = true;
while (que.size()) {
int u = que.front(); que.pop();
vis[u] = true;
// 到达终点,小心终点是 1 不是 sz - 1
if (u == 1) return true;
for (int i = 0; i < sz; i++) {
if (connected[u][i] && !vis[i] && tangent[u][i] > m) {
//pre[i] = u;
que.push(i);
}
}
}
return false;
}
int main() {
scanf("%d%lf%lf", &N, &M, &vy);
points.push_back(Start);
points.push_back(End);
for (int i = 0; i < N; i++) {
scanf("%lf%lf%lf%lf", &st[i].x, &st[i].y, &ed[i].x, &ed[i].y);
points.push_back(st[i]);
points.push_back(ed[i]);
}
sz = points.size();
for (int i = 0; i < sz; i++) {
for (int j = i + 1; j < sz; j++) {
bool flag = true;
for (int k = 0; k < N && flag; k++) {
if (segment_intersection(points[i], points[j], st[k], ed[k])) {
//printf("### %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f\n", points[i].x, points[i].y, points[j].x, points[j].y, st[k].x, st[k].y, ed[k].x, ed[k].y);
flag = false;
}
//当横坐标是M的时候,a1a2其实就是obstacle的端点,这样子也是不合法的。因为题目中说不可以碰到端点。
if (!sign(fabs(points[i].x) - M) || !sign(fabs(points[j].x) - M)) flag = false;
}
if (points[i].y > points[j].y) connected[j][i] = flag;
else if(points[i].y < points[j].y)connected[i][j] = flag;
tangent[i][j] = tangent[j][i] = calc(points[i], points[j]);
}
}
//小心这个地方是 INF,别写成M,题目没说 vx <=M !
double lb = 0, ub = INF;
for (int i = 0; i < 100; i++) {
double mid = (lb + ub) / 2;
if (bfs(vy / mid)) ub = mid;
else lb = mid;
}
if (sign(lb - INF) != 0) printf("%.15f\n", lb);
else printf("-1\n");
return 0;
}
J. Just Another Game of Stones
- 吉老师线段树,复杂度是 O ( n log 2 n ) . O(n\log^2 n). O(nlog2n).
- 吉司机线段树主要是用来解决序列区间上满足部分满足一定性质的修改操作的,比方说把区间中小于x的数改成x,大于x的数改成x,甚至是区间取并和区间取或(实际上也就是区间中的每个数的每一位进行类似小于x改成x那种操作)下面主要说一下怎么处理区间中把小于x 的数改成x,其他的可以类比来做;
- 首先要记一个中间量,也就是次小值;然后再把线段树上每一个区间的最小值及其个数记录下来;
- 对于每次修改,设要和x进行对比;
- 如果当前找到的区间的最小值>=x,那么肯定没必要再往下处理了;
- 如果x>最小值但x<次小值(注意x严格小于次小值)那么只需要把最小值改成x,记录的最小值的数目都不需要改,这样线段树上维护的其他信息也可以很方便的修改(例如区间和一类的信息)
- 如果x>=次小值,那递归处理左右子区间再合并信息回来;
- 题意。有一个线段树,维护两个操作
- 1 l r:对于所有 i ∈ [ l , r ] i \in [l, r] i∈[l,r] 执行 a i = m a x ( a i , x ) a_i = max(a_i, x) ai=max(ai,x)
- 2 l r x:对于区间 [ l , r ] [l, r] [l,r] 内的 a i a_i ai 加上 x 这一共 r − l + 2 r - l + 2 r−l+2 个数进行尼姆游戏,问先手必胜的情况下第一次能有多少种不同的取石子方案。
- 根据尼姆游戏,我们知道,我们先把所有值取异或得到 res,然后记 res 最高位1是第 k 位,查一下有多少个 a i a_i ai 的第 k 位是1.
- 对于第一个操作。我们记录这个区间最小值、次小值、最小值的个数。那么,如果传进来的x >= 最小值,那么什么都不变;如果最小值 < x < 次小值,那么就把次小值变成 x,其他的不用变;如果 x >= 次小值,那么就递归处理两个儿子。
- 对于第二个操作,因为是取异或,且需要知道位的信息,所以我们对每一位维护一个信息。而该位异或的结果取决于该位的个数。
- 数组一定要开大一点,千万别吝惜。因为有时候会出现莫名其妙的错误。就比如这道题,把sum数组从30变成35后就不出现段错误了。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn = 200010;
struct node {
int l, r;
int mmin, se_min, num; //最小值,次小值,最小值个数
int sum[35];
}tr[maxn * 4];
int res_sum[35];
int w[maxn];
void change(int u, int pre, int cur) {
for (int i = 0; i < 30; i++) {
if ((pre >> i) & 1) tr[u].sum[i] -= tr[u].num;
if ((cur >> i) & 1) tr[u].sum[i] += tr[u].num;
}
}
void pushup(int u) {
tr[u].num = 0;
int left = 2 * u, right = 2 * u + 1;
for (int i = 0; i < 30; i++)
tr[u].sum[i] = tr[left].sum[i] + tr[right].sum[i];
tr[u].mmin = min(tr[left].mmin, tr[right].mmin);
tr[u].se_min = min(tr[left].se_min, tr[right].se_min);
//下面修改 tr[u].num的值,顺便更新 tr[u].se_min 的值
if (tr[u].mmin == tr[left].mmin) {
tr[u].num += tr[left].num;
}
else {
tr[u].se_min = min(tr[u].se_min, tr[left].mmin);
}
if (tr[u].mmin == tr[right].mmin) {
tr[u].num += tr[right].num;
}
else {
tr[u].se_min = min(tr[u].se_min, tr[right].mmin);
}
}
void pushdown(int u) {
//改变mmin和sum
int left = 2 * u, right = 2 * u + 1;
if (tr[u].mmin > tr[left].mmin) {
change(left, tr[left].mmin, tr[u].mmin);
tr[left].mmin = tr[u].mmin;
}
if (tr[u].mmin > tr[right].mmin) {
change(right, tr[right].mmin, tr[u].mmin);
tr[right].mmin = tr[u].mmin;
}
}
void build(int u, int l, int r) {
if (l == r) {
tr[u].l = l, tr[u].r = r;
tr[u].mmin = w[l], tr[u].se_min = 2e9, tr[u].num = 1;
change(u, 0, w[l]);
}
else {
tr[u].l = l, tr[u].r = r;
int mid = (l + r) / 2;
build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int x) {
if (x <= tr[u].mmin) return;
if (l <= tr[u].l && tr[u].r <= r && x < tr[u].se_min) {
//因为 x < tr[u].se_min,因此更新后最小值的数目不变。
change(u, tr[u].mmin, x);
tr[u].mmin = x;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
if (l <= mid) modify(2 * u, l, r, x);
if (r > mid) modify(2 * u + 1, l, r, x);
pushup(u);
}
void query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
for (int i = 0; i < 30; i++) {
res_sum[i] += tr[u].sum[i];
}
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
if (l <= mid) query(2 * u, l, r);
if (r > mid) query(2 * u + 1, l, r);
}
int main() {
int N, Q;
scanf("%d%d", &N, &Q);
for (int i = 1; i <= N; i++) scanf("%d", &w[i]);
build(1, 1, N);
while (Q--) {
int op, l, r, x;
scanf("%d%d%d%d", &op, &l, &r, &x);
if (op == 1) {
modify(1, l, r, x);
}
else {
memset(res_sum, 0, sizeof res_sum);
query(1, l, r);
int res = x;
for (int i = 0; i < 30; i++) {
if (res_sum[i] & 1) res ^= (1 << i);
}
if (!res) {
printf("0\n");
continue;
}
int h = log2(res);
printf("%d\n", res_sum[h] + ((x >> h) & 1));
}
}
return 0;
}
J题实际上是本场唯一的一个套路题,前半考察对nim游戏的基础理解,后半考察Segment Beats。我组题会偏向于在比赛中设置这么一个题目,可以说是给那些有准备的队伍一个糖果。而实际上我之所以选用Segment Beats就是因为在不久前ccpc网络赛中就出现过一个,可以理解为我在考察大家平时的补题情况。
K. K Co-prime Permutation
- 构造一个排列,使得恰有 k k k 个满足 g c d ( p i , i ) = 1 gcd(p_i, i) = 1 gcd(pi,i)=1.
- 容易想到一个结论, g c d ( i , i + 1 ) = 1 gcd(i, i + 1) = 1 gcd(i,i+1)=1。因此,可以构造前 k k k 个数依次往后移动一个,后 n − k n - k n−k 个数原封不动。注意特判 k = 1 k = 1 k=1 时,答案为 -1.
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 1000010;
int ans[maxn], N, K;
int main() {
scanf("%d%d", &N, &K);
if (K == 0) printf("-1\n");
else {
for (int i = 1; i <= K; i++) {
ans[i] = i + 1 > K ? i + 1 - K : i + 1;
}
for (int i = K + 1; i <= N; i++) {
ans[i] = i;
}
for (int i = 1; i <= N; i++) {
printf("%d%c", ans[i], i == N ? '\n' : ' ');
}
}
return 0;
}
L. Let’s Play Curling
- 给定一个长度为n的a数组和长度为m的b数组,每个数组中的数代表着当前各个队伍有的stone的位置。选定一个位置c,如果存在一个红队的石头到c的距离小于所有蓝队的石头到c的距离,那么红队积一分,问c选最合适的地方的时候红队的最高得分是多少,如果不存在方案则输出Impossible
- 也就是说找到一个点 c,看看 c − b i c - b_i c−bi 和 c + b i c + b_i c+bi 这个范围内有多少个 a 数组里面的数字啦。其实挺简单的,就是看在 b[0] 左边有多少个 a, b i b_i bi 和 b i + 1 b_{i+1} bi+1 之间有多少个a, b M b_{M} bM 后面有多少个a。找到最大值即可。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100010;
int a[maxn], b[maxn], N, M;
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &N, &M);
for (int i = 0; i < N; i++) scanf("%d", &a[i]);
for (int i = 0; i < M; i++) scanf("%d", &b[i]);
sort(a, a + N), sort(b, b + M);
int ans = 0;
//c 在 b 的中间
for (int i = 1; i < M; i++) {
int l = upper_bound(a, a + N, b[i - 1]) - a;
int r = lower_bound(a, a + N, b[i]) - a;
r--;
ans = max(ans, r - l + 1);
}
//c在 b 的两边
int l = 0, r = lower_bound(a, a + N, b[0]) - a;
r--;
ans = max(ans, r - l + 1);
l = upper_bound(a, a + N, b[M - 1]) - a, r = N - 1;
ans = max(ans, r - l + 1);
if (ans == 0) printf("Impossible\n");
else printf("%d\n", ans);
}
}
M. Monster Hunter
- 树形dp,挖坑。