zoukankan      html  css  js  c++  java
  • 根号分治

    根号分治

    有多种题目类型, 但主要思想就是按是否超过阈值 T 分类。其中大小超过 T 的共有(frac nT)个,但其个数不超过 T ,小于 T 的个数虽多,但其大小较优,因此我们可以根据不同题目的性质设计两种算法,分别适用

    例题一: CF1039D You Are Given a Tree

    题目大意

    有一棵 n 个节点的树
    其中一个简单路径的集合被称为 k 合法当且仅当:
    树的每个节点至多属于其中一条路径,且每条路径恰好包含 k 个点
    对于 k ∈ [1, n],求出 k 合法路径集合的最多路径数
    即:设k合法路径集合为 S ,求最大的 |S|

    数据范围

    (2 le n le 100000)

    解题思路

    一般来说根号分治一部分是可以直接暴力做,另一部分利用其他技巧

    在此题中,设阈值为 T,k 小于 T 时可以暴力 (Theta(n)) 扫一遍,大于 T 时发现答案的个数不会超过 (frac nT) 个,并且答案具有单调性,我们可以二分查找每个答案控制哪个区间即可,复杂度 (Theta(frac {n^2log_2N}{T})),平衡复杂度得 (T = sqrt{nlog_2N}) 时最优。

    其他优化:递归改成递推会快很多,采用整体二分可以去掉 log

    从网上复制下来的整体二分代码

    inline void solve(int l, int r, int L, int R) {
        if (l > r || L > R) return;
        if (L == R) {
            for (int i = l; i <= r; i++) ans[i] = L; return;
        }
        int mid = (l + r) >> 1;
        cur = mid; cnt = 0; dfs(1, 0); ans[mid] = cnt;
        solve(l, mid - 1, cnt, R); solve(mid + 1, r, L, cnt);
    }
    

    完整代码:

    
    int dp[N], d[N], f[N], mx[N], t;
    
    void dfs(int x, int fa) {
    	d[++t] = x, f[x] = fa;
    	for (auto y: to[x]) if (y ^ fa) dfs(y, x);
    }
    
    #define re register
    int get(int k) {
    	int cnt = 0;
    	for (re int i = n; i; --i) dp[i] = 1;
    	for (re int i = n; i ; --i) {
    		int x = d[i];
    		if (f[x] && dp[f[x]] && dp[x]) {
    			if (dp[x] + dp[f[x]] >= k) 
    				cnt++, dp[f[x]] = 0;
    			else Mx(dp[f[x]], dp[x] + 1);
    		}
    	}
    	return cnt;
    }
    
    int ans[N];
    int main() {
    	ios::sync_with_stdio(false);
    	read(n); int m = sqrt(n * log(n) / log(2));
    	for (re int i = n - 1, x, y; i; i--)
    		read(x), read(y), add(x, y);
    	ans[1] = n, dfs(1, 0);
    	for (re int i = 2;i <= m; i++) ans[i] = get(i);
    	
    	for (re int i = m + 1, l, r, t;i <= n; i = r + 1) {
    		l = i, r = n, t = get(i);
    		while (l <= r) {
    			int mid = (l + r) >> 1;
    			if (get(mid) ^ t) r = mid - 1;
    			else l = mid + 1;
    		}
    		for (re int j = i;j <= r; j++) ans[j] = t;
    	}
    
    	for (re int i = 1;i <= n; i++) cout << ans[i] << endl;
    	
    	return 0;
    }
    
    

    习题:

    例题二:CF587F Duff is Mad

    之前写过的题解

    题目大意

    给定 (n) 个字符串 (s_{1 dots n})

    • (q) 次询问 (s_{l dots r})(s_k) 中出现了多少次。
    • (n,q,sum_{i=1}^n |s_i| le 10^5)

    解题思路

    很容易想到暴力思路,,离线后直接暴力在原 trie 树上走, 拿树状数组维护一下就行

    很不巧的是 (sum|s_k|) 并没有保证,,因为一个 k 可以被询问多次,因此我们考虑其他做法

    然后我们想如果 (|s_k| le sqrt m) (m 是字符串大小之和),直接暴力做是可行的, 这样复杂度为 (Theta(qlog_q+nsqrt m log_m)), 可以改变阈值获得更优的复杂度, 但这样也可过

    如果 (|s_k| ge sqrt m), 代表着这样的串只有 (sqrt m) 个, 即使对每个串 (Theta(n)) 暴力也可以了, 所以我们将询问挂在 (s_k) 上, 将它的每个前缀节点标记为 1 , dfs 一遍求子树权值和,从 1 到 n 加入字符串,直接加上 val[end[i]] 就是字符串 i 的贡献

    我的 dfs 直接写的暴力跳 fail,结果也过了?!

    这题需要对大于小于阈值的两种情况使用两种算法

    代码如下:

    const int N = 100500;
    vector<char> s[N];
    char res[N];
    int ch[N][26], en[N], dfn[N];
    int f[N], cnt, num, T, m, n;
    int ne[N], to[N], h[N], tot;
    inline void add_e(int x, int y) {
    	ne[++tot] = h[x], to[h[x] = tot] = y;
    }
    
    int add(char *s, int len) {
    	int p = 0;
    	for (int i = 1;i <= len; i++) {
    		int di = s[i] - 'a';
    		if (!ch[p][di]) ch[p][di] = ++cnt;
    		p = ch[p][di];
    	} return p;
    }
    
    void build(void) {
    	queue<int> q;
    	for (int i = 0;i < 26; i++)	
    		if (ch[0][i]) q.push(ch[0][i]);
    	while (q.size()) {
    		int x = q.front(); q.pop();
    		add_e(f[x], x);
    		for (int i = 0;i < 26; i++)
    			if (ch[x][i]) f[ch[x][i]] = ch[f[x]][i], q.push(ch[x][i]);
    			else ch[x][i] = ch[f[x]][i];
    	}
    }
     
    int siz[N];
    void dfs(int x) {
    	dfn[x] = ++num, siz[x] = 1;
    	for (int i = h[x]; i; i = ne[i]) 
    		dfs(to[i]), siz[x] += siz[to[i]];
    }
    
    struct node {
    	int k, l, num;
    	bool operator < (const node &i) const {
    		return l < i.l;
    	}
    };
    
    vector<node> v[N], q; 
    
    ll ans[N], d[N], val[N];
    void Add(int x, int k) {
    	for (; x <= num; x += x & -x) d[x] += k;
    }
    
    ll Sum(int x) {
    	ll res = 0;
    	for (; x ; x -= x & -x) res += d[x];
    	return res;
    }
    
    int main() {
    	read(n), read(m); T = 250;
    	for (int i = 1;i <= n; i++) {
    		scanf ("%s", res + 1); 
    		int t = strlen(res + 1); en[i] = add(res, t);
    		s[i].push_back('f');
    		for (int j = 1;j <= t; j++) s[i].push_back(res[j]);
    	}
    	build(); dfs(0);
    	
    	for (int i = 1;i <= m; i++) {
    		int l, r, k; read(l), read(r), read(k);
    		if ((int)s[k].size() > T) {
    			if (l-1) v[k].push_back((node){-1, l-1, i});
    			v[k].push_back((node){1, r, i});
    		}
    		else {
    			if (l-1) q.push_back((node){-k, l-1, i});
    			q.push_back((node){k, r, i});
    		}
    	}
    	sort(q.begin(), q.end());
    	for (int i = 1;i <= n; i++) {
    		if (!v[i].size()) continue;
    		sort(v[i].begin(), v[i].end());
    		memset(val, 0, sizeof(val));
    		int p = 0;
    		for (int j = 1;j < (int)s[i].size(); j++) {
    			p = ch[p][s[i][j]-'a'];
    			int x = p;
    			while (x) val[x]++, x = f[x];
    		}
    		ll sum = 0, now = 1;
    		for (auto j: v[i]) {
    			while (now <= j.l) 
    				sum += val[en[now++]];
    			ans[j.num] += j.k * sum;
    		}
    	}
    	
    	int now = 1;
    	for (auto i: q) {
    		while (now <= i.l) {
    			int x = en[now];
    			Add(dfn[x] + siz[x], -1);
    			Add(dfn[x], 1); now++; 
    		}
    		int p = 0, f = i.k > 0 ? 1 : -1;
    		if (i.k < 0) i.k = -i.k;
    		ll sum = 0;
    		for (int j = 1;j < (int)s[i.k].size(); j++) {
    			p = ch[p][s[i.k][j]-'a'];
    			sum += Sum(dfn[p]);
    		}
    		ans[i.num] += f * sum;
    	}
    	
    	for (int i = 1;i <= m; i++) printf ("%lld
    ", ans[i]); 
    	
    	return 0;
    }
    
  • 相关阅读:
    机器码call和jmp地址的计算
    linux下系统对于sigsegv错误时的处理
    elf文件中的.plt .rel.dyn .rel.plt .got .got.plt的关系
    docker随谈
    nginx性能优化技巧
    ubuntu安装php常见错误集锦
    ubuntu php5.6源码安装
    关于mysqld_safe
    ubuntu mysql5.7源码安装
    linux下nginx模块开发入门
  • 原文地址:https://www.cnblogs.com/Hs-black/p/12693974.html
Copyright © 2011-2022 走看看