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\)是神奇的,当且仅当:
此处\(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题题面
所谓不成对不同翻译的不好,其实就是,三个数里面至少有两个相同
思路:
我们考虑构造一些神奇的东西,我们令:
即:前i个数的异或和。并且我们发现,在每次操作完后,若依旧满足题意,那么一定有:
又因为一定至少有两个数相同,所以一定有一个数等于\(pref_i\),因为相同的两个数异或之后等于0。所以我们令三元组\(\zeta=(P,Q,R)\),若这是一个合法三元组,那么一定形如下面三种情况:
所以这提醒我们使用动态规划的方法求解这个方案数类型的问题。
定义\(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种情况:
情况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}\),这是最复杂的情况,先给出转移:
因为我们对于这样的情况:
我们有三种方式转移到现在的状态。
而对于这样的情况:
我们分别对每个三元组都有两种转移方式。
我们发现,到最后,只有情况\(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;
}

浙公网安备 33010602011771号