概述
虽说是 Div2,但也不是特别简单,E 还是不太裸的。。
A
Description
Pasha 有一个正整数长度的木棍 $n$。他想要完成三次切割以获得四个部分。每个部分必须有一些正整数长度,这些长度的总和显然是 $n$。
Pasha 喜欢长方形但讨厌正方形,所以他想知道,有多少种方法可以将棍子分成四个部分,这样就可以用这些部分形成一个矩形,但不可能形成正方形。
你的任务是帮助 Pasha 并计算这些方式的数量。
Solution
看代码。
Code
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int n;
int main()
{
cin >> n;
cout << (n % 2 ? 0 : (n / 2 - 1) / 2) << endl;
}
B
Description
Vika 有 $n$ 桶油漆,第 $i$ 桶有 $a_i$ 升油漆。
Vika 有一张无限长的长方形纸条,她将长方形纸条分成了无限个正方形,她将按照以下规则对正方形涂色。
- 涂一个正方形需要 $1$ 升油漆。
- 第一个正方形可以用第任意第 $i$ 桶油漆。
- 若第 $k$ 个正方形用了第 $x$ 桶油漆,则第 $k+1$ 个正方形将用第 $x+1$ 桶油漆,若 $x=n$,则第 $k+1$ 个正方形将用第 $1$ 桶油漆。若 $a_x = 0$ 则停止涂色。
求 Vika 最多可以涂多少个正方形。
Solution
设最小的元素是 $x$,很显然答案的下界是 $nx$。
把所有元素都减去 $x$ 之后,我们需要找到一段所有元素都大于零的最长连续段,把这部分也加进答案里即可。
这个最长连续段要么就是连续的一段,要么是头尾相接的一段,都模拟一下就好了。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using std::cin;
using std::cout;
using std::endl;
const int maxn = 2e5 + 10;
int a[maxn], n;
long long ans = 0;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
int pos = 0;
a[0] = 2e9;
for (int i = 1; i <= n; i++)
if (a[i] <= a[pos])
pos = i;
int lpos = 1;
for (int i = 1; i <= n; i++)
if (a[i] == a[pos])
{
lpos = i;
break;
}
int lst = lpos;
int sum = lpos + n - pos - 1;
for (int i = lpos + 1; i <= n; i++)
{
if (a[i] != a[pos])
continue;
sum = std::max(sum, i - lst - 1);
lst = i;
}
cout << (long long)a[pos] * n + sum << endl;
}
C
Description
给定 $k$,求 $2^k$ 个 $2^k$ 维向量满足每个向量的坐标表示中任意一维都为 $1$ 或 $-1$,且这些向量两两点积为 $0$。
$0\leq k\leq 9$。
Solution
这个题一看就比较找规律,再联想分治…
先构造一个如下的矩阵($1$ 表示 $1$,$0$ 表示 $-1$)。
11
10
这是 $k=1$ 的情况,很显然满足要求。
怎么从它扩展到 $k=2$ 呢?可以构造一个如下的矩阵:
1111
1010
1100
1001
就是把 $k=1$ 的矩阵在四个地方复制一份,再把右下角的矩阵取反就行。
然后重复以上操作就构造完了。
证明一下:首先左半部分在原来两个矩阵内部内积是 $0$,取反之后仍然在内部内积是 $0$,而两个矩阵相同的行内积在右面正好会抵消,于是这么构造就是对的。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using std::cin;
using std::cout;
using std::endl;
const int maxn = 1 << 10;
int a[maxn][maxn];
void solve(int x, int y, int len, int opt)
{
if (len == 1)
{
a[x][y] = opt;
return;
}
int p = len / 2;
solve(x, y, p, opt), solve(x, y + p, p, opt), solve(x + p, y, p, opt), solve(x + p, y + p, p, opt ^ 1);
}
int k;
int main()
{
cin >> k;
solve(1, 1, 1 << k, 1);
for (int i = 1; i <= (1 << k); i++, cout << endl)
for (int j = 1; j <= (1 << k); j++)
cout << (a[i][j] ? '+' : '*');
}
D
Description
有一个网格,给出 $n$ 条线段,每条线段覆盖一些网格,问被覆盖的网格的面积并是多少。
$n\leq10000$。
Solution
太扫描线了!!!
把题目的网格图转成平面直角坐标系,线段转成点,然后扫描线即可。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <utility>
using std::cin;
using std::cout;
using std::endl;
const int maxn = 2e5 + 10;
typedef long long ll;
struct node
{
int x1, x2, y, opt;
bool operator<(const node &b) const { return y < b.y; }
} a[maxn];
int p[maxn], tot, n, cnt;
struct Tree
{
struct Node
{
int l, r;
ll sum, lzy;
} t[maxn << 3];
inline int ls(int x) { return x * 2; }
inline int rs(int x) { return x * 2 + 1; }
inline void push_up(int k) { t[k].sum = t[k].lzy > 0 ? p[t[k].r + 1] - p[t[k].l] : t[ls(k)].sum + t[rs(k)].sum; }
void build(int l, int r, int k)
{
t[k].l = l, t[k].r = r;
if (l == r)
{
t[k].sum = 0;
return;
}
int mid = (l + r) / 2;
build(l, mid, ls(k)), build(mid + 1, r, rs(k));
push_up(k);
}
void modify(int x, int y, int v, int k)
{
if (p[t[k].r + 1] <= x || p[t[k].l] >= y)
return;
if (p[t[k].r + 1] <= y && p[t[k].l] >= x)
{
t[k].lzy += v, push_up(k);
return;
}
int mid = floor((x + y) / 2.0); // 注意,坐标有负数,需要向下取整而不是向零取整
if (x <= mid)
modify(x, y, v, ls(k));
if (y > mid)
modify(x, y, v, rs(k));
push_up(k);
}
} t;
signed main()
{
std::ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++)
{
int x1, x2, y1, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (y1 == y2)
{
if (x1 > x2)
std::swap(x1, x2);
x2++, y2++;
a[++tot] = {x1, x2, y1, 1}, a[++tot] = {x1, x2, y2, -1};
p[++cnt] = x1, p[++cnt] = x2;
}
else
{
if (y1 > y2)
std::swap(y1, y2);
x2++, y2++;
a[++tot] = {x1, x2, y1, 1}, a[++tot] = {x1, x2, y2, -1};
p[++cnt] = x1, p[++cnt] = x2;
}
}
std::sort(p + 1, p + 1 + cnt), std::sort(a + 1, a + tot + 1);
cnt = std::unique(p + 1, p + 1 + cnt) - p - 1;
t.build(1, cnt - 1, 1);
ll ans = 0, lst = a[1].y;
for (int i = 1; i <= tot; i++)
{
ans += (a[i].y - lst) * t.t[1].sum;
t.modify(a[i].x1, a[i].x2, a[i].opt, 1);
lst = a[i].y;
}
cout << ans << endl;
}
E
Description
给定一个长度为 $n$($n<200000$)的字符串 $s$,有两种指令:
-
将区间 $[L,R]$ 内的字符变为 $ch$;
-
给定长度为 $k$($1\leq k\leq10$)的字符串排列 $t$,向 $s$ 串中添加字符,使得 $s$ 以 $t$ 为模式循环,求最少的循环次数。
最多 $20000$ 条指令。
字符集大小为 $k$。
Solution
首先有一个结论:当两个相邻字符在字符串排列中前后顺序相同,这两个一定在同一个循环节内(因为不在的话会导致循环节增加一个)。
这样对于一次询问,一个很暴力的想法是:把字符串遍历一遍,看相邻字符在排列中的顺序。
注意到 $k$ 很小,我们尝试把询问的复杂度降到和 $k$ 有关。
发现我们只需要相邻字符的信息,而且这个信息是个数,那我们可以直接把每两种字符的相邻次数存下来询问时 $O(k^2)$ 枚举一遍字符集,把顺序不同的加进答案即可。
询问解决了,下一步是修改。
我们需要区间赋值,整体查询,且维护信息可以合并,那直接选择线段树,每个结点维护这段区间中上文的数组,以及最左元素、最右元素方便合并。
时间复杂度 $O(nk^2+mk^2\log n)$。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <utility>
using std::cin;
using std::cout;
using std::endl;
const int maxn = 2e5 + 10, maxk = 11;
char s[maxn];
int n, m, k;
struct Tree
{
static const int maxn = ::maxn * 4;
int cnt[maxn][maxk][maxk], lch[maxn], rch[maxn], lzy[maxn];
inline int ls(int x) { return x * 2; }
inline int rs(int x) { return x * 2 + 1; }
void push_up(int x)
{
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++)
cnt[x][i][j] = cnt[ls(x)][i][j] + cnt[rs(x)][i][j];
cnt[x][rch[ls(x)]][lch[rs(x)]]++, lch[x] = lch[ls(x)], rch[x] = rch[rs(x)];
}
void access(int x, int ch, int len) { memset(cnt[x], 0, sizeof(cnt[x])), cnt[x][ch][ch] = len - 1, lch[x] = rch[x] = lzy[x] = ch; }
void push_down(int x, int l, int r)
{
if (lzy[x] == -1)
return;
int mid = (l + r) / 2;
access(ls(x), lzy[x], mid - l + 1), access(rs(x), lzy[x], r - mid);
lzy[x] = -1;
}
void build(int l, int r, int k)
{
if (l == r)
{
lch[k] = rch[k] = s[l] - 'a';
return;
}
int mid = (l + r) / 2;
build(l, mid, ls(k)), build(mid + 1, r, rs(k));
push_up(k);
}
void modify(int l, int r, int x, int y, int v, int k)
{
if (l >= x && r <= y)
{
access(k, v, r - l + 1);
return;
}
int mid = (l + r) / 2;
push_down(k, l, r);
if (x <= mid)
modify(l, mid, x, y, v, ls(k));
if (y > mid)
modify(mid + 1, r, x, y, v, rs(k));
push_up(k);
}
Tree() { memset(lzy, -1, sizeof(lzy)); }
} t;
int pos[maxk];
int main()
{
std::ios::sync_with_stdio(false);
cin >> n >> m >> k >> (s + 1);
t.build(1, n, 1);
int opt, x, y;
char ch;
while (m--)
{
cin >> opt;
if (opt == 1)
cin >> x >> y >> ch, t.modify(1, n, x, y, ch - 'a', 1);
else
{
long long ans = 0;
cin >> (s + 1);
for (int i = 1; i <= k; i++)
pos[s[i] - 'a'] = i;
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++)
if (pos[i] >= pos[j])
ans += t.cnt[1][i][j];
cout << ans + 1 << endl;
}
}
}