Loading

2025.2.12(CF.R1004.part-div.2)

前言:CF特有的代码短,思维量大到爆炸

A~C

直接看好朋友Yorg的博客yza的A~C

D:

题意:

给你一个长度为\(n\)的序列\(x,x_{1,2......n},x_i\in[1,n]\),还有一个未知的长度为\(n\)的序列\(y\),两个序列可能由\((x_i,y_i)\)这样的\(n\)对二元组构成一个有\(n\)个点的网格图,或者有\(n\)个点的有向图(边权总为1),你可以通过交互的方式进行两次询问,每次询问两个范围在\(1\)\(n\)的值,如果代表网格图则给出两点之间的曼哈顿距离,否则给出两点之间在有向图上的最短距离,由此得出\(x\)\(y\)构成的关系

思路

情况1:

若序列\(x\)至少存在两个大小相同的元素,那么一定存在\(num\in[1,n],num\notin x\),所以我们询问它与其他任意点的值,如果是图,那么最短距离一定是\(0\),否则曼哈顿距离一定不为\(0\)

情况2:

若序列\(x\)成为了\([1,n]\)的一个排列,那么找到其中大小为\(1,n\)的元素,因为是有向图,所以\(dis(1,n)\)最大也就是\(n-1\),而一旦我们调换起始点和终点,在两次询问下,距离不可能同时为\(n-1\),也就是\(dis(1,n),dis(n,1)\)在只有\(n\)条边的有向图中不可能同时为一,而曼哈顿距离\(m(1,n),m(n,1)\)都一定大于等于1,所以据此判断即可

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

void solve(){
	int n;
	scanf("%d",&n);
	std::vector<int> a(n + 1,0);
	std::vector<int> sve;
	for(int i = 1;i <= n;++i){
		int x;
		scanf("%d",&x);
		++a[x];
		sve.push_back(x);	
	}		
	
	for(int i = 1;i <= n;++i){
		if(a[i] == 0){
			int x,y;
			printf("? %d %d\n",i,sve[0]);
			flush(std::cout);
			scanf("%d",&x);
			printf("? %d %d\n",sve[0],i);
			flush(std::cout);
			scanf("%d",&y);
			if(x == 0 || y == 0 || x != y){
				printf("! A\n");
				flush(std::cout);
				return;
			}else{
				printf("! B\n");
				flush(std::cout);
				return;
			}
		}
	}
	int minp = 0,maxp = 0;
	for(int i = 0;i < n;++i){
		if(sve[i] < sve[minp]) minp = i ;
		if(sve[i] > sve[maxp]) maxp = i ;
	} 
	int x,y;
	printf("? %d %d\n",minp + 1,maxp + 1);
	flush(std::cout);
	scanf("%d",&x);
	printf("? %d %d\n",maxp + 1,minp + 1);
	flush(std::cout);
	scanf("%d",&y);
	if(x < n - 1 || y < n - 1 || x != y)
	{
		printf("! A\n");
		flush(std::cout);
		return;
	}else{
		printf("! B\n");
		flush(std::cout);
		return;
	}
}

int main(){
	int T;
	scanf("%d",&T);
	while(T--) solve();
	return 0;
} 

E:

题意:

我们定义一个长度为\(n\)的序列\(a\)神奇的,当且仅当:

\[\forall_{i\in[1,n-1]},min_{j = 1}^{i}\{a_i\}\geq mex_{j=i+1}^{n}\{a_{i}\} \]

此处\(mex\)定义为在该序列集合中不存在的最小的非负整数

我们希望求出序列\(a\)最长神奇子序列的长度,(如果序列\(a\)是序列\(b\)的子序列,则\(a\)可以通过从\(b\)中删除若干(可能为零或全部)元素得到)。

思路:

tips:

1.显然一个没有元素\(0\)的序列的\(mex\)就是\(0\),所以显然,没有元素\(0\)的序列一定是神奇的。

2.若一个序列含有两个0,则其一定不是神奇的

所以思路就很明了了,答案就在\(n-cnt_0\)\(n-cnt_0+1\)之间,其中\(cnt_0\)表示0的个数,那么第一个方案显然一定存在,考虑第二个,可以证明,如果想要让序列中存在一个\(0\),那么选左边的一定是最优的,然后\(O(n)\)计算是否可行,最后输出答案即可。

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

std::unordered_map<int,bool> exsit;
const int INF = 0x3f3f3f3f;
void solve(){
	int n;
	scanf("%d",&n);
	std::vector<int> a(n + 1);
	std::vector<int> pre_min(n + 1);
	std::vector<int> pre_mex(n + 1);
	pre_min[0] = INF;
	bool flag = true,f_ans = true;
	int cnt = 0;
	for(int i = 1;i <= n;++i){
		int x;
		scanf("%d",&x);
		if(x || (flag && !x)){
			a[++cnt] = x;
			if(!x){
				flag = false;
			}
		}
	} 
	
	if(flag)
	{
		printf("%d\n",n);
		return;
	}
	
	
	for(int i = 1;i <= cnt;++i){
		pre_min[i] = std::min(pre_min[i - 1],a[i]);
	}
	
	exsit.clear();
	int tmp_mex = 0;
	for(int i = cnt;i >= 1;--i){
		exsit[a[i]] = true;
		while(exsit[tmp_mex]){
			++tmp_mex;
		}
		pre_mex[i] = tmp_mex;
	}
	
	for(int i = 1;i < cnt;++i){
		if(pre_mex[i + 1] > pre_min[i]){
			f_ans = false;
			break;
		}
	}
	if(f_ans) printf("%d\n",cnt);
	else printf("%d\n",cnt - 1);
}

int main(){
	int T;
	scanf("%d",&T);
	while(T--) solve();
	return 0;
}

F:

题意:

F题题面
所谓不成对不同翻译的不好,其实就是,三个数里面至少有两个相同

思路:

我们考虑构造一些神奇的东西,我们令:

\[pref_i=\oplus_{j=1}^ia_j \]

即:前i个数的异或和。并且我们发现,在每次操作完后,若依旧满足题意,那么一定有:

\[P\oplus Q\oplus R=pref_i \]

又因为一定至少有两个数相同,所以一定有一个数等于\(pref_i\),因为相同的两个数异或之后等于0。所以我们令三元组\(\zeta=(P,Q,R)\),若这是一个合法三元组,那么一定形如下面三种情况:

\[(x,x,pref_i) \]

\[(x,pref_i,x) \]

\[(pref_i,x,x) \]

所以这提醒我们使用动态规划的方法求解这个方案数类型的问题。

定义\(f_{i,x}\),表示经过\(i\)次操作后,达到形如\((x,x,pref_i)、 (x,pref_i,x)、 (pref_i,x,x)\)的状态的方案数。

那么\(f_{0,0}=1\)

现在我们希望用\(f_{i-1,?}\)来更新\(f_{i,x}\),那么我们来分类讨论。想从\(i - 1\)转移过来,无非就3种情况:

\[(x\oplus a_i,x,pref_i)、(x,x\oplus a_i,pref_i)、(x,x,pref_i\oplus a_i) \]

情况1:\(pref_i\oplus a_i\)
那么显然上一个三元组就是\((x,x,pref_{i-1})\),所以说这种情况下\(f_{i,x}=f_{i-1,x}\)

情况2:(\(x\)被更新)

也就是\(x\oplus a_i\),在这种情况下,为了使状态从上一步有效,这三个数中的两个必须相等,第三个必须等于\(pref_{i-1}\)。但是我们又知道\(pref_i\neq pref_{i-1}\),因为\(a_i\neq 0\),也就意味着\(x\)只能等于\(pref_i\)\(pref_{i-1}\)

\((1)\): \(x=pref_i\),此时的\(f_{i,x}\)依旧等于\(f_{i-1,x}\),因为三元组\((pref_i,pref_i,pref_i)\)可以从\((pref_i,pref_i,pref_{i-1}),(pref_i,pref_{i-1},pref_i),(pref_{i-1},pref_i,pref_i)\)这样的状态转移过来,也就是\(f_{i-1,pref_i}\)

\((2)\): \(x=pref_{i-1}\),这是最复杂的情况,先给出转移:

\[f_{i,pref_{i-1}}=3\cdot f_{i-1,pref_{i-1}}+2\cdot f_{i-1,pref_i} \]

因为我们对于这样的情况:

\[(pref_{i-1},pref_{i-1},pref_{i-1}) \]

我们有三种方式转移到现在的状态。

而对于这样的情况:

\[(pref_i,pref_{i},pref_{i-1}),(pref_i,pref_{i-1},pref_i),(pref_{i-1},pref_i,pref_i) \]

我们分别对每个三元组都有两种转移方式。

我们发现,到最后,只有情况\(2(2)\)中的\(f_{i,x}\neq f_{i-1,x}\)。所以出乎意料的,我们只需要计算\(f\)的一个值,并且可以使用滚动数组,甚至不需要倒叙枚举,但是需要注意,由于数组\(a\)中元素很大,我们不能直接开数组,推荐使用散列表,也就是使用STL中的unordered_map容器,最后统计答案的时候,把map里面所存的元素加起来就是最终的答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<unordered_map>
#include<vector>
#define ll long long

const int MOD = 1e9 + 7;
std::unordered_map<int,ll> dp; 
void solve(){
	int n;
	scanf("%d",&n);
	std::vector<int> pref(n + 1); 
	for(int i = 1;i <= n;++i){
		int x;
		scanf("%d",&x);
		if(i == 1)
		{
			pref[i] = x;
			continue;
		}
		pref[i] = pref[i - 1] ^ x;
	}
	
	dp.clear();
	dp[0] = 1;
	for(int i = 1;i <= n;++i){
		dp[pref[i - 1]] = (3 * dp[pref[i - 1]] % MOD + 2 * dp[pref[i]] % MOD) % MOD;
	}
	
	ll ans = 0;
	for(auto it = dp.begin();it != dp.end();++it){
		ans = (ans + (*it).second) % MOD;
	}
	printf("%lld\n",ans);
}

int main(){
	int T;
	scanf("%d",&T);
	while(T--) solve();
	return 0;
}

D1(part div.1)

题意:

到时候自己搜吧,懒得弄过来了,顺便留个心眼,不要在vj上看翻译了。

思路:

dp思路有时间再补了,组合思路反正代码里面有公式,自己理解一下就好了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
const int MOD = 1e9 + 7;
const int MAXN = 1e2 + 10;
const int MAXM = 1e4 + 10;

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 fac[MAXM],inv[MAXM];
void init(){
	fac[0] = 1;
	for(int i = 1;i <= 10001;++i) fac[i] = fac[i - 1] * i % MOD;
	inv[10001] = qpow(fac[10001],MOD - 2);
	for(int i = 10000;i >= 0;--i) inv[i] = inv[i + 1] * (i + 1) % MOD;	
}

int C(int a,int b){
	if(b > a || b < 0 || a < 0) return 0;
	return fac[a] * inv[b] % MOD * inv[a - b] % MOD;	
}

void solve(){
	int n,c,m;
	std::cin >> n >> c >> m;
	for(int i = 1;i <= m;++i){
		int x;
		std::cin >> x;
	}
	
	std::cout << C(c * (n - 1),m - c) << '\n';	
}

signed main(){
	int T;
	std::cin >> T;
	init();
	while(T--) solve();
	return 0;
}
posted @ 2025-03-05 08:23  AxB_Thomas  阅读(12)  评论(0)    收藏  举报
Title