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

    「HAOI2016」食物链

    题意

    现在给你 (n) 个物种和 (m) 条能量流动关系,求其中的食物链条数。

    (1 leq n leq 100000, 0 leq m leq 200000)

    题解

    拓扑 (dp) 入门题,没什么好讲的。

    但是注意要看清题,单个生物不算食物链。

    代码

    #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 Rep(i, r) for (register int i = (0), i##end = (int)(r); 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
    
    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 ("2060.in", "r", stdin);
    	freopen ("2060.out", "w", stdout);
    #endif
    }
    
    const int N = 1e5 + 1e3;
    
    int n, m, f[N]; 
    
    vector<int> G[N]; int indeg[N];
    
    int main () {
    
    	File();
    
    	n = read(); m = read();
    
    	For (i, 1, m) {
    		int u = read(), v = read();
    		G[u].push_back(v); ++ indeg[v];
    	}
    
    	queue<int> Q;
    	For (i, 1, n) if (!indeg[i] && G[i].size()) Q.push(i), f[i] = 1;
    
    	int ans = 0;
    	while (!Q.empty()) {
    		int u = Q.front(); Q.pop();
    		for (int v : G[u]) {
    			f[v] += f[u];
    			if (!(-- indeg[v])) Q.push(v);
    		}
    		if (!bool(G[u].size())) ans += f[u];
    	}
    	printf ("%d
    ", ans);
    
        return 0;
    
    }
    

    「HAOI2016」放棋子

    题意

    给你一个 (N imes N) 的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放 (N) 枚棋子(障碍的位置不能放棋子),要求你放 (N) 个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。

    (N le 200)

    题解

    题意其实就是给你一个障碍排列 ({A_i}) 求有多少个排列 ({P_i}) 满足对于 (forall i) 都有 (A_i ot = P_i)

    不难发现 (A_i) 的顺序是不影响答案的,那么就是错排数了。

    利用递推公式 (f[n] = (n - 1)(f[n - 1] + f[n - 2]))python 的高精度就可以求解了。(偷懒啦)

    代码

    n = int(input())
    f = [0] * (n + 3)
    f[2] = 1
    for i in range(3, n + 1):
        f[i] = (i - 1) * (f[i - 1] + f[i - 2])
    print(f[n])
    

    「HAOI2016」地图

    题意

    出题人语文老师 die 了。

    有一个 (n) 个点 (m) 条边的仙人掌,其中每个点有一种颜色种类 (a_i)

    (q) 次询问,每次给出三个参数 (ty, x, y) ,表示如果当前 (1 o x) 所有简单路径都封死的情况下,(x) 能到达点的颜色编号 (a_i le y) 且出现次数 (mod 2 = ty) 的颜色有多少种。。

    (n leq 100000, m leq 150000, Q leq 100000, a_i leq 10^6)

    题解

    前面那个仙人掌,然后封路。其实就是对应求出圆方树后 (x) 的子树。(至于这个圆方树不一定要对于点双建新点,用原来的点就行了)

    问题就转化成为多次询问区间中颜色在一段区间内的颜色 (le y) 出现次数为奇/偶的点。

    如果做过 Gty的二逼妹子序列 那么就绝对会做这题啦。

    考虑莫队,那么插入和删除都要 (mathcal O(n sqrt m)) 次,询问区间权值种类只有 (mathcal O(m)) 次。

    如果用线段树维护,瓶颈在于前面插入删除,就变成 (mathcal O(n sqrt m log n)) 应该跑不过。

    利用平衡结合的思路,减少前者的复杂度,增加后者的复杂度。不难想到分块可以做到 (mathcal O(1)- mathcal O(sqrt n)) 或者 (mathcal O(sqrt n) - mathcal O(1)) 插入+询问。

    那么就可以做到 (mathcal O(n sqrt m + m sqrt 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 Rep(i, r) for (register int i = (0), i##end = (int)(r); 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 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 ("2062.in", "r", stdin);
    	freopen ("2062.out", "w", stdout);
    #endif
    }
    
    const int N = 1.5e5 + 1e3;
    
    int n, m;
    
    vector<int> G[N], E[N];
    
    int dfn[N], lowlink[N], stk[N], top;
    
    void Tarjan(int u, int fa = 0) {
    	static int clk = 0;
    	dfn[u] = lowlink[u] = ++ clk;
    	stk[++ top] = u;
    	for (int v : G[u]) if (!dfn[v]) {
    		Tarjan(v, u);
    		chkmin(lowlink[u], lowlink[v]);
    		if (lowlink[v] >= dfn[u]) {
    			int cur;
    			do E[u].pb(cur = stk[top --]); while (cur != v);
    		}
    	} else if (v != fa) chkmin(lowlink[u], dfn[v]);
    }
    
    int efn[N], num[N];
    
    void Dfs_Init(int u, int fa = 0) {
    	static int clk = 0;
    	num[dfn[u] = ++ clk] = u;
    	for (int v : E[u]) 
    		if (v != fa) Dfs_Init(v, u);
    	efn[u] = clk;
    }
    
    int a[N], Hash[N];
    
    int blksz, blkid[N], Beg[N], End[N];
    
    struct Query {
    	int id, opt, l, r, lim;
    } Q[N];
    
    struct Cmp {
    	inline bool operator () (const Query &lhs, const Query &rhs) {
    		if (blkid[lhs.l] != blkid[rhs.l]) return blkid[lhs.l] < blkid[rhs.l];
    		if (lhs.r != rhs.r) return lhs.r < rhs.r;
    		return lhs.id < rhs.id;
    	}
    };
    
    int sum[N][2], times[N];
    
    inline void Insert(int col) {
    	if (times[col]) -- sum[blkid[col]][times[col] & 1];
    	++ sum[blkid[col]][(++ times[col]) & 1];
    }
    
    inline void Delete(int col) {
    	-- sum[blkid[col]][times[col] & 1];
    	if ((-- times[col])) ++ sum[blkid[col]][times[col] & 1];
    }
    
    int ans[N];
    
    int main () {
    
    	File();
    	
    	n = read(); m = read();
    
    	For (i, 1, n)
    		Hash[i] = a[i] = read();
    	sort(Hash + 1, Hash + n + 1);
    	int cnt = unique(Hash + 1, Hash + n + 1) - Hash - 1;
    	For (i, 1, n)
    		a[i] = lower_bound(Hash + 1, Hash + cnt + 1, a[i]) - Hash;
    
    	For (i, 1, m) {
    		int u = read(), v = read();
    		G[u].pb(v); G[v].pb(u);
    	}
    	Tarjan(1); Dfs_Init(1);
    
    	int q = read();
    	blksz = sqrt(max(n, q) + .5);
    	For (i, 1, max(n, q)) {
    		blkid[i] = i / blksz + 1;
    		if (blkid[i] != blkid[i - 1])
    			End[blkid[i - 1]] = i - 1, Beg[blkid[i]] = i;
    	}
    	End[blkid[max(n, q)]] = max(n, q);
    
    	For (i, 1, q) {
    		int opt = read(), x = read(), y = read();
    		y = upper_bound(Hash + 1, Hash + cnt + 1, y) - Hash - 1;
    		Q[i] = (Query) {i, opt, dfn[x], efn[x], y};
    	}
    	sort(Q + 1, Q + q + 1, Cmp());
    
    	int l = 1, r = 0;
    
    	For (i, 1, q) {
    
    		while (r < Q[i].r) Insert(a[num[++ r]]);
    		while (l > Q[i].l) Insert(a[num[-- l]]);
    		while (r > Q[i].r) Delete(a[num[r --]]);
    		while (l < Q[i].l) Delete(a[num[l ++]]);
    
    		int lim = Q[i].lim, res = 0;
    		For (j, 1, blkid[lim] - 1)
    			res += sum[j][Q[i].opt];
    		For (j, Beg[blkid[lim]], lim) if (times[j])
    			res += (times[j] & 1) == Q[i].opt;
    		ans[Q[i].id] = res;
    
    	}
    
    	For (i, 1, q)
    		printf ("%d
    ", ans[i]);
    
    	return 0;
    
    }
    

    「HAOI2016」字符合并

    题意

    有一个长度为 $ n $ 的 $ 01 $ 串,你可以每次将相邻的 $ k $ 个字符合并,得到一个新的字符并获得一定分数。得到的新字符和分数由这 $ k $ 个字符确定。你需要求出你能获得的最大分数。

    $ 1 leq n leq 300, 0 leq c_i leq 1, w_i geq 1, k leq 8$

    题解

    一开始想到 (dp) 了,又不会转移。。。最近为啥这么多这种情况啊 TAT 看来细节还是不会写。

    看到数据范围不难想到一个 区间+状压 (dp) ,令 (f_{l, r, S})([l, r]) 最后合并成 (S) 这个状态的最优答案。

    注意到如果 (|S| > k) 一定不优,我们一定会在当前把它合并掉。

    那么转移的时候如果 (|S| = k) 那么我们就合并就好啦。

    其他的话,转移不需要枚举左右都选了很多个的情况,强制左边选 (|S| - 1) 个右边选 (1) 个就好啦,讨论的情况会少很多QAQ

    这样的话,复杂度是 (mathcal O(n^3 2^k)) 的,可能被卡常。

    我们再进行一点小小的优化,你会发现每次合并的时候,很多合并对应的方案是相同的。我们在枚举断点那里每次跳 (k - 1) 然后合并就好啦 qwq

    复杂度此时就变成 (mathcal O(displaystyle frac{n^32^k}k)) 啦,跑的还挺快的。

    总结

    最优化转移还是那句话,宜多不宜少。你把很多个更优秀的状态记到劣一点的状态上是不会影响答案的,此时转移会变得容易许多。

    然后想一个看起来不对的 (dp) ,猜测一下它能包含所有状态,举一下反例,发举不出,那么此时它就能包含所有可能的状态啦。

    代码

    #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 Rep(i, r) for (register int i = (0), i##end = (int)(r); 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
    
    using namespace std;
    
    using ll = long long;
    
    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 ("2063.in", "r", stdin);
    	freopen ("2063.out", "w", stdout);
    #endif
    }
    
    const int N = 310;
    
    const ll inf = 0x3f3f3f3f3f3f3f3f;
    
    ll f[N][N][1 << 8], g[2];
    
    char str[N]; int a[N], c[N], w[N];
    
    int main () {
    
    	File();
    
    	int n = read(), k = read();
    
    	scanf ("%s", str + 1);
    	For (i, 1, n) a[i] = str[i] ^ 48;
    	Rep (i, 1 << k) c[i] = read(), w[i] = read();
    
    	For (i, 1, n) For (j, i, n) Rep (S, 1 << k) f[i][j][S] = -inf;
    
    	Fordown (i, n, 1) For (j, i, n) {
    		if (i == j) {
    			f[i][j][a[i]] = 0; continue;
    		}
    		int len = (j - i) % (k - 1); if (!len) len = k - 1;
    		for (int mid = j; mid > i; mid -= k - 1) Rep (S, 1 << len) {
    			chkmax(f[i][j][S << 1], f[i][mid - 1][S] + f[mid][j][0]);
    			chkmax(f[i][j][S << 1 | 1], f[i][mid - 1][S] + f[mid][j][1]);
    		}
    
    		if (len == k - 1) {
    			g[0] = g[1] = -inf;
    			Rep (S, 1 << k) 
    				chkmax(g[c[S]], f[i][j][S] + w[S]);
    			Rep (id, 2) f[i][j][id] = g[id];
    		}
    	}
    
    	ll ans = -inf;
    	Rep (S, 1 << k) 
    		chkmax(ans, f[1][n][S]);
    	printf ("%lld
    ", ans);
    
        return 0;
    
    }
    

    「HAOI2016」找相同字符

    可以参考我原来写的 后缀数组小结 ,里面的最后一道例题就是啦。

    这个也可以广义 (SAM) 解决,或者一个串在另外一个 (SAM) 跑匹配就行了。

  • 相关阅读:
    博客园
    未释放的已删除文件
    ssh连接缓慢
    剑指 Offer 38. 字符串的排列
    剑指 Offer 37. 序列化二叉树
    剑指 Offer 50. 第一个只出现一次的字符
    剑指 Offer 36. 二叉搜索树与双向链表
    剑指 Offer 35. 复杂链表的复制
    剑指 Offer 34. 二叉树中和为某一值的路径
    剑指 Offer 33. 二叉搜索树的后序遍历序列
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10368622.html
Copyright © 2011-2022 走看看