Loading

CW寒假年前集训1.20(Codeforces Round 998 div.3)

T1:

这应该不需要解析思路了吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

int a[6];
void solve(){
	std:: cin >> a[1] >> a[2] >> a[4] >> a[5];
	int sum1 = a[1] + a[2];
	int sum2 = a[4] - a[2];
	int sum3 = a[5] - a[4]; 
	if(sum1 == sum2 && sum2 == sum3) std::cout << 3 << '\n';
	else if(sum1 == sum2 || sum2 == sum3 || sum1 == sum3) std::cout << 2 << '\n';
	else std::cout << 1 << '\n';
}

int main(){
	int T;
	std::cin >> T;
	while(T--) solve();
	return 0;
} 

T2:

题意:

\(n\times m\)个牌分在\(n\)头牛手里,每人手里\(m\)个,要有一个排列使得按照这个排列上的顺序出牌,让所有人都可以出完,出牌规则是要大于上一个人出的牌。

思路:

我们可以找到 \(0∼n−1\)对应的牛牛,这就是出牌顺序,接下来\(n∼n\times m−1\)都按这个牛牛顺序出牌,如果有人没有这张牌就是无解情况,否则直接输出最开始的出牌顺序即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>


void solve(){
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n * m);
    for (int i = 0; i < n; ++ i) {
    	for (int j = 0; j < m; ++ j) {
    		int x;
    		std::cin >> x;
    		a[x] = i;
    	}
    }

    std::vector<int> ans(n), last(n, -n);
    for (int i = 0; i < n * m; ++ i) {
    	if (i - last[a[i]] < n) {
    		std::cout << -1 << "\n";
    		return;
    	}

    	last[a[i]] = i;
    	if (i < n) {
    		ans[i] = a[i];
    	}
    }

    for (int i = 0; i < n; ++ i) {
    	std::cout << ans[i] + 1 << " \n"[i == n - 1];
    }
}
int main(){
	int T;
	std::cin >> T;
	while(T--) solve();
	return 0;
}

T3:

题意:

\(n\)个数,每次\(Alice\)选一个数\(a\),\(Bob\)选一个数\(b\),如果\(a+b=k\),那么分数加一,问最终分数。

思路:

分数是固定的,因为如果牌库里有\(k - a\)那么\(Bob\)一定会选。那么计算每个\(a\)\(k - a\)能产生的贡献即可。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>

void solve(){
	int n,k;
    std::cin >> n >> k;
    std::vector<int> cnt(2 * n + 1);
    for (int i = 1; i <= n;++i) {
    	int x;
    	std::cin >> x;
    	++cnt[x];
    }

    int ans = 0;
    for (int i = 1; i <= k / 2;++i) {
    	if (k % 2 == 0 && i == k / 2) ans += cnt[i] / 2;
		else 	ans += std::min(cnt[i], cnt[k - i]);
    }

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

int main(){
	int T;
	std::cin >> T;
	while(T--) solve();
}

T4:

题意:

一个数组\(a\),你可以让两个相邻的数一起减去他们之中的最小值(可执行任意次)。问能不能使数组非递减。

思路:

假设\([1,i]\)已经非递减,那么如果\(a_{i} > a_{i+1}\),那么操作\(i\)\(i+1\)是无效的,现在只能考虑\(i\)\(i-1\),所以让\(a_{i} = a_{i} - a_{i-1}\)。若\(a_{i} \le a_{i+1}\),若我们不操作的话,遇到\(a_{i + 1} > a_{i + 2}\)那么只操作\(i,i+1\)是不够的,因为这样后\(a_{i} = 0\),而此时\(a_{i} < a_{i - 1}\)所以还是要让\(a_{i} = a_{i} - a_{i - 1}\)。若后面无递减情况,那么我们这样操作也不会影响数组的单调性。所以每次都让\(a_{i} = a_{i} - a_{i - 1}\)一定是更优的。

最后不要忘了特判\(a_{1} > a_{2}\)的情况。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>

void solve(){
	int n;
	std::cin >> n;
	std::vector<int> a(n + 1);
	for(int i = 1;i <= n;++i) std::cin >> a[i];
	
	if(a[1] > a[2]) {
		std::cout << "NO" << '\n';
		return;
	} //若a[1] > a[2],由不等式的性质,其无论如何都不可能可行 
	
	for(int i = 2;i + 1 <= n;++i){
		a[i] -= a[i - 1];
		if((a[i] > a[i + 1])) {
			std::cout << "NO" << '\n';	
			return;
		}
	}/*假定[1,i]已经非递减,若a[i] < a[i + 1],则依旧满足非递减的性质,若a[i] > a[i + 1],则调整a[i] 与 a[i + 1] 的大小是无效的 
	   则考虑a[i] > a[i - 1]的关系,显然,让a[i] 下调后依旧不能满足题意(因为此时a[i - 1]已经变成0),那么一定不可能用题所给操作使序列不递减 
	*/ 
	std::cout << "YES" << '\n';
	return;
}

int main(){
	int T;
	std::cin >> T;
	while(T--) solve();
	return 0;
}

T5:

题意:

给你两个图\(f,g\),你可以每次操作给\(f\)加一条边或者减一条边。问让两个图任意两个点联通性相同需要的最小操作数

思路:

这是一道连通性问题,我们考虑用并查集来维护这两个图。

形式化的:我们用\(f_{u}\)\(g_{u}\)来表示点u在两个图分别所在的集合(联通块)

首先,若\(Edge(u,v) \in f\)\(g_{u} \neq g_{u}\),那么这条边一定要删。

接着,我们枚举图\(g\)的每个集合(联通块),若\(\exists v \in g_{u}且v \notin f_{u}\),那么显然\(f\)需要加一条边,同时在图\(f\)的集合里合并\(f_{u}\)\(f_{v}\),最后,别忘了每执行一次操作就让答案贡献加1

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
const int N = 2e5 + 5;
int fa1[N],fa2[N];
 
int findF(int x){
	return (fa1[x] == x ? x : fa1[x] = findF(fa1[x]));
}
void mergeF(int x,int y){
	x = findF(x),y = findF(y);
	if(x == y) return;
	fa1[y] = x;
}

int findG(int x){
	return (fa2[x] == x ? x : fa2[x] = findG(fa2[x]));
}
void mergeG(int x,int y){
	x = findG(x),y = findG(y);
	if(x == y) return;
	fa2[y] = x;
}

void solve(){
	int n,m,k;
	std::cin >> n >> m >> k;
	for(int i = 1;i <= n;++i) fa1[i] = i,fa2[i] = i;
	std::vector<int> u1(m + 1),v1(m + 1),u2(k + 1),v2(k + 1);
	for(int i = 1;i <= m;++i){
		std::cin >> u1[i] >> v1[i];
	} 
	for(int i = 1;i <= k;++i){
		std::cin >> u2[i] >> v2[i];
		mergeG(u2[i],v2[i]);
	}
	
	int ans = 0;
	for(int i = 1;i <= m;++i){
		if(findG(u1[i]) != findG(v1[i])){
			++ans;
		}else{
			mergeF(u1[i],v1[i]);
		}
	}
	
	for(int i = 1;i <= k;++i){
		if(findF(u2[i]) != findF(v2[i])){
			mergeF(u2[i],v2[i]);
			++ans;
		}
	}	
	
	std::cout << ans << '\n';
}

int main(){
	int T;
	std::cin >> T;
	while(T--) solve();
	return 0;
}

T6:(我最爱组合题了!)

题意:

求有多少数组\(a\),满足\(1\le |a| \le n,1 \le a_{i} \le k\),以及\(\prod_{i = 1}^{|a|}a_{i} = x\),其中\((1 \le x \le k)\)

思路:

注意到原题面给出的\(max(k)\)只有\(10^5\),所以他的质因子是有限的,最多只有\(log_{2}k\)个地方不填\(1\),但\(max(n)\)给到了\(9 \times 10^8\)

那么好,我们就可以试着去计算序列乘积大小为\(i\)长度为\(j\)且序列中每个元素都大于\(1\)的方案数,记为\(f_{i,j}\)

所以其递推式也是显然的:

\[f_{i,j} = \sum_{p|i,p > 1}f_{\frac{i}{p},j-1} \]

其组合意义为在乘积大小为\(\frac{i}{p}\)长度为\(j - 1\)的序列后面加上p这个因子。

有了\(f\)后,那么记\(ans_{k}\)为乘积大小为\(k\)的满足条件的数组个数,那么如果我们有\(j\)个不为1的数,数组长度为\(i\)那么有\(\dbinom{i}{j}\)种填法,则:

\[ans_{k} = \sum_{i=1}^n\sum_{j=1}^{min(log_{2}i,n)}\dbinom{i}{j}f_{k,j} \]

又注意到

\[\sum_{i=1}^n\dbinom{i}{j} = \dbinom{n+1}{j+1} \]

这一常用恒等式
所以:

\[ans_{k} = \sum_{j=1}^{min(log_{2}i,n)}\dbinom{n+1}{j+1}f_{k,j} \]

实现过程中的小提示:

我们发现,预处理\(f\)数组时的时间复杂度是$$O(K\sqrt{K}logK)$$的,其实很极限,常数一旦稍微大一点,就会TLE大样例,但其实在很早的时候我们是学过\(logK\)枚举因子的(具体实现看代码),所以复杂度就优化为了$$O(Klog^2K)$$

其次,由于\(n\)实在过大,我们不能直接逆元处理组合数,但你又发现\(logK\)又十分小,所以可以每次一接近常数复杂度的方法去求每一次所要的组合数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#define int long long

const int K = 1e5 + 10;
const int ML = 20;
const int MOD = 998244353;

int dp[K][ML];

int qpow(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = (res * a) % MOD;
		a = (a * a) % MOD;
		b >>= 1;
	}
	return res;
}

int inv(int n){
	return qpow(n,MOD - 2);
}

void solve(){
	int n,k;
	std::cin >> k >> n;
	std::cout << n << ' ';
	for(int i = 2;i <= k;++i){
		int ans = 0;
		int C = (n + 1) * n / 2;
		C %= MOD;
		for(int j = 1;j <= std::min(16LL,n);++j){
			ans += C * dp[i][j];
			ans %= MOD;
			C *= (n - j);
			C %= MOD;
			C *= inv(j + 2);
			C %= MOD; 
		}
		std::cout << ans << ' ';
	}
	std::cout << '\n';
}

signed main(){
	std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr), std::cout.tie(nullptr);
	for(int i = 2;i < K;++i) {
        dp[i][1] = 1;
    }

    for (int j = 2;j <= 16;++j) {
        for (int n = 1;n < K;++n) {
            for(int k = 2 * n;k < K;k += n)
            {
            	dp[k][j] = (dp[k][j] + dp[n][j - 1]) % MOD;
			} 
        }
    }
    
	int T;
	std::cin >> T;
	while(T--) solve(); 
	return 0;
}

T7:

题意:

不想写了,直接给网址好了

题面

思路:

这是一道还不错的思维题。

有几个性质,自己画图就能证明:

1.不论怎么交换翻转,若不看上下相对大小,每一对数的相对位置不会变
2.若\(n \ge 3\),则必然可以使交换两列而不翻转,同理也可以使两对
posted @ 2025-03-05 08:04  AxB_Thomas  阅读(16)  评论(0)    收藏  举报
Title