引入
- 人生第一次 CF
也是难得的一次时间在下午的比赛
在读题上浪费了好多时间,药丸
因为不熟悉赛制, D 题不检查就提交
结果 WA 了两发
题目
洛谷 RemoteJudge
Codeforces
A
题意
- 给定 n 和 k
- 有 n 个人,每个人需要 2 张红纸, 5 张绿纸, 8 张蓝纸
- 需要买一些纸,但一次必须买一堆 k 张同色的纸
- 求至少需要买几堆纸
- 1≤n,k≤108
题解
本题的主要难点在理解英文题目
- ⌈k2n⌉+⌈k5n⌉+⌈k8n⌉
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
int n, k;
int main()
{
std::cin >> n >> k;
std::cout << (2 * n + k - 1) / k + (5 * n + k - 1) / k
+ (8 * n + k - 1) / k << std::endl;
return 0;
}
B
题意
- 给定一个数列 a ,满足 ai=i×(−1)i
-
q 组询问
- 每次询问 ∑i=lrai
-
1≤q≤103 , 1≤l≤r≤109
题解
- 如果 r−l+1 是奇数,就直接转化成区间 [l,r−1] 的和再加上 ar ,显然区间 [l,r−1] 的长度是偶数
- 如果区间长度 r−l+1 是偶数,那么
- (1) l 是奇数时区间和为 2r−l+1
- (2) l 是偶数时区间和为 −2r−l+1
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
int q;
int main()
{
int l, r;
q = read();
while (q--)
{
l = read(); r = read();
int res = l & 1 ? r - l + 1 >> 1 : - (r - l + 1 >> 1);
if (!(r - l & 1))
{
if (r & 1) res -= r;
else res += r;
}
printf("%d\n", res);
}
return 0;
}
C
题意
- 给定一个 n×m 的网格,对于第 i 行第 j 列,如果 i+j 为偶数则 (i,j) 为白色,否则为黑色
- 进行两次操作
- 第一次把一个子矩形全部染黑
- 第二次把一个子矩形全部染白
- 最后求棋盘上白、黑的格子数
- 多组数据(不超过 103 组), 1≤n,m≤109
题解
- 操作之前一个子矩形的黑白格子数很容易计算
- 如果格子数为偶数,则黑白格子各占一半
- 否则与这个子矩形的左下角的颜色同色的格子数是该子矩形总格子数除以二后上取整
- 将两个子矩形分开处理即可
- 如果两个子矩形有交则需要处理相交部分,可以利用容斥的思想
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}
typedef long long ll;
int n, m, X1, Y1, X2, Y2, X3, Y3, X4, Y4;
ll w, b;
ll rects(int xl, int yl, int xr, int yr)
{
if (!(1ll * (xr - xl + 1) * (yr - yl + 1) & 1))
return 1ll * (xr - xl + 1) * (yr - yl + 1) >> 1;
if (xl + yl & 1) return 1ll * (xr - xl + 1) * (yr - yl + 1) >> 1;
return (1ll * (xr - xl + 1) * (yr - yl + 1) >> 1) + 1;
}
ll stcer(int xl, int yl, int xr, int yr)
{
return 1ll * (xr - xl + 1) * (yr - yl + 1) - rects(xl, yl, xr, yr);
}
void work()
{
n = read(); m = read();
X1 = read(); Y1 = read(); X2 = read(); Y2 = read();
X3 = read(); Y3 = read(); X4 = read(); Y4 = read();
if (1ll * n * m & 1) w = (1ll * n * m >> 1) + 1, b = w - 1;
else w = b = 1ll * n * m >> 1;
w -= rects(X1, Y1, X2, Y2) + rects(X3, Y3, X4, Y4);
b -= stcer(X1, Y1, X2, Y2) + stcer(X3, Y3, X4, Y4);
w += 1ll * (X2 - X1 + 1) * (Y2 - Y1 + 1);
b += 1ll * (X4 - X3 + 1) * (Y4 - Y3 + 1);
if (Max(X1, X3) <= Min(X2, X4) && Max(Y1, Y3) <= Min(Y2, Y4))
{
w += rects(Max(X1, X3), Max(Y1, Y3), Min(X2, X4), Min(Y2, Y4));
b += stcer(Max(X1, X3), Max(Y1, Y3), Min(X2, X4), Min(Y2, Y4));
w -= 1ll * (Min(X2, X4) - Max(X1, X3) + 1)
* (Min(Y2, Y4) - Max(Y1, Y3) + 1);
}
std::cout << w << " " << b << std::endl;
}
int main()
{
int T = read();
while (T--) work();
return 0;
}
D
题意
- 给定一个边长为 2n 的正方形
- 需要进行恰好 k 次切割
- 每次把一个边长为 2a ( a>0 )的正方形横切一下竖切一下,切割成 4 个边长为 2a−1 的正方形
- 求是否存在一种切割方案使得下面的条件全部满足
- (1)左下角和右上角的正方形大小相同
- (2)存在一条路径,只经过与左下角和右上角大小相同的正方形,从左下角走到右上角。这条路径中相邻的两个正方形必须有公共边(只有公共顶点不行),设左下角和右上角的正方形大小都为 2x
- 如果存在一种切割方案,则需要输出 x
- 多组数据(不超过 103 组), 1≤n≤109 , 1≤k≤1018
题解
- 首先,如果 n>31 则存在一种方案 x=n−1 ,直接输出即可
- 否则枚举 x ,下面以 x=n−3 的情况为例
- 首先构造出这条路径
- 可以递推求出转到如上图这种状态的最少切割次数 q1
- 横切 2n−x−1 刀,竖切 2n−x−1 刀,均分成 22(n−x) 个规模为 2x 的正方形
- 然后把除第一行、第一列之外的,所有规模为 2x 的 (2n−x−1)2 个正方形全部切成 1×1 的小正方形,这样就得到了答案为 x 时的最多切割次数 q2
- 如果 q1≤k≤q2 则合法
- 复杂度 O(tmin(n,31))
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
const int N = 40;
typedef long long ll;
int n;
ll k, f[N], g[N], p2[N];
bool greaters(ll a, ll b, ll c)
{
if (c < 0) return 0;
if (!b) return a * b < 0;
return a < (c + b - 1) / b;
}
void work()
{
int i, j;
ll delta;
std::cin >> n >> k;
if (n > 31) return (void) printf("YES %d\n", n - 1);
Rof (i, n - 1, 0)
{
if (f[n - i] > k) continue;
ll grids = 1ll * (p2[n - i] - 1) * (p2[n - i] - 1);
if (greaters(g[i], grids, k - g[n - i])) continue;
return (void) printf("YES %d\n", i);
}
puts("NO");
}
int main()
{
int i, j, T; std::cin >> T;
f[1] = p2[0] = g[1] = 1;
For (i, 1, 35) p2[i] = 2ll * p2[i - 1];
For (i, 2, 35) f[i] = 3ll * f[i - 1] + 1,
g[i] = 4ll * g[i - 1] + 1;
while (T--) work();
return 0;
}
E
题意
- 给定一个 n×m 字符矩阵
- 求有多少个非空子矩阵
- 满足能够对于子矩阵的每一行,将该行的字符重新排列
- 使得子矩阵的每行每列都是回文串
- 字符集为小写英文字母
- 1≤n,m≤250
算法: Hash + Manacher
- 小清新字符串题
- 先转化下一个子矩阵合法的条件
- (1)子矩阵的每一行都能重组成回文串(出现次数为奇数的字符不超过 1 个)
- (2)对于子矩阵的任意对称的两行 i 和 j 以及任意字符 c ,都满足子矩阵第 i 行字符 c 的出现次数与第 j 行字符 c 的出现次数相等
- 条件(1)很容易预处理出
- 而条件(2)可以对于每一行 i 的每一个区间 [l,r] ,预处理出区间内所有字符的出现次数组成的哈希值 hash[i][l][r]
- 那么条件(2)就转化成了如果子矩阵跨列 [l,r] ,第 i 行与第 j 行对称,则需要满足 hash[i][l][r]=hash[j][l][r]
- 有了这个条件,我们就可以开始统计了
- 枚举子矩阵跨列的范围 [l,r]
- 问题转化成有多少个区间 [a,b] 满足
- (1)对于任意的 a≤i≤b 都满足第 i 行的 [l,r] 能重组成回文串
- (2)对于任意的 a≤i,j≤b , i−a=b−j (其实就是 i,j 在区间 [a,b] 内对称),有 hash[i][l][r]=hash[j][l][r]
- 发现(2)表示 hash[a...b][l][r] 是一个回文数组
- 对 hash[][l][r] 数组跑 Manacher 统计,注意处理掉条件(1)
- 复杂度 O(nm2)
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 255, M = 26, L = N << 1, ZZQ = 998244353, YSY = 1e9 + 7;
int n, m, h1[N][N][N], h2[N][N][N], pw1[M], pw2[M], str1[L], str2[L],
ans, R[L], cnt[M];
char s[N][N];
bool pal[N][N][N];
void manacher(int n)
{
int i, mx = 0, pos;
For (i, 1, n)
{
R[i] = mx > i ? Min(mx - i, R[(pos << 1) - i]) : 1;
while (str1[i - R[i]] == str1[i + R[i]]
&& str2[i - R[i]] == str2[i + R[i]]) R[i]++;
if (i + R[i] > mx) mx = i + R[i], pos = i;
}
}
void solve(int l, int r)
{
int i, tot = 0;
str1[0] = str2[0] = -19310918;
For (i, 1, n)
{
str1[++tot] = -19370707;
str2[tot] = -19370707;
str1[++tot] = h1[i][l][r];
str2[tot] = pal[i][l][r] ? h2[i][l][r] : -i;
}
str1[++tot] = -19370707;
str2[tot] = -19370707;
str1[tot + 1] = str2[tot + 1] = -19450815;
manacher(tot);
For (i, 1, tot)
if (str2[i] < -1000 || str2[i] >= 0)
ans += i & 1 ? R[i] - 1 >> 1 : R[i] >> 1;
}
int main()
{
int i, j, k;
std::cin >> n >> m;
pw1[0] = pw2[0] = 1;
For (i, 1, 25)
{
pw1[i] = 317ll * pw1[i - 1] % ZZQ;
pw2[i] = 317ll * pw2[i - 1] % YSY;
}
For (i, 1, n) scanf("%s", s[i] + 1);
For (i, 1, n) For (j, 1, m)
{
int o1 = 0, o2 = 0, cc = 0;
For (k, 0, 25) cnt[k] = 0;
For (k, j, m)
{
int c = s[i][k] - 'a';
o1 = (o1 + pw1[c]) % ZZQ;
o2 = (o2 + pw2[c]) % YSY;
if ((++cnt[c]) & 1) cc++;
else cc--;
pal[i][j][k] = cc <= 1;
h1[i][j][k] = o1; h2[i][j][k] = o2;
}
}
For (i, 1, m) For (j, i, m) solve(i, j);
std::cout << ans << std::endl;
return 0;
}
F
题意
- 给定 n 个集合
- 每个集合里有一些区间
- 有 m 个询问
- 每次给出 a,b,x,y
- 询问是否对于每个 i∈[a,b] ,都满足第 i 个集合内存在一个区间包含于 [x,y]
- 强制在线
-
1≤n,m≤105 ,1≤k≤3×105 ( k 为总区间个数)
- 区间端点在 [1,109] 内
算法:主席树
- 小清新数据结构题
- 先离散化区间端点
- 询问可以看成将所有右端点不超过 y 的区间全部加入,设这种情况下第 i 个集合内区间的左端点的最大值 为 lmaxi (第 i 个集合为空则 lmaxi=0 )
- 那么就是询问 mini=ablmaxi 是否大于等于 x
- 如果大于等于 x 则输出 yes ,否则 no
- 建一些线段树,第 i 棵线段树储存所有右端点不超过 i 的区间全部加入时,这种情况下的 lmaxi 及其区间最小值
- 考虑到空间问题,使用主席树,将区间按右端点排序后,对于所有的 i ,将前 i 个区间建一个历史版本,第 i 个历史版本建在第 i−1 个历史版本上
- 复杂度 O((n+m+k)logn)
代码
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}
template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}
const int N = 3e5 + 5, M = N << 1, L = 3e7 + 5;
int n, m, k, tmp, arr[M], rt[N], pos[M], ToT;
struct interval
{
int l, r, p;
} a[N];
struct node
{
int lc, rc, val;
} T[L];
inline bool comp(interval a, interval b)
{
return a.r < b.r;
}
void ins(int y, int &x, int l, int r, int p, int val)
{
T[x = ++ToT] = T[y];
if (l == r) return (void) (T[x].val = Max(T[x].val, val));
int mid = l + r >> 1;
if (p <= mid) ins(T[y].lc, T[x].lc, l, mid, p, val);
else ins(T[y].rc, T[x].rc, mid + 1, r, p, val);
T[x].val = Min(T[T[x].lc].val, T[T[x].rc].val);
}
int query(int l, int r, int s, int e, int p)
{
if (l == s && r == e) return T[p].val;
int mid = l + r >> 1;
if (e <= mid) return query(l, mid, s, e, T[p].lc);
else if (s >= mid + 1) return query(mid + 1, r, s, e, T[p].rc);
else return Min(query(l, mid, s, mid, T[p].lc),
query(mid + 1, r, mid + 1, e, T[p].rc));
}
int main()
{
int i, _a, _b, x, y;
n = read(); m = read(); k = read();
For (i, 1, k) a[i].l = read(), a[i].r = read(), a[i].p = read();
For (i, 1, k) arr[(i << 1) - 1] = a[i].l, arr[i << 1] = a[i].r;
std::sort(arr + 1, arr + (k << 1) + 1);
tmp = std::unique(arr + 1, arr + (k << 1) + 1) - arr - 1;
For (i, 1, k)
{
a[i].l = std::lower_bound(arr + 1, arr + tmp + 1, a[i].l) - arr;
a[i].r = std::lower_bound(arr + 1, arr + tmp + 1, a[i].r) - arr;
}
std::sort(a + 1, a + k + 1, comp);
For (i, 1, k)
{
ins(rt[i - 1], rt[i], 1, n, a[i].p, a[i].l);
pos[a[i].r] = i;
}
For (i, 1, tmp) if (!pos[i]) pos[i] = pos[i - 1];
while (m--)
{
_a = read(); _b = read(); x = read(); y = read();
x = std::lower_bound(arr + 1, arr + tmp + 1, x) - arr;
y = std::upper_bound(arr + 1, arr + tmp + 1, y) - arr - 1;
puts(x > y || query(1, n, _a, _b, rt[pos[y]]) < x ? "no" : "yes");
fflush(stdout);
}
return 0;
}
总结
- 第一次 CF ,感觉 #524 质量还是不错的
- 收获第一波 Rating ,祭