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;
    
    }
    
  • 相关阅读:
    javascript常用知识点总结
    【HOJ1356】【Miller_rabin素性测试】Prime Judge
    【POJ1568】【极大极小搜索+alpha-beta剪枝】Find the Winning Move
    【CF39E】【博弈论】What Has Dirichlet Got to Do with That?
    【BZOJ2281】【博弈论+DP】 [Sdoi2011]黑白棋
    【HDU3802】【降幂大法+矩阵加速+特征方程】Ipad,IPhone
    【POJ3243】【拓展BSGS】Clever Y
    【HDU2815】【拓展BSGS】Mod Tree
    【模板】【网络流】Dinic
    【模板】【凸包】Graham_scan
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10086623.html
Copyright © 2011-2022 走看看