zoukankan      html  css  js  c++  java
  • SCOI2016 Day1 简要题解

    「SCOI2016」背单词

    题意

    这出题人语文水平真低。。 搬了 skylee 大佬的题意。

    给你 (n) 个字符串,不同的排列有不同的代价,代价按照如下方式计算(字符串 (s) 的位置为 (x) ):

    1. 排在 (s) 后面的字符串有 (s) 的后缀,则代价为 (n^2)
    2. 排在 (s) 前面的字符串有 (s) 的后缀,且没有排在 (s) 后面的 (s) 的后缀,则代价为 (x-y)(y) 为最后一个与 (s) 不相等的后缀的位置);
    3. (s) 没有后缀,则代价为 (x)

    现在需要最小化排列的代价。

    (n le 10^5, 1 le sum_s |s| le 5.1 imes 10^5)

    题解

    首先 (n^2) 的代价明显比其他两种操作的总和还多,显然不够优秀。

    我们需要尽量避免 (1) 情况的出现,这显然是可以达到的。

    因为所有后缀的出现会存在一个偏序结构,构成了一个 (DAG) ,我们找个 (DFS) 序遍历就行了。

    其实这个 (DAG) 就是它反串构成的 (Trie)

    我们相当于要在 (Trie) 上找一个 (DFS) 序把每个 关键点 编号,最小化 每个点与它祖先第一个 关键点 的编号差值的和。

    关键点: 就是插入串最后的结束节点。

    由于是个 (DFS) 序, 对于一个点的每个儿子需要遍历的话,肯定要遍历这个儿子的子树。

    所以对于当前这层贪心的话,不难发现这是一个类似于接水问题,我们肯定是让需要时间较短 ((Size) 较小)的儿子排前面。

    然后发现直接实现只会有 (40) 分。

    为什么错了呢?

    假设对于 (overline{ba}, overline{bc}) 这两个串,但是不存 (overline{b}) 串。但由于 (Trie) 上会存在 (overline{b}) 这个节点,所以我们会在空串的时候考虑的时候会假设有 (overline{b}) 这个串。

    这显然是不行的。其中一种解决办法就是,前面我们需要把 (n) 个串的前缀关系,重新建个新树。然后再按前面那样做就可以了。

    所以复杂度就是 (O(n log n + |S|)) 的。

    代码

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << (x) << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    #define pb push_back
    
    using namespace std;
    
    template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
    template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
    
    inline int read() {
        int x(0), sgn(1); char ch(getchar());
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
        for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
        return x * sgn;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("2012.in", "r", stdin);
    	freopen ("2012.out", "w", stdout);
    #endif
    }
    
    typedef long long ll;
    
    ll ans = 0;
    
    namespace Trie {
    
    	const int Maxn = 510010, Alpha = 26;
    
    	int from[Maxn];
    
    	int Size = 0, sz[Maxn], trans[Maxn][Alpha], key[Maxn];
    
    	inline void Insert(char *str) {
    		int u = 0;
    		For (i, 1, strlen(str + 1)) {
    			int id = str[i] - 'a';
    			if (!trans[u][id]) trans[u][id] = ++ Size;
    			++ sz[u = trans[u][id]];
    			from[u] = id;
    		}
    		key[u] = true;
    	}
    
    	vector<int> G[Maxn];
    	void Get_Tree(int u = 0, int Last = 0) {
    		if (key[u]) G[Last].pb(u), Last = u;
    		For (i, 0, Alpha - 1) if (trans[u][i])
    			Get_Tree(trans[u][i], Last);
    	}
    
    	int id = 0;
    	void Dfs(int u = 0, int Last = 0) {
    		if (u) ans += (++ id) - Last, Last = id;
    		sort(G[u].begin(), G[u].end(), [&](const int &lhs, const int &rhs) { return sz[lhs] < sz[rhs]; } );
    		for (int v : G[u]) Dfs(v, Last);
    	}
    
    };
    
    char str[510010];
    
    int main () {
    
    	File();
    
    	using namespace Trie;
    
    	int n = read();
    
    	For (i, 1, n) {
    		scanf ("%s", str + 1);
    		reverse(str + 1, str + strlen(str + 1) + 1); Insert(str);
    	}
        
    	Get_Tree(); Dfs();
    	printf ("%lld
    ", ans);
        
        return 0;
    
    }
    

    「SCOI2016」幸运数字

    题意

    有一颗 (n) 个点的树,每个点有个权值 (G_i)

    (q) 次询问,每次询问树上一条路径上所有数 (G_i) 中选择一些数异或的最大值。

    (n le 20000, q le 200000, G_i le 2^{60})

    题解

    从一个集合中选择一些数异或出最大值显然是线性基的模板。

    那么我们只需要得到树上一条路径的线性基。

    但线性基是不可减的,我们可以考虑用倍增把线性基合并。

    复杂度就是 (O((n + q) log n imes 60^2)) 可以卡过去。

    其实有更好的解决方式,由于 (q) 比较大,我们尽量使它不要带 (log)

    可以点分治,把每个询问挂在被第一次被分开的中心,然后走到一个询问的点后把它线性基保留起来。

    最后复杂度是 (O((n log n + q) imes 60^2)) 的。

    总结

    树上查链不可减但可以合并的信息,有三种方式。不支持修改可以用 倍增(可在线 or 点分治(离线) or 树剖(可在线)

    支持修改就用 树剖 + 线段树

    注意线性基插入一个线性基的复杂度是 (displaystyle O(frac{L^2}{omega})) 的,合并的复杂度是 (displaystyle O(frac{L^3}{omega}))

    代码

    最后那个做法还是比较好写的。

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << (x) << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    #define pb push_back
    
    using namespace std;
    
    template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
    template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;}
    
    template<typename T>
    inline T read() {
        T x(0), sgn(1); char ch(getchar());
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
        for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
        return x * sgn;
    }
    
    void File() {
    #ifdef zjp_shadow
        freopen ("2013.in", "r", stdin);
        freopen ("2013.out", "w", stdout);
    #endif
    }
    
    typedef long long ll;
    
    template<int Maxn>
    struct Linear_Base {
    
        ll Base[Maxn + 1];
    
        Linear_Base() { Set(Base, 0); }
        
        inline void Insert(ll val) {
            Fordown (i, Maxn, 0) 
                if (val >> i & 1) {
                    if (!Base[i]) { Base[i] = val; break ; }
                    else val ^= Base[i];
                }
        }
    
        inline ll Max() {
            ll res = 0;
            Fordown (i, Maxn, 0)
                chkmax(res, res ^ Base[i]);
            return res;
        }
    
    };
    
    typedef Linear_Base<60> Info;
    
    Info Merge(Info x, Info y) {
        For (i, 0, 60) if (y.Base[i]) x.Insert(y.Base[i]); return x;
    }
    
    void Add(Info &x, Info y) {
        For (i, 0, 60) if (y.Base[i]) x.Insert(y.Base[i]);
    }
    
    const int N = 20100;
    
    vector<int> G[N];
    
    int n, q, dep[N], anc[N][17], Log2[N]; Info data[N][17]; 
    
    inline ll Calc(int x, int y) {
    	Info res = Merge(data[x][0], data[y][0]);
    	if (dep[x] < dep[y]) swap(x, y);
    	int gap = dep[x] - dep[y];
    	Fordown (i, Log2[gap], 0) 
    		if (gap >> i & 1)
    			Add(res, data[x][i]), x = anc[x][i];
    
    	Add(res, data[x][0]);
    	if (x == y)
    		return Merge(res, data[y][0]).Max();
    
    	Fordown (i, Log2[dep[x]], 0)
    		if (anc[x][i] != anc[y][i]) {
    			Add(res, data[x][i]);
    			Add(res, data[y][i]);
    			x = anc[x][i]; y = anc[y][i];
    		}
    	Add(res, data[x][0]);
    	Add(res, data[y][0]);
    	return Merge(res, data[anc[x][0]][0]).Max();
    }
    
    void Dfs_Init(int u, int fa = 0) {
    	dep[u] = dep[anc[u][0] = fa] + 1;
    	for (int v : G[u]) 
    		if (v != fa) Dfs_Init(v, u);
    }
    
    int main() {
    
    	File();
    
    	n = read<int>(); q = read<int>();
    
    	For (i, 1, n) data[i][0].Insert(read<ll>());
    	For (i, 1, n - 1) {
    		int u = read<int>(), v = read<int>(); 
    		G[u].pb(v); G[v].pb(u);
    	}
    	Dfs_Init(1);
    
    	For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
    	For (j, 1, Log2[n]) For (i, 1, n) {
    		anc[i][j] = anc[anc[i][j - 1]][j - 1];
    		data[i][j] = Merge(data[i][j - 1], data[anc[i][j - 1]][j - 1]);
    	}
    
    	while (q --)
    		printf ("%lld
    ", Calc(read<int>(), read<int>()));
    
    	return 0;
    
    }
    

    「SCOI2016」萌萌哒

    题意

    一个长度为 $ n $ 的大数,用 $ S_1S_2S_3 ldots S_n $表示,其中 $ S_i $ 表示数的第 $ i $ 位,$ S_1 $ 是数的最高位,告诉你一些限制条件,每个条件表示为四个数 $ (l_1, r_1, l_2, r_2) $,即两个长度相同的区间,表示子串 $ S_{l_1}S_{l_1 + 1}S_{l_1 + 2} ldots S_{r_1} $ 与 $ S_{l_2}S_{l_2 + 1}S_{l_2 + 2} ldots S_{r_2} $ 完全相同。

    比如 $ n = 6 $ 时,某限制条件 $ (l_1 = 1, r_1 = 3, l_2 = 4, r_2 = 6) $,那么 (123123)(351351) 均满足条件,但是 (12012)(131141) 不满足条件,前者数的长度不为 $ 6 $,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

    $ 1 leq n leq 10 ^ 5, 1 leq m leq 10 ^ 5, 1 leq {l_i}_1, {r_i}_1, {l_i}_2, {r_i}_2 leq n $ 并且保证 $ {r_i}_1 - {l_i}_1 = {r_i}_2 - {l_i}_2 $。

    题解

    不难发现其实每次就是限制对应的位置数相同。

    然后最后就会限制成 (tot) 种可以填的不同的数,最后的答案就是 (9 imes 10^{tot - 1})

    我们把强制要求相等的数放入并查集中就行了。直接实现这个过程是 (O(n^2 alpha(n))) 的复杂度。

    考虑如何优化,区间对应连边容易想到线段树优化建边。但此处没有那么好用。

    考虑把一个区间拆成 (log) 个长为 (2^i) 的区间。

    具体来说令 (fa[i][j])(i) 向右长为 (2^j) 的区间在并查集上的 (root)

    初始化的时候 (fa[i][j])(root)(i)

    然后一个询问我们就在对应区间上连边就行了。

    但是最后我们区间的连通性并不能代表点的连通性,所以我们需要把连通性下放。

    具体来说把 ((i, j)) 这个点和 ((i, j - 1), (i + 2^{j - 1}, j - 1)) 对应相连就行了。

    最后看 ((i, 0)) 有几个不同的联通块就可以完成了。

    其实前面那个那个不需要拆成 (O(log n)) 个区间。由于是要保证联通,多连是没有关系的,我们可以类似 (ST) 表查询那样,把最大的两个可以代表区间相连。

    复杂度就是 (O((n log n + m) alpha (n)))

    总结

    这题还是比较巧妙的,类似于线段树优化建边的思想,我们可以拆成子区间去解决这个问题。

    类似于这种题,只要构建出模型保证和原图一样的连通性即可。

    代码

    #include <bits/stdc++.h>
    
    #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
    #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
    #define Set(a, v) memset(a, v, sizeof(a))
    #define Cpy(a, b) memcpy(a, b, sizeof(a))
    #define debug(x) cout << #x << ": " << (x) << endl
    #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
    
    using namespace std;
    
    template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
    template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
    
    inline int read() {
        int x(0), sgn(1); char ch(getchar());
        for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
        for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
        return x * sgn;
    }
    
    void File() {
    #ifdef zjp_shadow
    	freopen ("2014.in", "r", stdin);
    	freopen ("2014.out", "w", stdout);
    #endif
    }
    
    const int N = 2e5 + 1e3;
    
    int n, m, fa[N][22], Log2[N];
    
    int find(int x, int y) {
    	return fa[x][y] == x ? x : fa[x][y] = find(fa[x][y], y);
    }
    
    const int Mod = 1e9 + 7;
    
    inline int fpm(int x, int power) {
    	int res = 1;
    	for (; power; power >>= 1, x = 1ll * x * x % Mod)
    		if (power & 1) res = 1ll * res * x % Mod;
    	return res;
    }
    
    int main () {
    
    	File();
    
    	n = read(); m = read();
    
    	For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
    	For (i, 1, n) For (j, 0, Log2[n]) fa[i][j] = i;
    
    	For (i, 1, m) {
    		int p1 = read(), r1 = read(), p2 = read(), r2 = read();
    		int len = Log2[r1 - p1 + 1];
    		fa[find(p1, len)][len] = find(p2, len);
    		fa[find(r1 - (1 << len) + 1, len)][len] = find(r2 - (1 << len) + 1, len);
    	}
    
    	Fordown (j, Log2[n], 1) For (i, 1, n) {
    		int u = find(i, j), v = find(i, j - 1);
    		fa[v][j - 1] = find(u, j - 1);
    		u = find(i, j), v = find(i + (1 << j - 1), j - 1);
    		fa[v][j - 1] = find(u + (1 << j - 1), j - 1);
    	}
    
    	int tot = 0;
    	For (i, 1, n) if(find(i, 0) == i) ++ tot;
    	printf ("%lld
    ", fpm(10, tot - 1) * 9ll % Mod);
    
    	return 0;
    
    }
    
  • 相关阅读:
    Java实现 LeetCode 802 找到最终的安全状态 (DFS)
    Java实现 LeetCode 802 找到最终的安全状态 (DFS)
    Java实现 LeetCode 802 找到最终的安全状态 (DFS)
    Java实现 LeetCode 804 唯一摩尔斯密码词 (暴力)
    Java实现 LeetCode 803 打砖块 (DFS)
    Java实现 LeetCode 804 唯一摩尔斯密码词 (暴力)
    Java实现 LeetCode 803 打砖块 (DFS)
    Java实现 LeetCode 804 唯一摩尔斯密码词 (暴力)
    英文标点
    post sharp 与log4net 结合使用,含执行源码 转拷
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10086623.html
Copyright © 2011-2022 走看看