又是一次自闭场,我太菜了
A
距离除以速度。
B
由于
n
<
=
100
n<=100
n<=100,暴力
O
(
n
2
)
O(n^2)
O(n2)即可知道所有可能的配对回文情况。再检查是否可能有自回文的情况,如果有再在答案上加上一个即可。
C
在等待客户来的时候,可以对温度区间进行左右拓展: (x, y) -> (x - t, y + t)
当客户到了之后,将可到达的温度区间与客户的满意区间取交集: (x, y) ∩= (tx, ty)
;如果出现某次取交集后为空,则输出NO。
补题
D
我可能不配上分…昨晚应该在做梦吧…
给出1-n
的排列相邻两个数之间的大小关系,试构造LIS最短的一种情况与LIS最长的一种情况。
先考虑最短情况:
因为对于每个连续上升区间,最短LIS长度不可能比其更小;所以我们就尝试构造该排列使得最短LIS长度就恰为最长的连续上升区间长度。为了达到这个目的,就要求对于一个上升区间,其之前所有的增区间都要严格地比它高,而之前的减区间对答案没有影响,这是因为如果取了之前减区间某个点,肯定不会比取这个V
型谷底点更优,所以在减区间的情况我们需要保证构造不会越界[1, n]
。
容易想到这种情况的构造办法,不过需要预处理出来连续区间的长度(缩区间为点来优化时间复杂度)。用up记录当前最高可取的数值(初始为
n
n
n),now记录当前拐点,之后针对V
型进行构造:对于一个V
型结构,先移动到/
的末端,从右往左填入up--
,再移动到\
,从左往右填入up--
。如是反复操作即可。
注意当now == 1
时要为起点初始化值,因为在之后的填值时拐点并不参与;当缩点后第一个单调区间为<
时也要先从大到小填一遍,再回归到刚刚的V
结构处理办法上。
再考虑最长情况:
显然将每一段连续增区间都连接起来就可以获得一个长度最长的LIS,与最短情况类似,无需考虑减区间可能带来的影响。
下面构造最长的LIS。
初始化ans[1] = 0
,设置up = 1
, down = -1
,当当前比较符为小于号时填入up++
,为大于号则填入down--
。
这样就可以完成一个相对高度为
n
n
n、所有增区间皆可成功串联的
n
n
n个互不相同的数的排列。
再对所有元素加上-down
就可以将其提升到绝对高度在[1, n]
的排列。
const int maxn = 2e5 + 10;
struct node{
char op; int len;
};
int n;
char s[maxn];
int ans[maxn];
vector<node> vec;
int top;
void least(){
int p = 0;
int up = n, now = 1;
if(vec[0].op == '<'){
for(int i = 0; i <= vec[0].len; i++) ans[vec[0].len + 1 - i] = up--;
p = 1; now = vec[0].len + 1;
}
//V型
while(p < top){
if(p + 1 < top){
for(int i = 0; i < vec[p + 1].len; i++)
ans[now + vec[p].len + vec[p + 1].len - i] = up--;
if(now == 1) ans[now] = up--;
for(int i = 1; i <= vec[p].len; i++)
ans[now + i] = up--;
now += vec[p].len + vec[p + 1].len;
}
else{
if(now == 1) ans[now] = up--;
for(int i = 1; i <= vec[p].len; i++) ans[now + i] = up--;
}
p += 2;
}
for(int i = 1; i <= n; i++) printf(i != n? "%d ": "%d", ans[i]);
printf("\n");
}
void most(){
int down = -1, up = 1;
ans[1] = 0;
for(int i = 1; i <= n - 1; i++){
if(s[i] == '<') ans[i + 1] = up++;
else ans[i + 1] = down--;
}
for(int i = 1; i <= n; i++) ans[i] += -down;
for(int i = 1; i <= n; i++) printf(i != n? "%d ": "%d", ans[i]);
printf("\n");
}
int main(){
// Fast;
int t; scanf("%d", &t);
while(t--){
vec.clear();
scanf("%d", &n); scanf("%s", s + 1);
int i = 1;
while(i <= n - 1){
char op = s[i]; int len = 1;
while(i + 1 <= n - 1 && s[i] == s[i + 1]){
len++; i++;
}
i++;
vec.push_back({op, len});
}
top = (int)vec.size();
least(); most();;
}
return 0;
}
昨晚在写的时候想到了最长LIS的这种漂亮构造办法,但是发现好像并不适用最短LIS,要求解最短LIS应该一定要缩点,但是当时已经没有多少时间了…发现的有点晚…
E
因为在树上对于每一对点来说其之间距离都唯一确定,所以当每条边都可以重复经过时,从点
a
a
a到点
b
b
b就可以花费
d
i
s
a
b
+
2
k
,
k
∈
Z
+
dis_{ab}+2k, k∈Z^+
disab+2k,k∈Z+ 到达。如果加入了某条边后从
a
a
a点到
b
b
b点之间距离的奇偶性可以发生变化,那么就有可能在
k
k
k步后到达原本不能够到达的
b
b
b点。
所以只需要判断三种情况是否有一种满足 d i s a b + 2 t = k ( t ∈ Z + ) dis_{ab}+2t = k(t∈Z^+) disab+2t=k(t∈Z+):
a->b
a->x->y->b
a->y->x->b
因为不确定
a
a
a点到
b
b
b点怎么样经过Edge[x, y]
最优,所以要对两种情况都考虑一下。
自己想到这里后发现自己居然不会在树上 O ( l o g n ) O(logn) O(logn)的求两点之间的距离…=. =
因为之前线段树练习中遇到过括号序(进栈记录一次出栈记录一次)好像可以用线段树快速处理两个点之间的距离…但是为了避免瞎搞而心态崩我还是去看了下正统的求两点距离…
LCA
参考博客: LCA算法解析-Tarjan&倍增&RMQ (写得很让我喜欢)
记dis数组为当前节点到根节点(不妨取为1)的距离,如果快速求得了两点的LCA就可以利用
d
i
s
a
b
=
d
i
s
[
a
]
+
d
i
s
[
b
]
−
2
∗
d
i
s
[
l
c
a
]
dis_{ab}=dis[a] + dis[b] - 2 * dis[lca]
disab=dis[a]+dis[b]−2∗dis[lca]得到两点的距离。
确实很有道理。
tarjan离线算法
其本质有点类似于括号序?
如果某个节点及其子树被遍历完毕,则将其与其父节点用并查集合并;如果当前搜索点是查询的一组LCA且另一个节点已被访问过,就可以确定这组点的LCA即为另一个节点的父节点(即并查集的根节点,也就是当前节点的一个正在被搜索祖父节点)。
显然这个父节点既是另一个已访问节点的祖父节点,又是当前搜索节点的一个祖父节点;且由于dfs序(括号序)的性质,这个节点也是第一个具有遍及当前节点的分支的父亲节点。那么这种离线做法的正确性就不言自明了。
倍增
其最本质的思想在于暴力往更浅的地方找祖先直到两个点相遇。因为其复杂度不可接受,所以考虑用二进制倍增优化。
假设要求
L
C
A
(
x
,
y
)
LCA(x, y)
LCA(x,y):
若起始时
x
x
x和
y
y
y的深度不同,由于其深度差值一定可以用二进制表示,
O
(
l
o
g
n
)
O(logn)
O(logn)地先将二者深度齐平。当深度齐平后如果x == y
说明恰好已经找到了LCA(此时x或y为另一方的子树节点);不然,就需要不断地往上走直到成为其公共祖先的两个左右节点:如果走了
2
k
2^k
2k步后二者祖先相同,按照我们的约定这两个点不能相遇而只能成为孩子节点,所以应该在[1,
2
k
2^k
2k- 1]中寻找,缩小步伐重走;如果走了
2
k
2^k
2k步后祖先不同,那么这个位置一定没有到达要求的LCA,更新位置后缩小步伐继续走;如是反复,跳出循环后father[x] == father[y] = LCA(x, y)
欧拉序
我还是比较喜欢这种做法,毕竟前不久刚刚玩了一段时间的线段树
不同于括号序,在dfn序中当元素进栈时记录一次,被子节点回溯到后再记录一次。因为一共有n个元素,可能进栈n次,也只会回溯n - 1次(根节点不会回溯),所以总共的长度为2 * n - 1
(有待考证,应该没有问题?);因为有点虚所以还是使用dfn序的长度times - 1吧。
假设节点x在节点y之前被遍历(即left[x] < left[y]
),那么有了欧拉序后只需要在该序列中的区间[left[x], left[y]]
中找最浅的一个节点即可,这个节点一定就是
L
C
A
(
x
,
y
)
LCA(x, y)
LCA(x,y).
如果当询问次数远大于节点个数时,可以用ST表优化查询;但是这道题由于查询和节点个数差不多,所以用线段树查询的效率也是差不多的,顺手点就写线段树了。
1Try(还是很舒服的)
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)
const int maxn = 1e5 + 10;
struct star{
int to, next;
}edge[maxn << 1];
int n, top = 0;
int head[maxn];
void add(int u, int v){
edge[top].to = v;
edge[top].next = head[u];
head[u] = top++;
}
int dis[maxn];
int vis[maxn], dfn[maxn << 1], left[maxn << 1], right[maxn << 1];
int times = 1;
void dfs(int start){
vis[start] = 1; dfn[times] = start; left[start] = times; times++;
for(int i = head[start]; ~i; i = edge[i].next){
if(!vis[edge[i].to]){
vis[edge[i].to] = 1;
dis[edge[i].to] = dis[start] + 1;
dfs(edge[i].to);
dfn[times++] = start;
}
}
right[start] = times - 1;
}
int st[maxn << 3];
void up(int p){
if(dis[st[ls]] < dis[st[rs]]) st[p] = st[ls];
else st[p] = st[rs];
}
void build(int p, int l, int r){
if(l == r){
st[p] = dfn[l];
return;
}
build(ls, l, mid); build(rs, mid + 1, r);
up(p);
}
int query(int p, int l, int r, int L, int R){
if(L <= l && r <= R){
return st[p];
}
int ans = 0, temp;
if(L <= mid){
temp = query(ls, l, mid, L, R);
if(dis[temp] < dis[ans]) ans = temp;
}
if(R > mid){
temp = query(rs, mid + 1, r, L, R);
if(dis[temp] < dis[ans]) ans = temp;
}
return ans;
}
void de(){
puts("DFN:"); fro(int i = 1; i < times; i++) printf("%d ", dfn[i]); printf("\n");
for(int i = 1; i <= n; i++) printf("%d: %d %d dis: %d\n", i, left[i], right[i], dis[i]);
}
int getdis(int x, int y){
int L, R;
if(left[x] < left[y]){
L = left[x]; R = left[y];
}
else{
L = left[y]; R = left[x];
}
return dis[x] + dis[y] - 2 * dis[query(1, 1, 2 * n - 1, L, R)];
}
int main(){
// Fast;
memset(head, -1, sizeof head);
scanf("%d", &n);
for(int i = 0, x, y; i < n - 1; i++){
scanf("%d %d", &x, &y);
add(x, y); add(y, x);
}
dis[0] = 0x3f3f3f3f;
dfs(1);
// de();
build(1, 1, times - 1);
int m; scanf("%d", &m);
int x, y, a, b, k;
for(int i = 0; i < m; i++){
scanf("%d%d%d%d%d", &x, &y, &a, &b, &k);
int flag = 0, temp = 0;
temp = getdis(a, b);
if(k >= temp && (k - temp) % 2 == 0) flag = 1;
temp = getdis(a, x) + 1 + getdis(y, b);
if(k >= temp && (k - temp) % 2 == 0) flag = 1;
temp = getdis(a, y) + 1 + getdis(x, b);
if(k >= temp && (k - temp) % 2 == 0) flag = 1;
puts(flag? "YES": "NO");
}
return 0;
}
好像ios:base(在iostream里)
有个啥玩意的标识符就是left
,导致我得把using namespace std
关了才能避免重新取名字…emm
补完题感觉D和E不算特别难的题目…尽管场上D浪费了90分钟也没想出来,E题发现至今居然都不会树上两点的距离(我忘补坑了)…加上自己对这些算法的实现还不够娴熟,可能在时间上也不是很来得及做完…确实菜,不配上分…
明天要开始上网络课了,寒假算是正式过了,感觉和一个多月前一样菜… qwq
继续加油吧!