AtCoder Beginner Contest 399


A - Hamming Distance

点击查看代码
void solve() {
    int n;
    std::cin >> n; 
    std::string s, t;
    std::cin >> s >> t;
    int ans = 0;
    for (int i = 0; i < n; ++ i) {
    	ans += s[i] != t[i];
    }

    std::cout << ans << "\n";
}

B - Ranking with Ties

题意:判断每个数是第几大。

排序后看是不是和前一个相等来判断排名。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::pair<int, int>> a(n);
    for (int i = 0; i < n; ++ i) {
    	int x;
    	std::cin >> x;
    	a[i] = {x, i};
    }

    std::ranges::sort(a, std::greater<>());
    std::vector<int> ans(n);
    for (int i = 0; i < n; ++ i) {
    	if (i == 0) {
    		ans[a[i].second] = 1;
    	} else if (a[i].first < a[i - 1].first) {
    		ans[a[i].second] = i + 1;
    	} else {
    		ans[a[i].second] = ans[a[i - 1].second];
    	}
    }

    for (int i = 0; i < n; ++ i) {
    	std::cout << ans[i] << "\n";
    }
}

C - Make it Forest

题意:给你一个图,求需要删去多少边才能使图变成森林。

直接dfs遍历每个联通块,经过的边可以保留,其它都删。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<std::vector<int>> adj(n);
    for (int i = 0; i < m; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    int ans = m;
    std::vector<int> st(n);
    auto dfs = [&](auto & self, int u) -> void {
    	st[u] = 1;
    	for (auto & v : adj[u]) {
    		if (!st[v]) {
    			-- ans;
    			self(self, v);
    		}
    	}
    };

    for (int i = 0; i < n; ++ i) {
    	if (!st[i]) {
    		dfs(dfs, i);
    	}
    }

    std::cout << ans << "\n";
}

D - Switch Seats

题意:给你\(2n\)个数,\([1, n]\)里每个数都出现两次,求有多少对\(a, b\),使得序列里\(a\)出现的两个位置都不相邻和\(b\)出现的两个位置都不相邻然后交换它们某两个位置上的数可以使得两个数的两个出现的位置都相邻。

把位置存下来,然后从前往后遍历,每次和后一个比较,看能不能符合条件。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(2 * n);
    std::vector<std::vector<int>> pos(n);
    for (int i = 0; i < 2 * n; ++ i) {
    	std::cin >> a[i];
    	-- a[i];
    	pos[a[i]].push_back(i);
    }	

    int ans = 0;
    std::set<std::pair<int, int>> s;
    for (int i = 0; i + 1 < 2 * n; ++ i) {
    	if (a[i] != a[i + 1]) {
    		int x = a[i], y = a[i + 1];
    		if (pos[x][1] - pos[x][0] == 1 || pos[y][1] - pos[y][0] == 1) {
    			continue;
    		}
    		std::vector<int> b;
    		b.push_back(pos[x][0]);
    		b.push_back(pos[x][1]);
    		b.push_back(pos[y][0]);
    		b.push_back(pos[y][1]);
    		auto c = b;
    		std::ranges::sort(b);
    		if (b == c) {
    			continue;
    		}
    		bool flag = true;
    		for (int i = 1; i < 4; i += 2) {
    			if (b[i] != b[i - 1] + 1) {
    				flag = false;
    			}
    		}

    		if (flag) {
    			s.insert({std::min(x, y), std::max(x, y)});
    		}
    	}
    }

    std::cout << s.size() << "\n";
}

E - Replace

题意:给你两个字符串\(s, t\),每次把\(s\)中某个字符的位置都换成另一种字符,求几次操作变成\(t\)

赛前两分钟极限过。
我们给每个字符向它需要变成的字符连边,那么显然每个字符的出边只能有一个。那么总共由26个点,每个点恰好有个出边,那么会形成一些环和链,每个环可能由外面的一些点指向它。链很好处理,直接变就行,对于环,不能直接按顺序变,需要先把环上某个点变成另一个点,然后环就断成了链,就可以操作,然后把这个点变回来。那么这个点变成谁?如果有环外面的点指向环,显然变成这个外面的点就行,如果没有,我们可以找一个最终不会出现的点,不过这个会使得操作加1。
那么我们可以用tarjan找环,然后判断每个环外面是否由点指向它,并看能不能找一个点使得最终它不会出现。然后对于每个环,有外面指向它的点可以用就用,不能就只能用不会出现的点,然后答案加一。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s, t;
    std::cin >> s >> t;

    std::array<int, 26> p;
    std::ranges::fill(p, -1);
    std::array<int, 26> in{};
	for (int i = 0; i < n; ++ i) {
		if (p[s[i] - 'a'] != -1 && p[s[i] - 'a'] != t[i] - 'a') {
			std::cout << -1 << "\n";
			return;
		}

		p[s[i] - 'a'] = t[i] - 'a';
		in[t[i] - 'a'] = 1;
	}
		
	std::vector<int> adj[26];
	for (int i = 0; i < 26; ++ i) {
		if (p[i] != -1 && p[i] != i) {
			adj[i].push_back(p[i]);
		}
	}

	std::array<int, 26> dfn{}, low{}, in_stk{}, id{};
	int idx = 0, top = 0, cnt = 0;
	std::vector<int> stk(100);
	std::vector<std::vector<int>> scc(30);
	auto tarjan = [&](auto & self, int u) -> void {
	    dfn[u] = low[u] = ++ idx;
	    stk[ ++ top] = u; in_stk[u] = 1;
	    for (auto & v : adj[u]) {
	        if (!dfn[v]) {
	            self(self, v);
	            low[u] = std::min(low[u], low[v]);
	        } else if (in_stk[v]) {
	            low[u] = std::min(low[u], dfn[v]);
	        }
	    }
	    
	    if (dfn[u] <= low[u]) {
	        ++ cnt;
	        int v;
	        do {
	            v = stk[top -- ];
	            in_stk[v] = 0;
	            id[v] = cnt;
	            scc[cnt].push_back(v);
	        } while (v != u);
	    }
	};

	for (int i = 0; i < 26; ++ i) {
		if (!dfn[i]) {
			tarjan(tarjan, i);
		}
	}

	if (cnt == 1) {
		std::cout << -1 << "\n";
		return;
	}

	int ans = 0;
	for (int i = 0; i < 26; ++ i) {
		ans += p[i] != -1 && p[i] != i;
	}
	std::vector<int> st(cnt + 1);
	for (int i = 1; i <= cnt; ++ i) {
		if (scc[i].size() == 1) {
			if (p[scc[i][0]] != -1 && p[scc[i][0]] != scc[i][0]) {
				st[id[p[scc[i][0]]]] = 1;
			}
		}
	}

	bool flag = false;
	for (int i = 0; i < 26; ++ i) {
		if (!in[i]) {
			flag = true;
			break;
		}
	}
	for (int i = 1; i <= cnt; ++ i) {
		if (scc[i].size() > 1) {
			if (!st[i] && !flag) {
				std::cout << -1 << "\n";
				return;
			}

			if (!st[i]) {
				ans += 1;
			}
		}
	}


	std::cout << ans << "\n";
}

F - Range Power Sum

题意:求数组每个子区间和的\(k\)次方之和。

参考了官方题解。
观察到\(k\)很小,只有10。
那么我们推一下式子,设\(f[k][i]\)为右端点以\(i\)结尾的区间的\(k\)次方的和。即\(f[k][i] = \sum_{l=1}^{i} (\sum_{r=l}^{i} a_r)^k\)
考虑我们加入\(a_{i+1}\)\(f[k][i + 1] = \sum_{l=1}^{i + 1} (\sum_{r=l}^{i + 1} a_r)^k\),这个式子等于\(a_{i+1}^k + \sum_{l=1}^{i} (a_{i+1} + \sum_{r=l}^{i} a_r)^k\),那么根据二项式定理,\((a_{i+1} + \sum_{r=l}^{i} a_r)^k = \sum_{j=0}^{k} C(k, j)a_{i+1}^{k-j}(\sum_{r=l}^{i} a_r)^{j}\),那么\(f[k][i + 1] = a_{i+1}^k + \sum_{l=1}^{i}\sum_{j=0}^{k} C(k, j)a_{i+1}^{k-j}(\sum_{r=l}^{i} a_r)^{j} = a_{i+1}^k + \sum_{j=0}^{k} C(k, j)a_{i+1}^{k-j}\sum_{l=1}^{i}(\sum_{r=l}^{i} a_r)^{j}\),发现\(\sum_{l=1}^{i}(\sum_{r=l}^{i} a_r)^{j}\)就是\(f[j][i]\),那么可以得到\(f[k][i + 1] = a_{i+1}^{k} + \sum_{j=0}^{k}C(k, j)a_{i+1}^{k-j}f[j][i]\)
初始化就是\(f[0][i] = i\),因为以\(i\)结尾的区间有\(i\)个,它们的0次方都是1,加起来就是\(i\)

代码省略取模类。

点击查看代码
struct Comb {
    int n;
    std::vector<Z> _fac;
    std::vector<Z> _invfac;
    std::vector<Z> _inv;
    
    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {
        init(n);
    }
    
    void init(int m) {
        if (m <= n) return;
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);
        
        for (int i = n + 1; i <= m; i++) {
            _fac[i] = _fac[i - 1] * i;
        }
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i--) {
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        }
        n = m;
    }
    
    Z fac(int m) {
        if (m > n) init(2 * m);
        return _fac[m];
    }
    Z invfac(int m) {
        if (m > n) init(2 * m);
        return _invfac[m];
    }
    Z inv(int m) {
        if (m > n) init(2 * m);
        return _inv[m];
    }
    Z binom(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac(n) * invfac(m) * invfac(n - m);
    }
} comb;

void solve() {
    int n, K;
    std::cin >> n >> K;
    std::vector<int> a(n + 1);
    for (int i = 1; i <= n; ++ i) {
    	std::cin >> a[i];
    }

    std::vector f(K + 1, std::vector<Z>(n + 1));
    for (int i = 1; i <= n; ++ i) {
    	f[0][i] = i;
    }
    for (int k = 1; k <= K; ++ k) {
    	for (int i = 1; i <= n; ++ i) {
    		f[k][i] = power<Z>(a[i], k);
    		for (int j = 0; j <= k; ++ j) {
    			f[k][i] += comb.binom(k, j) * power<Z>(a[i], k - j) * f[j][i - 1];
    		}
    	}
    }

    Z ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	ans += f[K][i];
    }

    std::cout << ans << "\n";
}
posted @ 2025-03-29 21:53  maburb  阅读(375)  评论(0)    收藏  举报