我解决的:B、H、G(1 WA)。
没看的:无。
旁观的:A、D、E、F、I、J、K。
看了但没做出来的:C。
A City of Lights
简单题,略。
K Dishonest Driver
题意:要求压缩长为 的字符串,一个串 连续出现 次可以被压缩为 ,压缩后的长度为 的长度。求可达到的最短长度。 。
考虑区间 DP,设 为区间 的最短可压缩长度。用 KMP 预处理出可以直接压缩的区间然后 DP 即可。
时间复杂度为 ,由于时限有 6s 所以应该并不怕。
E Rounding
应该不是很难,略。
I Mason’s Mark
神秘搜索题,略。字母不会被噪点覆盖,那么可以投机取巧:先找到字母的左上角,然后找出宽和高,然后判断:如果底下有个洞就是 A,右边有个洞就是 C,否则就是 B。
B Blurred pictures
题意: 的网格内,每一行有一个区间 是白色方格,其余均为黑色方格。白色方格构成凸联通块。问最大白色子矩形。 。
一个非常慢的方法是二分边长 ,然后会发现能形成以第 行为底边的正方形的要求是:
- 上 行不能有左端点大于 。
- 上 行不能有右端点小于 。
- 对第 行,其的上 行参照条件 1 和 2。
这可以通过 multiset 来辅助判定,时间复杂度为 。
更好的方法是结合凸联通块的性质,对某一行 和某个边长 ,可以 地检查其是否可以是边长为 的正方形的顶边。由于 作为答案,若可行只会递增,而 也只会递增,因此时间复杂度是 。
#include <bits/stdc++.h>
using namespace std;
int n, l[100005], r[100005];
bool check(int i, int k){
if (r[i] - l[i] + 1 < k) return false;
if (i + k - 1 > n) return false;
int ll = max(l[i], l[i + k - 1]);
return r[i] - ll + 1 >= k && r[i + k - 1] - ll + 1 >= k;
}
int main(){
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d%d", &l[i], &r[i]);
int k = 1;
for (int i = 1; i <= n; ++i){
while (check(i, k + 1)) ++k;
}
printf("%d\n", k);
return 0;
}
H Travel Guide
题意(转化后):给定三维平面上 个点,一个点 被另一个点 支配当且仅当 。问有多少个点不被支配。
这看起来像是一个三维偏序,但是比三维偏序简单一点,所以可以用比三维偏序思维难度更低的做法。
一种做法是:按照 的关键字顺序升序遍历点,检查每一个点是否被之前的某个点支配。
对于 坐标相同的一段点,我们将其再成多个 坐标相同的小段,每一个小段中 坐标不是最小的那些点必定被支配。对于 坐标最小的点,如果前面有 坐标更小且 坐标不更大的点,那也被支配。因此这里维护一个前缀 坐标最小值即可。
而对于这一段点之前的那些点,如果前面有点 满足 坐标均不大于这段点中的点 ,那么 被 支配。我们考虑对 坐标建立权值线段树,对每一个 坐标维护具有此坐标的点中, 坐标的最小值。这样就能快速判断。
看完这一段之后,把这段点的信息插入线段树,然后再看下一段,如此继续下去即可。时间复杂度为 。
#include <bits/stdc++.h>
#define INF 2000000000
using namespace std;
int n, m;
int to[1000005], nxt[1000005], w[1000005], at[100005] = {0}, cnt = 0;
int dis[3][100005];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
int lis[100005], tot;
void dijkstra(int dist[], int S){
while (!pq.empty()) pq.pop();
dist[S] = 0;
pq.push(make_pair(0, S));
tot = 0;
for (; ; ){
while (!pq.empty()){
if (pq.top().first > dist[pq.top().second])
pq.pop();
else break;
}
if (pq.empty()) break;
int dd = pq.top().first, h = pq.top().second;
pq.pop();
lis[tot++] = h;
for (int i = at[h]; i; i = nxt[i]){
if (dist[to[i]] > dd + w[i]){
dist[to[i]] = dd + w[i];
pq.push(make_pair(dist[to[i]], to[i]));
}
}
}
}
int seg[270005], siz, tmp[100005];
unordered_map<int, int> mp;
pair<int, pair<pair<int, int>, int> > tmp2[100005];
bool vis[100005] = {0};
void update(int k, int x){
k += siz - 1;
seg[k] = min(seg[k], x);
while (k > 1){
k >>= 1;
seg[k] = min(seg[k << 1], seg[k << 1 | 1]);
}
}
int query(int id, int l, int r, int b){
if (l > b) return INF;
if (r <= b) return seg[id];
int mid = (l + r) >> 1;
return min(query(id << 1, l, mid, b), query(id << 1 | 1, mid + 1, r, b));
}
void solve_2d(int l, int r){
int pre_mini = INF;
for (int i = l, j; i < r; i = j){
j = i;
while (j < r && tmp2[i].second.first == tmp2[j].second.first)
++j;
// #0 the same, #2 the same, so impossible for the others
int rb = j;
while (j < r && tmp2[i].second.first.first == tmp2[j].second.first.first){
vis[tmp2[j].second.second] = true;
++j;
}
int tmp_mini = tmp2[i].second.first.second;
if (pre_mini <= tmp_mini){
while (i < rb) {
vis[tmp2[i].second.second] = true;
++i;
}
} else pre_mini = tmp_mini;
}
}
int main(){
scanf("%d%d", &n, &m);
for (int i = 1, u, v, ww; i <= m; ++i){
scanf("%d%d%d", &u, &v, &ww);
to[++cnt] = v, nxt[cnt] = at[u], w[cnt] = ww, at[u] = cnt;
to[++cnt] = u, nxt[cnt] = at[v], w[cnt] = ww, at[v] = cnt;
}
memset(dis, 0x3f, sizeof(dis));
dijkstra(dis[0], 0);
dijkstra(dis[1], 1);
dijkstra(dis[2], 2);
// connected graph, so tot = n
// discretize the dist for #0
for (int i = 0; i < n; ++i)
tmp[i] = dis[0][i];
sort(tmp, tmp + n);
int nn = unique(tmp, tmp + n) - tmp;
for (int i = 0; i < nn; ++i){
mp[tmp[i]] = i + 1;
}
// initialize segment tree
for (siz = 1; siz < nn; siz <<= 1)
;
for (int i = 1; i < siz + siz; ++i)
seg[i] = INF;
// sort by #2
for (int i = 0; i < n; ++i){
tmp2[i].first = dis[2][i];
tmp2[i].second.second = i;
}
for (int i = 0; i < n; ++i){
tmp2[i].second.first.first = dis[0][i];
}
for (int i = 0; i < n; ++i){
tmp2[i].second.first.second = dis[1][i];
}
sort(tmp2, tmp2 + n);
// calc ans
for (int i = 0, j; i < n; i = j){
j = i;
while (j < n && tmp2[i].first == tmp2[j].first)
++j;
solve_2d(i, j);
// use segment tree
for (int t = i; t < j; ++t){
int id = tmp2[t].second.second;
if (vis[id]) continue;
if (query(1, 1, siz, mp[tmp2[t].second.first.first]) <=
tmp2[t].second.first.second)
vis[id] = true;
}
// insert into segment tree
for (int t = i; t < j; ++t){
update(mp[tmp2[t].second.first.first], tmp2[t].second.first.second);
}
}
int ans = 0;
for (int i = 0; i < n; ++i){
if (!vis[i]) ++ans;
}
printf("%d\n", ans);
return 0;
}
J Mona Lisa
题意:有 4 个随机数生成器,每一个的 seed
不同。给定
,找出四个数
,使得第
个生成器的第
个随机数,这 4 个数最后
个 bit 异或起来是 0。
这种神奇的要求最后结果为 0 的异或题一般要考虑 meet in the middle。
这里不仅用了 meet in the middle,还用了生日悖论。设第 个生成器产生的随机数为 。先产生 个 ,如果它们最低 个 bit 相同就将这两个数的异或加入 map。
然后再产生 个 ,如果它们最低 个 bit 相同,就到 map 里找有没有能和它们对上的 。
根据生日悖论,如果取 ,且 ,那么就很有可能会找到一个可行解。
(代码来自队友)
#include <bits/stdc++.h>
using namespace std;
uint64_t state[4][2];
uint64_t next(int i) {
uint64_t s0 = state[i][0], s1 = state[i][1], result = s0 + s1;
s1 ^= s0;
state[i][0] = ((s0 << 55) | (s0 >> 9)) ^ s1 ^ (s1 << 14);
state[i][1] = (s1 << 36) | (s1 >> 28);
return result;
}
int n;
unordered_map<uint64_t, vector<pair<uint64_t, int>>> B, D;
unordered_map<uint64_t, pair<int, int>> ab, cd;
void addab(uint64_t v, int i, int j) {
auto it = cd.find(v);
if (it != cd.end()) {
printf("%d %d %d %d\n", i, j, it->second.first, it->second.second);
exit(0);
}
ab[v] = make_pair(i, j);
}
void addcd(uint64_t v, int i, int j) {
auto it = ab.find(v);
if (it != ab.end()) {
printf("%d %d %d %d\n", it->second.first, it->second.second, i, j);
exit(0);
}
cd[v] = make_pair(i, j);
}
int main() {
scanf("%d", &n);
int r = n / 3;
uint64_t nn = (1ll << n) - 1, rr = (1ll << r) - 1;
for (int i = 0; i < 4; i++) {
scanf("%llu", &state[i][0]);
state[i][1] = state[i][0] ^ 0x7263d9bd8409f526ll;
}
for (int i = 1; ; i++) {
uint64_t a = next(0) & nn, b = next(1) & nn;
uint64_t c = next(2) & nn, d = next(3) & nn;
B[b & rr].emplace_back(b, i);
D[d & rr].emplace_back(d, i);
if (B.count(a & rr))
for (auto p: B[a & rr])
addab(a ^ p.first, i, p.second);
if (D.count(c & rr))
for (auto p : D[c & rr])
addcd(c ^ p.first, i, p.second);
}
return 0;
}
G Strings
题意:有一个串 和 次操作,第 次操作产生串 。操作有两种:指定 ,取 作为 ;指定 ,使得 。问最后生成的串中所有字符的 ASCII 码之和。 。
将串做成二叉树的形式,叶子都是 的子串。然后模拟各个操作即可。注意复用节点。
#include <bits/stdc++.h>
#define MOD 1000000007
using namespace std;
typedef long long ll;
int n, m, tot;
int sum[10005] = {0};
char s[10005], op[10];
int lch[8000005], rch[8000005];
int sm[8000005];
ll len[8000005];
int rt[2505];
inline int modadd(int x, int y){
return (x + y >= MOD ? x + y - MOD : x + y);
}
inline ll getlen(int id){
if (id >= m * m) return len[id];
return (id % m) - (id / m) + 1;
}
inline int getsum(int id){
if (id >= m * m) return sm[id];
return sum[id % m + 1] - sum[id / m];
}
int build_tree(int rt_id, ll l, ll r){
if (rt_id < m * m){
// 如果这个根是 S(0) 的子串,那就取其一部分
int lb = rt_id / m, rb = rt_id % m;
rb = lb + (int)r;
lb += (int)l;
return lb * m + rb;
}
if (l == 0 && r == getlen(rt_id) - 1) {
// 整个根可以复用,不用修改
return rt_id;
}
// 只要左子树或者右子树
if (l >= getlen(lch[rt_id]))
return build_tree(rch[rt_id], l - getlen(lch[rt_id]), r - getlen(lch[rt_id]));
if (r < getlen(lch[rt_id]))
return build_tree(lch[rt_id], l, r);
// 否则新建一个根,向左右两边递归
int new_rt = tot++;
sm[new_rt] = 0;
len[new_rt] = 0ll;
lch[new_rt] = build_tree(lch[rt_id], l, getlen(lch[rt_id]) - 1);
sm[new_rt] = getsum(lch[new_rt]);
rch[new_rt] = build_tree(rch[rt_id], 0, r - getlen(lch[rt_id]));
sm[new_rt] = modadd(sm[new_rt], getsum(rch[new_rt]));
len[new_rt] = getlen(lch[new_rt]) + getlen(rch[new_rt]);
return new_rt;
}
int main(){
scanf("%d%s", &n, s);
m = strlen(s);
--n;
tot = m * m;
rt[0] = m - 1;
// 将子串 [l, r] 编号为 l * len(s) + r
for (int i = 1; i <= m; ++i)
sum[i] = modadd(sum[i - 1], s[i - 1]);
for (int R = 1; R <= n; ++R){
scanf("%s", op);
if (op[0] == 'S'){
int x;
ll lb, rb;
scanf("%d%lld%lld", &x, &lb, &rb);
--rb;
rt[R] = build_tree(rt[x], lb, rb);
} else {
int x, y;
scanf("%d%d", &x, &y);
rt[R] = tot++;
lch[rt[R]] = rt[x];
rch[rt[R]] = rt[y];
sm[rt[R]] = modadd(getsum(rt[x]), getsum(rt[y]));
len[rt[R]] = getlen(rt[x]) + getlen(rt[y]);
}
}
printf("%d\n", getsum(rt[n]));
return 0;
}
C Crosswords
题意:有一个 的矩阵,给定 个长度为 的单词、 个长度为 的单词,现在要用字母填充矩阵,使得每一行形成的单词都在给定的 个之中,每一列形成的单词都在给定的 个之中。求方案数目。
std 说是暴力,于是写了一个暴力,真的可以。绝了。
简单来说就是用两个 Trie 存横竖的单词表,然后维护一下每一列在 Trie 上的指针以及当前行在 Trie 上的指针,按格子 DFS 就行了。
或者更保险一点,写一个 7 维 DP,然后记忆化搜索,也是一样的。
#include <bits/stdc++.h>
using namespace std;
int n, m, A, B;
int trieh[1000005][26] = {0}, toth = 1;
int triev[1000005][26] = {0}, totv = 1;
int ans, col_pointer[10];
// col_pointer:每一列填到现在为止,在 trie 上的指针
char tmp[10], mat[10][10];
void insert(int trie[][26], int& tot){
int p = 1, l = strlen(tmp);
for (int i = 0; i < l; ++i){
if (!trie[p][tmp[i] - 'a'])
trie[p][tmp[i] - 'a'] = ++tot;
p = trie[p][tmp[i] - 'a'];
}
}
void dfs(int r, int c, int row_pointer){
// row_pointer: r 这一行填到现在为止,在 trie 上的指针
if (r == n){
++ans;
return ;
}
for (int i = 0; i < 26; ++i){
if (!trieh[row_pointer][i]) continue;
int lst_p = col_pointer[c];
if (!triev[lst_p][i]) continue;
col_pointer[c] = triev[lst_p][i];
// dfs
if (c == m - 1) dfs(r + 1, 0, 1);
else dfs(r, c + 1, trieh[row_pointer][i]);
// back
col_pointer[c] = lst_p;
}
}
int main(){
scanf("%d%d%d%d", &n, &A, &m, &B);
for (int i = 1; i <= A; ++i){
scanf("%s", tmp);
insert(triev, totv);
}
for (int i = 1; i <= B; ++i){
scanf("%s", tmp);
insert(trieh, toth);
}
ans = 0;
for (int i = 0; i < m; ++i)
col_pointer[i] = 1;
dfs(0, 0, 1);
printf("%d\n", ans);
return 0;
}
D Monument Tour
待补。。。
F Paris by Night
待补。。。