• 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) 跑匹配就行了。

  • 相关阅读:
    如何定义开发完成?(Definition of Done)
    Git协同工作流介绍
    Git常用命令拾遗
    搭建基于Docker社区版的Kubernetes本地集群
    Mqtt学习指南
    JavaWeb 学习总结
    异常:org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
    MySQL 插入中文错误:java.sql.SQLException: Incorrect string value:
    Servlet 中文乱码问题解析及详细解决方法
    常用正则表达式
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10368622.html
Copyright © 2020-2023  润新知