暴力&DFS&BFS的艺术(学习总结全)

前言

  1. 所有学过的东西总结在一个博客里面吧。精简!
  2. 没啥好总结的基础知识,就总结一些类型的题目吧。
  3. 训练方式
    1. 当学习习惯已经养成,平均每天至少8小时在学习。说明在学习时间上已经是达标的了!
    2. 什么都不变,只是心态变化一点点:反正这玩意儿(其实也不止是这玩意儿)短期之内看不到变化,那倒不如在突破和享受之间选择更加关注后者。更“佛系”的刷题(也不要求短期内达到目的地,但是还是要朝着这样想前进。->况且,往往身心越是轻松的是否,越能够坦然疾行),战略上蔑视,战术上重视。
  4. 后面写博客,越来越水分了。重在总结题型,叙述啥的水的很,两下了事。。。——2021.10.15

题目

题目1(n层完全二叉树求距离为D的点对数量&暴力枚举点对的LCA)

  1. 题目链接E - Distance on Large Perfect Binary Tree
  2. 题意:给定一个n层的完全二叉树,求这棵树中距离为D的(i,j)点对数量 。
    1. 2<=n<=1e6,1<=D<=2e6。
    2. 点对不同当i或者j不同,其中(i,j)!=(j,i)如果i!=j。
    3. (i,j)距离即两节点之间边的数目。
  3. 题解
    1. 提示暴力枚举距离为D的点对(i,j)的LCA
    2. 题解来源:官方题解。
  4. 代码
/*
1. **题目链接**:[E - Distance on Large Perfect Binary Tree ](https://atcoder.jp/contests/abc220/tasks/abc220_e)
2. **题意**:给定一个n层的完全二叉树,求这棵树中距离为D的(i,j)点对数量 。
	1. 2<=n<=1e6,1<=D<=2e6。
	2. 点对不同当i或者j不同,其中(i,j)!=(j,i)如果i!=j。
	3. (i,j)距离即两节点之间边的数目。 
3. **题解**:
	1. **提示**:==暴力枚举距离为D的点对(i,j)的LCA==
4. **代码**: 
*/
#include<bits/stdc++.h>
#define int long long
#define dbg(x) cout<<#x<<"==="<<x<<endl
using namespace std;
template<class T>
void read(T &x) {
	T res=0,f=1;
	char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) res=(res<<3)+(res<<1)+(c-'0'),c=getchar();
	x=res*f;
}
const int N=2e6+5;
const int mod=998244353;

int n,D;
int b[N],a[N],len;
void init(int n) {
	b[0]=1;
	for(int i=1; i<=n; i++) b[i]=b[i-1]*2%mod;
}
signed main() {
	init(N-1);
	read(n),read(D);
	int ans=0;
	for(int i=1; i<=n; i++) {
		len=n-i;
		//LCA为第i层的节点,然后枚举左右节点的dep
		//dep_l or dep_r=0
		if(len>=D) a[i]=b[D]%mod*b[i-1]%mod;
		//枚举dep_l
		if(len>=D-1) a[i]=(a[i]+(D-1)*b[D-2]%mod*b[i-1]%mod)%mod;
		else {
			int mx=len,mi=D-len;
			if(mx>=mi) a[i]=(a[i]+(mx-mi+1)*b[D-2]%mod*b[i-1]%mod)%mod;
		}
		ans=(ans+a[i])%mod;
	}
	//上面枚举的是所有(i,j),i<j。现在还要乘上2
	ans=ans*2%mod;
	cout<<ans;
	return 0;
}

题目2:尺取^异或值为0的区间(记录异或前缀和相同的位置即可)&一种思路:记录相同xx值(比如前缀)的位置(优雅暴力)

  1. 传送门E. Bored Bakry
  2. 题意:给定一个长度为 n n n的数组 a a a,求最大的区间满足条件: a [ l ] & a [ l + 1 ] & . . . & a [ r − 1 ] & a [ r ] > a [ l ] ⊕ a [ l + 1 ] ⊕ . . . ⊕ a [ r − 1 ] ⊕ a [ r ] a[l]\&a[l+1]\&...\&a[r-1]\&a[r]>a[l]\oplus a[l+1]\oplus ...\oplus a[r-1] \oplus a[r] a[l]&a[l+1]&...&a[r1]&a[r]>a[l]a[l+1]...a[r1]a[r]
    1. 1 ≤ n ≤ 1 e 6 1\le n \le 1e6 1n1e6
    2. 1 ≤ a i ≤ 1 e 6 1\le a_i\le 1e6 1ai1e6
  3. 题解1(简单易懂&复杂度高)
    1. 显然长度只可能为偶数,且某一位一定全为1,同时更高位的异或值一定为0。
    2. 从高位到低位枚举每一位全为1 的大区间(尺取),找到这个大区间内下标最小的,异或前缀也为xo[i]的位置即可。
      1. 结论如果异或前缀xo[l-1]==xo[r],那么xo[l~r]=0
    3. 每次遇到0要清空记录的位置,用一个set<int>记录需要清空的xor前缀的位置,多一个 log ⁡ n \log n logn。总复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),可以卡过。
      在这里插入图片描述
    4. 官方题解:枚举每个 r 从1 到 n。对于每个 r ,找到满足以下三个条件的 l 。
      1. 要找到最小的 l 满足 r-l+1 为偶数。
      2. [l,r] 在这一位全部为 1 。
      3. [l,r] 在更高位的异或和为 0。
    5. 代码
#include <bits/stdc++.h>
// #define int long long
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
template <class T>
void read(T &x) {
    T res = 0, f = 1;
    char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) res = (res << 3) + (res << 1) + (c - '0'), c = getchar();
    x = res * f;
}
const int N = (1 << 20) + 100;
int n, a[N];
int xo;
int pre[N][2];
set<int> st;
int ans = 0;
void init() {
    xo = 0;
    memset(pre, -1, sizeof(pre));
    pre[xo][0 % 2] = 0;
    st.insert(xo);  //细节,也是要被删除的
}
void solve() {
    read(n);
    for (int i = 1; i <= n; i++) read(a[i]);
    for (int j = 20; j >= 0; j--) {
        init();
        for (int i = 1; i <= n; i++) {
            xo ^= (a[i] >> (j + 1));  //前缀和
            //该位为 1
            if ((a[i] & (1 << j)) > 0) {
                int p = pre[xo][i % 2];  //查找连续 1 && 异或值也为 xo 的位置
                if (p != -1)
                    ans = max(ans, i - p);
                else
                    pre[xo][i % 2] = i;
                st.insert(xo);
            } else {
                for (auto k : st) pre[k][0] = pre[k][1] = -1;
                st.clear();
                pre[xo][i % 2] = i;  //细节
                st.insert(xo);
            }
        }
    }
    cout << ans;
}
signed main() {
    solve();
    return 0;
}
  1. 题解2(只是写法不一样)
    1. i 从 1 到 n ,记录 1 到 i-1 的异或前缀的位置,到 i 的时候就找前一位 p=pre[xo[i]] ,如果 p+1 到 i 这一位全为 1 ,则更新ans=max(ans,i-p),同时 pre[xo[i]] 不需要更新;如果不全为 1 ,说明 pre[xo[i]] 已经 “out” 了,需要更新。(u1s1,雀食有点绕)
    2. 大概就这样?,复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
    3. 代码
#include <bits/stdc++.h>
// #define int long long
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
template <class T>
void read(T &x) {
    T res = 0, f = 1;
    char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) res = (res << 3) + (res << 1) + (c - '0'), c = getchar();
    x = res * f;
}
const int N = (1 << 20) + 100;
int n, a[N];
int sum[N], xo[N];
int pre[N][2], ans = 0;
void solve() {
    read(n);
    for (int i = 1; i <= n; i++) read(a[i]);
    for (int j = 20; j >= 0; j--) {
        sum[0] = xo[0] = 0;
        for (int i = 1; i <= n; i++) {
            sum[i] = sum[i - 1] + ((a[i] & (1 << j)) > 0);
            xo[i] = xo[i - 1] ^ (a[i] >> (j + 1));
        }
        memset(pre, -1, sizeof(pre)), pre[0][0] = 0;
        for (int i = 1; i <= n; i++) {
            if (pre[xo[i]][i % 2] == -1)
                pre[xo[i]][i % 2] = i;
            else {
                int p = pre[xo[i]][i % 2];
                if (sum[i] - sum[p] == i - p)
                    ans = max(ans, i - p);
                else
                    pre[xo[i]][i % 2] = i;
            }
            //奇偶不同位也要检查,如果不是连续的1,要令它变成-1。
            //————每次更新pre[xo[i]][0|1](都要更新,只需要关注:下一次操作的应该是满足条件的!!!)
            int p = pre[xo[i]][~(i % 2)];
            if (sum[i] - sum[p] != i - p) pre[xo[i]][~(i % 2)] = -1;
        }
    }
    cout << ans;
}
signed main() {
    solve();
    return 0;
}

题目3(cf2100,与题目4要求完全相反,题目4cf2200更难):给定 n ≤ 5 e 5 n\le 5e5 n5e5条线段 [ x , y ] [x,y] [x,y],求有相交部分但一个线段不被另一个线段完全包含的线段对数(两个约束,当一个约束同时满足时,可二分等更复杂度极低的方法处理另一个约束)

  1. 传送门D. Segment Tree
  2. 题意:如上
  3. 题解:对 l l l 进行排序,然后对所有左端点 < l <l <l 的线段,只需要记录它的右端点和序号即可。用一个set<int>,整体复杂度只需要 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  4. 代码
const int N = 2e6 + 5;
int n, l, r, x, y;
int fa[N];
vector<int> pre;
vector<pair<pii, int> > v;
set<pii> st;
int find(int x) { return (fa[x] == x) ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
    // cout<<"::"<<x<<" "<<y<<endl;
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}
bool solve() {
    int cnt = 0;
    // for (int i = 0; i < n; i++)
        // cout << ">>>" << i << " " << v[i].x.x << " " << v[i].x.y << " "
            //  << v[i].y << endl;
    for (int i = 0; i < n; i++) {
        // dbg(i);
        // cout<<">>>"<<i<<" "<<v[i].x.x<<" "<<v[i].x.y<<" "<<v[i].y<<endl;
        l = v[i].x.x, r = v[i].x.y, x = v[i].y;
        // for(auto j:st) cout<<j.x<<" "<<j.y<<endl;
        set<pii>::iterator p = st.lower_bound({l, 0});
        for (set<pii>::iterator it = p; it != st.end(); it++) {
            if((it->x)>=r) break;
            if (find(it->y) == find(x)) return false;
            merge(it->y, x),cnt++;
        }
        st.erase(st.begin(), p);
        st.insert({r, x});
    }
    // dbg(cnt);
    return (cnt == n - 1);
}
signed main() {
    read(n);
    for (int i = 1; i <= 2 * n; i++) fa[i] = i;
    for (int i = 1; i <= n; i++) {
        read(l), read(r);
        v.push_back({{l, r}, i});
    }
    sort(v.begin(), v.end());
    puts(solve() ? "YES" : "NO");
    return 0;
}
/*
Examples
inputCopy
6
9 12
2 11
1 3
6 10
5 7
4 8
outputCopy
YES
inputCopy
5
1 3
2 4
5 9
6 8
7 10
outputCopy
NO
inputCopy
5
5 8
3 6
2 9
7 10
1 4
outputCopy
NO
*/

题目4(cf2200,与题目3要求完全相反,比题目3cf2100更难):树的结构&DFS&BFS&递归的艺术(大的问题转化为小的问题,然后每一层只需要考虑一步即可)

  1. 传送门E. Tests for problem D
  2. 题意:一棵节点数为 n ≤ 5 e 5 n\le 5e5 n5e5的树,要求给每个点一个范围,共 2 ∗ n 2*n 2n个不同的数(范围也是在 [ 1 , 2 ∗ n ] [1,2*n] [1,2n]),满足条件:
    1. 树上相连的两个点,这两个点的范围有交点但是一个点的范围不能完全被另一点的范围包含。
    2. 不相连的点:一个点的范围完全被另一个点的范围包含或者两个点的范围互相没有相交部分。
      保证一定能找到这样的方案(有多个方案的话可以打印任何一种方案),要求输出每个点的范围。
  3. 题解
    1. 提示:BFS,每次只需要考虑子树 i i i 所在的范围即可。(DFS也ok,核心思想就是:大的问题转化为小的问题,然后每一层只需要考虑一步。树——正适合这个操作
  4. 代码
const int N = 2e6 + 5;
int n, u, v;
vector<int> g[N];
int sz[N], son[N], F[N];
pii ans[N];
void DFS(int x, int fa) {
    sz[x] = 1, F[x] = fa;
    for (auto i : g[x]) {
        if (i == fa) continue;
        son[x]++;
        DFS(i, x);
        sz[x] += sz[i];
    }
}
int cnt = 0;
struct node {
    int l, r, x;
} s[N];
void BFS() {
    queue<pair<pii, int> > q;  // int树占据的位置:[pii.first,pii.second]
    ans[1].x = 1;              //先处理左节点
    q.push({{2, 2 * n}, 1});
    while (!q.empty()) {
        pair<pii, int> now = q.front();
        q.pop();
        int l = now.x.x, r = now.x.y, x = now.y;
        // cout<<">>>>>>"<<l<<" "<<r<<" "<<x<<endl;
        ans[x].y = l + son[x];      //右端点
        if (son[x] == 0) continue;  //出口
 
        int L, R = r, j = 0;  //一个字母最简单
        for (auto i : g[x]) {
            if (i == F[x]) continue;
            // i_th树:占据了2*sz[i]-1长度的位置
            L = R - (2 * sz[i] - 1) + 1;
            // ADD:
            ans[i].x = l + j;
            q.push({{L, R}, i});
            R = L - 1, j++;
        }
    }
}
signed main() {
    read(n);
    for (int i = 1; i < n; i++) {
        read(u), read(v);
        g[u].push_back(v), g[v].push_back(u);
    }
    DFS(1, -1);
    // for (int i = 1; i <= n; i++)
    // cout << ">>>" << i << " " << sz[i] << " " << son[i] << " " << F[i]
    //  << endl;
    BFS();
    // cout << ">>>>>\n";
    for (int i = 1; i <= n; i++) printf("%d %d\n", ans[i].x, ans[i].y);
    return 0;
}
/*
Examples
inputCopy
6
1 2
1 3
3 4
3 5
2 6
outputCopy
9 12
7 10
3 11
1 5
2 4
6 8
inputCopy
1
outputCopy
1 2
*/

题目5(*cf2100):n个数字,每个数字都要涂色R或者B,判断是否存在涂R的数组合起来(十进制)为a的倍数,涂B的数组合起来为b的倍数?(复杂度判断&DFS&DP)

  1. 传送门F. Red-Black Number
  2. 题意:n个数字,每个数字都要涂色R或者B,判断是否存在涂R的数组合起来(十进制)为a的倍数,涂B的数组合起来为b的倍数?
    1. n , A , B ( 2 ≤ n ≤ 40 , 1 ≤ A , B ≤ 40 ) n , A, B (2≤n≤40, 1≤A,B≤40) n,A,B(2n40,1A,B40)
    2. R,B的个数都只能在 ( 0 , n ) (0,n) (0,n)区间内。且 n 位数,每位都要被涂。
    3. 如果不存在,打印"-1";否则打印任意一种满足条件的长度位 n 的RB字符串(同时要求:R的个数-B的个数的绝对值最小)。
  3. 样例:(帮助理解,题意可以描述的有点草率)
    在这里插入图片描述
  4. 题解:见代码
  5. 代码
#include <bits/stdc++.h>
// #define int long long
#define ll long long
#define pii pair<int, int>
#define x first
#define y second
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
template <class T>
void read(T &x) {
    T res = 0, f = 1;
    char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) res = (res << 3) + (res << 1) + (c - '0'), c = getchar();
    x = res * f;
}

const int N = 40 + 5;
int n, A, B, a[N];
char s[N];
int vis[N][N][N][N];
int ans[N], now[N];
void init() {
    memset(vis, -1, sizeof(vis));
    memset(ans, 0, sizeof(ans));
    ans[0] = 1e9;
}
//
void dfs(int pos, int pre_a, int pre_b, int sum_a) {
    if (vis[pos][pre_a][pre_b][sum_a] == 1) return;
    if (pos == n + 1) {
        if (pre_a == 0 && pre_b == 0 && sum_a != 0 && sum_a != n &&
            abs(sum_a - (n - sum_a)) < ans[0]) {
            for (int i = 1; i <= n; i++) ans[i] = now[i];
            ans[0] = abs(sum_a - (n - sum_a));
        }
        return;
    }
    now[pos] = 0;  //给b
    dfs(pos + 1, pre_a, (pre_b * 10 + a[pos]) % B, sum_a);
    now[pos] = 1;
    dfs(pos + 1, (pre_a * 10 + a[pos]) % A, pre_b, sum_a + 1);
    vis[pos][pre_a][pre_b][sum_a] = 1;
}
signed main() {
    int T;
    read(T);
    while (T--) {
        init();
        read(n), read(A), read(B);
        scanf("%s", s + 1);
        for (int i = 1; i <= n; i++) a[i] = s[i] - '0';
        dfs(1, 0, 0, 0);
        if (ans[0] == 1e9)
            cout << -1;
        else {
            for (int i = 1; i <= n; i++) putchar((ans[i] == 1) ? 'R' : 'B');
        }
        puts("");
    }
    return 0;
}
/*
输入复制
4
5 3 13
02165
4 2 1
1357
8 1 1
12345678
2 7 9
90
输出量复制
RBRBR
-1
BBRRRRBB
BR
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值