海亮01/03日杂题

海亮集训:01/03日

注:没有的就是没补完QAQ
题单

T1

T2

T3

T4

CF1697F

题意

构造一个长度为 \(n\) 的数列 \(a\),其中 \(1\le a_i\le k\)\(a\) 不降,即对于所有 \(1\le i \le n-1\)\(a_i \le a_{i+1}\)。给出 \(m\) 个约束,有三种约束,其中:

\(1\;i\;x\),表示 \(a_i \neq x\)

\(2\;i\;j\;x\),表示 \(a_i+ a_j\le x\)

\(3\;i\;j\;x\),表示 \(a_i+ a_j\ge x\)

现在有 \(t\) 组数据,要求你给出这个数列。无解输出 \(-1\)

题解

发现这东西很像2-SAT状物,但是如果直接设 \((i,p)=0(or \space1)\) 表示 \(a_i=p(or\space a_i\neq p)\) 不太好办,因为你需要保证对于每一个 \(i\) 有且仅有一个 \(p\) 使得 \((i,p)=1\) 成立。

那怎么办呢?

修改下刚刚的定义,设 \((i,p)=0(or \space1)\) 表示 \([a_i\ge p]=0(or\space 1)\)

那么这下就好办了。不妨设 \((i,p,a)\to(j,q,b)\) 表示如果 \((i,p)=a\),那么 \((j,q)=b\)

首先按照定义,需要有:

\[\begin{cases} (i,p,1)\to(i,p + 1,1)\\ (i,p+1,0)\to(i,p,0) \end{cases}\\ \begin{cases} (i,p,1)\to(i+1,p,1)\\ (i+1,p,0)\to(i,p,0) \end{cases}\\ \]

然后对于三种操作有:

第一种 \((i,x)\)

\[\begin{cases} (i,x,1)\to(i,x+1,1)\\ (i,x+1,0)\to(i,x,0) \end{cases} \]

第二种 \((i,j,x)\)

\[\begin{cases} (i,p,1)\to(j,x-p+1,0)\\ (j,x-p+1,1)\to(i,p,0) \end{cases} \]

第三种 \((i,j,x)\)

\[\begin{cases} (i,p+1,1)\to(j,x-p,1)\\ (j,x-p,1)\to(i,p+1,1) \end{cases} \]

证明显然。

然后跑 2-SAT 即可。

代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 1e6 + 10;
int n, m, k;
vector<int> edg[maxn];
int id[maxn / 10][12][2], total;
void add(int a,int b,int c,int d,int e,int f){
edg[id[a][b][c]].push_back(id[d][e][f]);
}
void addd(int a,int b,int c,int d,int e,int f){
add(a,b,c,d,e,f);add(d,e,f ^ 1,a,b,c ^ 1);
}
int dfn[maxn],low[maxn], idx;
int col[maxn], color;bool book[maxn];
stack<int> stk;
void dfs(int u){
dfn[u] = low[u] = ++idx;
stk.push(u);book[u] = 1;
for(int v : edg[u]){
if(!dfn[v]){dfs(v);low[u] = min(low[u],low[v]);}
else if(book[v]){low[u] = min(low[u],low[v]);}
}
if(dfn[u] == low[u]){
int v;color++;
do{
v = stk.top();stk.pop();
book[v] = 0;col[v] = color;
}while(v != u);
}
}
bool tarjan(){
idx = color = 0;while(!stk.empty())stk.pop();
for(int i = 1;i <= total;i++){dfn[i] = low[i] = col[i] = book[i] = 0;}
for(int i = 1;i <= total;i++)if(!dfn[i])dfs(i);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= k + 1;j++)
if(col[id[i][j][0]] == col[id[i][j][1]])return false;
return true;
}
void solve(){
n = read(); m = read(); k = read();total = idx = 0;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= k + 1;j++)
for(int o = 0;o < 2;o++){
id[i][j][o] = ++total;
edg[total].clear();
}
for(int i = 1;i <= n;i++){
add(i,1,0,i,1,1);add(i,k + 1,1,i,k + 1,0);
for(int p = 1;p <= k;p++){
addd(i,p + 1,1,i,p,1);
if(i != n)addd(i,p,1,i + 1,p,1);
}
}
int opt, u, v, w;
for(int i = 1;i <= m;i++){
opt = read(); u = read(); v = read();
if(opt == 1){addd(u,v,1,u,v + 1,1);}
else{
w = read();
if(opt == 2){
for(int j = 1;j <= k;j++){
int v1 = min(k + 1,max(1,w - j + 1));
addd(u,j,1,v,v1,0);
}
}
else{
for(int j = 1;j <= k;j++){
int v1 = min(k + 1,max(1,w - j));
addd(u,j + 1,0,v,v1,1);
}
}
}
}
if(!tarjan()){puts("-1");return;}
// for(int i = 1;i <= n;i++)
// for(int j = 1;j <= k + 1;j++){
// for(int o = 0;o < 2;o++){
// printf("col[%d,%d,%d]=%d\n",i,j,o,col[id[i][j][o]]);
// }
// }
for(int i = 1;i <= n;i++){
int x = -114514;
for(int j = k;j;j--)
if(col[id[i][j][0]] > col[id[i][j][1]]){x = j;break;}
printf("%d%c",x," \n"[i == n]);
}
puts("");return;
}
signed main(){
int T = read();
while(T--){solve();}
return 0;
}

T5

T6

CF1656H

题意

有两个集合\(A,B\),大小分别为\(n,m\),你需要找两个非空子集\(S_A\subseteq A,S_B\subseteq B\),使得\(S_A\)中的元素的最小公倍数和\(S_B\)中的元素的最小公倍数相等。若无解,输出NO,有解输出YES和任意一组解。

多组数据,数据组数\(t\leq200,1\leq \sum n,\sum m\leq1000,1\leq a_i,b_i\leq4\times10^{36}\)

题解

先从所有数都是同一个质数的幂次的弱化版本出发思考。

发现,对于集合 \(A=\{p^{a_1},p^{a_2},p^{a_3},\dots,p^{a_n}\}\),我们可以直接看成 \(A=\{{a_1},{a_2},{a_3},\dots,{a_n}\}\)

那么现在需要找出两个子集 \(A'=\{{a_{x_1}},{a_{x_2}},{a_{x_3}},\dots,{a_{x_k}}\}\),他们的最小公倍数就是 \(\max{a_{x_1},{a_{x_2}},\dots,{a_{x_k}}}\)

这时发现,如果从大到小排序,我们能够找到最大的 \(1\le i\le n,1\le j\le m,a_i=b_j\),那么剩下的 \(a[i+1,n],b[j+1,m]\)都是不可能出现在答案中的,直接删掉即可。

或者说,如果对于某一个 \(a_i(or \space b_i)\),它大于对面的最大值,显然删去这个数字即可。

然后扩展到多个质数的幂次。

仍然按照这个思路来,那么一个数 \(a_i\)\(b_i\) 同理)被删去当 \(\gcd_{j=1}^m\frac{a_i}{\gcd(a_i,b_j)}>1\)。证明显然,想想就明白了。

然后每次删除一个数字都需要扫一遍对面的所有数判定下有没有需要删除的。这个是 \(O(n^3\log \max a_i)\) 过不去的。尝试优化掉一个 \(n\) ,这样就能过去了。

\(gcd(x,0)=x\),然后每次删除的时候直接把这个数字变成 \(0\),然后就可以快乐的线段树维护了。

时间复杂度是 \(O(n^2\log n\log \max a_i)\) 的,能过。

代码

#include<bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
typedef __int128 ll;
#else
typedef long long ll;
#endif
inline ll read(){
ll x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f =-1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x * 10) + (ch - '0');ch = getchar();}
return x * f;
}
inline void write(ll x){
stack<char> stk;while(!stk.empty())stk.pop();
if(x < 0){putchar('-');x = -x;}
while(x){stk.push((x % 10) + '0');x /= 10;}
while(!stk.empty()){putchar(stk.top());stk.pop();}
}
const int maxn = 1e3 + 10;
int siz[2], rest[2];ll val[2][maxn];
bool del[2][maxn];
typedef pair<int,int> pii;
struct Segment_Tree{
ll d[maxn << 2], x;int opt;
void build(int l,int r,int p){
if(l == r){d[p] = x / __gcd(val[opt][l],x);return;}
int mid = (l + r) >> 1;
build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
d[p] = __gcd(d[p << 1],d[p << 1 | 1]);
}
void delet(int l,int r,int pos,int p){
if(l == r && l == pos){d[p] = 0;return;}
int mid = (l + r) >> 1;
if(pos <= mid)delet(l,mid,pos,p << 1);
else delet(mid + 1,r,pos,p << 1 | 1);
d[p] = __gcd(d[p << 1],d[p << 1 | 1]);
}
ll getans(){return d[1];}
}tree[2][maxn];
void solve(){
rest[0] = siz[0] = read();rest[1] = siz[1] = read();
for(int j = 0;j < 2;j++)
for(int i = 1;i <= siz[j];i++){
val[j][i] = read();
del[j][i] = 0;
}
queue<pii> que;
for(int j = 0;j < 2;j++){
for(int i = 1;i <= siz[j];i++){
tree[j][i].x = val[j][i];tree[j][i].opt = (j ^ 1);
tree[j][i].build(1,siz[j ^ 1],1);
if(tree[j][i].getans() > 1){
del[j][i] = 1;que.push(make_pair(j,i));rest[j]--;
}
}
}
while(!que.empty()){
int opt = que.front().first, pos = que.front().second;que.pop();
for(int i = 1;i <= siz[opt ^ 1];i++){
if(!del[opt ^ 1][i]){
tree[opt ^ 1][i].delet(1,siz[opt],pos,1);
if(tree[opt ^ 1][i].getans() > 1){
del[opt ^ 1][i] = 1;que.push(make_pair(opt ^ 1,i));rest[opt ^ 1]--;
}
}
}
}
if(!rest[0] || !rest[1]){puts("NO");return;}
printf("YES\n%d %d\n",rest[0],rest[1]);
for(int j = 0;j < 2;j++){
for(int i = 1;i <= siz[j];i++){
if(!del[j][i]){write(val[j][i]);putchar(' ');}
}
puts("");
}
}
signed main(){
int T = read();
while(T--)solve();
return 0;
}

T7

T8

CF1286F

题意

假设有一个包含 \(n\) 个元素的数组 \(a\) ,现在需要把此数组的每个元素都变为 \(0\)

可以进行的操作有:

  • 选定两个数 \(i\)\(x\) ,然后把 \(a_i\) 减去 \(x\) 。(注意, \(x\) 可以是负数)
  • 选定三个数 \(i,j\)\(x\) ,然后把 \(a_i\) 减去 \(x\) ,把 \(a_j\) 减去 \(x+1\)

请输出最少的操作次数。

\(n\le20\)

题解

首先必须发现答案上界就是 \(n\)(只进行操作一),那么有可能减小答案的方式就是进行操作二。

然后发现,如果按照选定的操作二进行连边,一定构成一个连通块,然后为了让答案减小,我们就需要每个连通块构成一个树的形状(否则只是进行操作一就可以了),这样我们最后的答案就变成了建立尽可能多的森林,答案就是 \(n\) - 森林中树的数量 \(cnt\)

然后发现,如果进行状压+类似背包的话,那么就可以用 \(O(3^nX)\) 的时间复杂度求解,其中 \(X\) 表示判定一个集合是否可以表示成一个森林(或树)。

如果操作二是两个数都减去相同数,我们显然就会做了,判定相当于找一个集合 \(A\in S\),使得 \(2\times \sum_{i\in A}a_i=\sum_{i\in S}a_i\)

但是这下是带个减一的,怎么办?

不难发现,你可以将划分集合看成在集合前面加正负号,然后使得最后的式子总和小于总数。

然后 meet in the middle + two point 就可以 \(O(2^{\frac{|S|}{2}})\) 的判定了。

没了。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 21, maxm = (1 << 20) + 1;
int f[maxm];
int n;ll a[maxn], b[maxn];
ll sl[maxm], sr[maxm];
ll X[maxm], Y[maxm];
void getque(int l,int r,ll *s){
int m = 1;s[m] = 0;
for(int i = l;i <= r;i++,m <<= 1){
for(int j = 1;j <= m;j++){X[j] = s[j] - b[i];Y[j] = s[j] + b[i];}
int x = 1, y = 1, k = 1;
while(x <= m && y <= m){
if(X[x] < Y[y]){s[k] = X[x++];}
else s[k] = Y[y++];
k++;
}
while(x <= m){s[k] = X[x++];k++;}
while(y <= m){s[k] = Y[y++];k++;}
}
}
bool check(int sta){
int siz = 0;ll sum = 0;
for(int i = 1;i <= n;i++){if((sta >> (i - 1)) & 1){b[++siz] = a[i];sum += a[i];}}
if((sum - (siz - 1)) & 1)return false;
getque(1,siz / 2,sl);getque(siz / 2 + 1,siz,sr);
int L = 1 << (siz / 2), R = 1 << (siz - siz / 2),need = 1 + (abs(sum) < siz) * 2;
for(int i = R, j = 1;i;i--){
while(j <= L && sl[j] + sr[i] <= -siz)j++;
for(int k = j;k <= L && need && sl[k] + sr[i] < siz;k++)need--;
// if(!need)return true;
}
return !need;
}
signed main(){
n = read();int m = 0;
for(int i = 1;i <= n;i++){
a[i] = read();
if(a[i])a[++m] = a[i];
}
n = m;
for(int i = 0;i < (1 << n);i++){
if(!f[i] && check(i)){
int rest = ((1 << n) - 1) ^ i;f[i] = 1;
for(int s = rest;s;s = ((s - 1) & rest))
f[i | s] = max(f[i | s],f[s] + 1);
}
}
printf("%d\n",n - f[(1 << n) - 1]);
return 0;
}

T9

CF1558F

题意

有一个长度为 $ n $ 的排列 $ a = [a_1, ..., a_n] $。保证 $ n $ 是奇数。

定义一次操作 $ f(i) $ 为:若 $ a_i > a_{i + 1} $,则将 $ a_i $ 和 $ a_{i + 1} $ 交换。

定义第 $ i $ 操作为:

  • 若 $ i $ 为奇数,则进行 $ f(1), f(3), ..., f(n - 2) $ 这些操作。
  • 若 $ i $ 为偶数,则进行 $ f(2), f(4), ..., f(n - 1) $ 这些操作。

求最早在第几轮操作后,排列已经有序。轮次从 $ 1 $ 开始编号。

题解

发现这玩应根本做不了对叭,尝试主动降低题目难度:

将原题目的值域变成 \([0,1]\) 范围,发现这样就比较好做了。

具体的,设 \(p_i\) 表示第 \(i\)\(0\) 最开始的位置, \(f_i\) 表示将 \(i\) (第 \(i\)\(0\))归位到 \(i-k_i\)\(k_i\) 表示 \([1,p_i]\) 之间有多少个 \(1\))需要多少步。

发现,如果 \(i\) 位置归位过程中始终没有遇到其他的 \(0\),那么答案显然就是 \(k_i+(p_i\mod2)\)\(+p_i\mod 2\) 是因为第一步没办法将奇数位的 \(0\) 提前,下一步才能提前)

如果遇到了其他的 \(0\),那么答案就是 \(f_j+1\),也就是多一步。

综上得到,\(f_i=\max(k_i+(p_i\mod2),f_{i-1}+1)\)

我们知道答案就是最右面的 \(f_m\),然后整理下答案。

如果设 \(t\) 表示最大的整数使得 \(\forall i\in[1,t],a_i=0\),发现答案就是:

\[\max_{i=t+1}^n(k_i+(p_i\mod 2)+m - i) \]

到现在为止我们做的是值域是 \([0,1]\) 范围的情况,那对于值域是 \([1,n]\) 的情况怎么办?

将每一个数变成 \([a_i\ge k]\),然后将 \(k\) 从小到大枚举,每次都统计一次答案,其中最大的那个就是答案。

每次 \(k\gets k+1\) 都只会更改一个数字,直接线段树维护一下即可。

然后怎么求 \(t\),直接对他使用线段树二分即可。

然后有的点还不是 \(0\) 但是被统计进入答案了怎么办?直接加一个 \(-\inf\),然后需要更改为 \(0\) 的时候再加一个 \(\inf\) 即可。

代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 2e5 + 10, INF = 0x3f3f3f3f;
int n;
int p[maxn];
struct Segment_Tree{
struct node{
int maxx, tag, active;
node(int maxx = -INF,int act = 0,int tag = 0
):maxx(maxx),tag(tag),active(act){}
}d[maxn << 2];
node mergenode(node l,node r){return node(max(l.maxx,r.maxx),l.active + r.active);}
void pushdown(int p){
if(d[p].tag != 0){
d[p << 1].tag += d[p].tag;
d[p << 1 | 1].tag += d[p].tag;
d[p << 1].maxx += d[p].tag;
d[p << 1 | 1].maxx += d[p].tag;
d[p].tag = 0;
}
}
void build(int l,int r,int p){
if(l == r){d[p] = node((l & 1) + (l - 1) - 1 - INF);return;}
int mid = l + r >> 1;
build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
void update(int l,int r,int s,int t,int p,int val){
if(s <= l && r <= t){d[p].maxx += val;d[p].tag += val;return;}
int mid = l + r >> 1;pushdown(p);
if(s <= mid)update(l,mid,s,t,p << 1,val);
if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,val);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
void update(int l,int r,int pos,int p){
if(l == r && l == pos){d[p].maxx += INF;d[p].active = 1;return;}
int mid = l + r >> 1;pushdown(p);
if(pos <= mid)update(l,mid,pos,p << 1);
else update(mid + 1,r,pos,p << 1 | 1);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
node query(int l,int r,int s,int t,int p){
if(s <= l && r <= t)return d[p];
int mid = l + r >> 1;pushdown(p);
if(t <= mid)return query(l,mid,s,t,p << 1);
if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
}
int findr(int l,int r,int p){
if(l == r)return l; int mid = l + r >> 1;
if(d[p << 1].active == mid - l + 1)return findr(mid + 1,r,p << 1 | 1);
else return findr(l,mid,p << 1);
}
int findr(){return findr(1,n,1);}
void update(int l,int r,int upd){if(r < l)return;update(1,n,l,r,1,upd);}
void active(int pos){if(pos < 1 || pos > n)return;update(1,n,pos,1);}
int query(int l,int r){if(r < l)return 0;return query(1,n,l,r,1).maxx;}
}tree;
void solve(){
n = read();tree.build(1,n,1);
for(int i = 1;i <= n;i++){p[read()] = i;}
int ans = 0;
for(int i = 1;i <= n;i++){
tree.update(p[i] + 1,n,-1);//puts("111111111");
tree.update(1,p[i],1); tree.active(p[i]);//puts("22222222");
int t = (tree.d[1].active == n) ? (n + 1) : (tree.findr());//puts("33333333");
ans = max(ans,tree.query(t,n));
}
printf("%d\n",ans);
}
signed main(){
int T = read();
while(T--){solve();}
return 0;
}

T10

CF1556G

题意

  • \(2^n\) 个点,编号为 \(i,j\) 的点之间有无向边当且仅当 \(\mathrm{popcount}(i \oplus j)=1\)
  • \(m\) 次操作,每次询问 \(a,b\) 是否能互相到达,或者删除编号在 \([l,r]\) 之间的所有点。
  • \(n \le 50,m \le 5 \times 10^4\)
  • 保证对于查询操作,\(a,b\) 均未被删除;对于删除操作,\([l,r]\) 之间的所有点均未被删除。

题解

首先时光倒流,从后向前做操作。

发现,如果你建立整个线段树,那么有着以下性质:

  • 如果一颗子树所有的子节点都没有被删除,那么这个子树的所有节点可以互相到达,也就是说,这个子树的根节点可以代表这个子树的所有点
  • 两个线段树上的节点能够连接,当且仅当双方只有一次向下走(走左儿子还是右儿子)的方向不同。

然后不难发现,应用动态开点线段树之后,可以用类似于区间覆盖的方式得到每个点被删除(倒序就是加入)

的时间,然后按照刚刚的连边方式,这条边加入的时间是两个端点被删除时间的较早者。

加边的时候可以强制从一个点出发分开走不同的方向,然后之后的只能走相同的方向。

然后分析下时间复杂度。

按照刚刚的连边方式,每个点的期望连边次数不会超过 \(O(n^2)\) 的,但是实际跑的时候好像没超过 \(O(n)\)

然后一共 \(O(nm)\) 个点,总边数 \(O(n^2m)\),是主要的时间复杂度瓶颈。

然后就没了。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read(){
ll x = 0, f = 1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
bool st;
const int maxn = 3e6 + 10,maxm = 1e5 + 10;
int ch[maxn][2], tot = 1;
int tim[maxn];
inline bool leaf(int p){return !ch[p][0] && !ch[p][1];}
void pushdown(int p){
if(!ch[p][0])ch[p][0] = ++tot;
if(!ch[p][1])ch[p][1] = ++tot;
if(tim[p]){
tim[ch[p][0]] = tim[ch[p][1]] = tim[p];
tim[p] = 0;
}
}
void insert(ll l,ll r,ll s,ll t,int p,int tim){
if(r < s || t < l)return;
if(s <= l && r <= t){::tim[p] = tim;return;}
ll mid = l + r >> 1;pushdown(p);
if(s <= mid)insert(l,mid,s,t,ch[p][0],tim);
if(mid < t)insert(mid + 1,r,s,t,ch[p][1],tim);
}
vector<pair<int,int> > edg[maxm];
void addedg(int x,int y){
if(leaf(x) && leaf(y)){edg[min(tim[x],tim[y])].push_back(make_pair(x, y));return;}
if(leaf(x)){addedg(x,ch[y][0]);addedg(x,ch[y][1]);return;}
if(leaf(y)){addedg(ch[x][0],y);addedg(ch[x][1],y);return;}
addedg(ch[x][0],ch[y][0]);addedg(ch[x][1],ch[y][1]);
}
int getpos(ll l,ll r,ll pos,int p){
if(leaf(p)){return p;}
ll mid = l + r >> 1;
if(pos <= mid)return getpos(l,mid,pos,ch[p][0]);
else return getpos(mid + 1,r,pos,ch[p][1]);
}
ll n, m;
pair<pair<ll,ll>, bool> qry[maxm];
int fa[maxn];
int getf(int x){return fa[x] == x ? x : fa[x] = getf(fa[x]);}
void merg(int x,int y){fa[getf(x)] = getf(y);}
bool ed;
signed main(){
cerr << (&st - &ed) / 1024.0 / 1024.0 << "Mib cost." << endl;
n = read(); m = read();n = (1LL << n) - 1;
tim[1] = m + 1;
for(int i = 1;i <= m;i++){
char opt[10];scanf("%s",opt);
ll x = read(), y = read();
if(opt[0] == 'b'){insert(0,n,x,y,1,i);qry[i].second = 0;}
else qry[i].second = 1;
qry[i].first = make_pair(x, y);
}
for(int i = 1;i <= tot;i++){
if(!leaf(i))addedg(ch[i][0],ch[i][1]);
fa[i] = i;
}
for(auto i : edg[m + 1]){merg(i.first,i.second);}
stack<bool> stk;while(!stk.empty())stk.pop();
for(int i = m;i;i--){
for(auto j : edg[i]){merg(j.first,j.second);}
if(qry[i].second){
stk.push(getf(getpos(0,n,qry[i].first.first,1)) == getf(getpos(0,n,qry[i].first.second,1)));
// printf("%lld %lld %lld %lld\n",getpos(0,n,qry[i].first.first,1),getpos(0,n,qry[i].first.second,1),
// getf(getpos(0,n,qry[i].first.first,1)),getf(getpos(0,n,qry[i].first.second,1)));
}
}
// for(int i = 0;i <= m + 1;i++)
// printf("siz[%lld] = %d\n",i,edg[i].size());
while(!stk.empty()){putchar('0' + stk.top());stk.pop();putchar('\n');}
return 0;
}
posted @   Call_me_Eric  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Live2D
欢迎阅读『海亮01/03日杂题』
点击右上角即可分享
微信分享提示