A.Make All Equal(思维)
题意:
给你一个循环数组 a _ 1 , a _ 2 , … , a _ n a\_1,a\_2,\ldots,a\_n a_1,a_2,…,a_n。
你最多可以对 a a a执行 n − 1 n-1 n−1次以下操作:每次操作中,你可以选择任意两个相邻的元素,并恰好删除其中一个元素。首、尾元素也视作两个相邻的元素。
你的目标是找出使 a a a中所有元素相等所需的最少运算次数。
分析:
观察题目,其实不难发现,保留一种数字之后,删除操作就可以删除任意其余数字。 找出这些数的最大出现次数,总数减去这个值即为答案。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;
void solve(){
map<int,int>mp;
int maxn=0;
int n;
cin>>n;
for(int i=1;i<=n;++i){
int x;
cin>>x;
mp[x]++;
maxn=max(maxn,mp[x]);
}
cout<<n-maxn<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
B.Generate Permutation(构造)
题意:
有一个长度为 n n n的整数序列 a a a,其中每个元素的初始值为 − 1 -1 −1。
美雪有两台打字机,第一台打字机从左往右写字母,指针最初指向 1 1 1,另一台打字机从右往左写字母,指针最初指向 n n n。
美雪会选择其中一台打字机进行以下操作,直到 a a a变成 [ 1 , 2 , … , n ] [1,2,\ldots,n] [1,2,…,n]的排列。
-
写数:将数组 a a a中不存在的最小正整数写入元素 a _ i a\_i a_i, i i i是指针指向的位置。这种操作只有在 a _ i = − 1 a\_i=-1 a_i=−1时才能执行。
-
回车:将指针返回到初始位置(例如,第一台打字机为 1 1 1,第二台打字机为 n n n)。
-
移动指针:将指针移动到下一个位置,让 i i i成为该操作前指针所指向的位置,如果美雪使用的是第一台打字机,则为 i : = i + 1 i:=i+1 i:=i+1,否则为 i : = i − 1 i:=i-1 i:=i−1。只有在操作之后, 1 ≤ i ≤ n 1\le i\le n 1≤i≤n成立时,才能执行此操作。
你的任务是构造长度为 n n n的任意排列 p p p,使得无论美雪使用哪台打字机, a = p a=p a=p所需的最小回车操作次数都相同。
分析:
观察测试样例,可以发现偶数长度无解。下面考虑奇数长度的情况。
我们假设排列为升序,那么正序需要操作一次,倒序需要操作 n n n次,那么正序和倒序所需要的操作数就为 n + 1 n+1 n+1次,因此需要做的就是让正序和倒序操作数相同。于是进行构造:先找出序列中位数,互换左右两边的数,交换完左边的数是倒着的,右边的数是正着的。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;
void solve(){
int n;
cin>>n;
if(n==1){
cout<<1<<endl;
}
else if(n%2==0)
cout<<-1<<endl;
else{
for(int i=n;i>n/2+1;--i)
cout<<i<<" ";
for(int i=1;i<=n/2+1;++i)
cout<<i<<" ";
cout<<endl;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
C.Guess The Tree(搜索)
题意:
这是一个互动问题。
Misuki选择了一棵有 n n n个节点、索引从 1 1 1到 n n n的秘密树,并要求你使用以下类型的查询来猜测它:
- “? a b”——Misuki会告诉你哪个节点 x x x使 ∣ d ( a , x ) − d ( b , x ) ∣ |d(a,x)-d(b,x)| ∣d(a,x)−d(b,x)∣最小,其中 d ( x , y ) d(x,y) d(x,y)是节点 x x x和 y y y之间的距离。如果存在多个这样的节点,那么Misuki会告诉您哪个节点最小化了 d ( a , x ) d(a,x) d(a,x)。
用最多 15 n 15n 15n次查询找出Misuki秘密树的结构!
分析:
分析每次询问得到的结果,可以发现每次都会给出 a , b a,b a,b路径上的中点,设为 m i d mid mid,那么我们继续询问 a , m i d a,mid a,mid与 m i d , b mid,b mid,b,直到 m i d = = a mid==a mid==a或者 m i d = = b mid==b mid==b。如果找到了答案,将他们存到 a n s ans ans即可。注意需要记录父节点以防重复询问。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=1005;
const int MOD=1000000007;
LL fa[N];
vector<pair<LL,LL>>ans;
int ask(int a, int b){
cout<<"? "<<a<<" "<<b<<endl;
int tmp;
cin>>tmp;
return tmp;
}
void dfs(int a,int b){
if (fa[a] && fa[b])
return;
int mid=ask(a, b);
if(a==mid || mid==b){
fa[b]=a;
ans.push_back({a,b});
return;
}
else{
dfs(a,mid);
dfs(mid,b);
}
}
void solve(){
int n;
cin>>n;
ans.clear();
for(int i=0;i<=n;i++){
fa[i]=0;
}
fa[1]=1;
while(ans.size()<n-1){
for(int i=2;i<=n;i++){
if(fa[i]==0){
dfs(1,i);
}
}
}
cout<<"! ";
for(int i=0;i<ans.size();i++){
cout<<ans[i].first<<" "<<ans[i].second<<" ";
}
cout<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
D.Longest Max Min Subsequence(贪心)
题意:
给你一个整数序列 a _ 1 , a _ 2 , … , a _ n a\_1,a\_2,\ldots,a\_n a_1,a_2,…,a_n。设 S S S是 a a a的所有可能的非空子序列的集合,且没有重复的元素。你的目标是找出 S S S中最长的序列。如果有多个序列,请找出将奇数位置上的项乘以 − 1 -1 −1后,使词序最小的序列。
例如,给定
a
=
[
3
,
2
,
3
,
1
]
a=[3,2,3,1]
a=[3,2,3,1],
S
=
[
1
]
,
[
2
]
,
[
3
]
,
[
2
,
1
]
,
[
2
,
3
]
,
[
3
,
1
]
,
[
3
,
2
]
,
[
2
,
3
,
1
]
,
[
3
,
2
,
1
]
S={[1],[2],[3],[2,1],[2,3],[3,1],[3,2],[2,3,1],[3,2,1]}
S=[1],[2],[3],[2,1],[2,3],[3,1],[3,2],[2,3,1],[3,2,1]。那么
[
2
,
3
,
1
]
[2,3,1]
[2,3,1]和
[
3
,
2
,
1
]
[3,2,1]
[3,2,1]将是最长的,而
[
3
,
2
,
1
]
[3,2,1]
[3,2,1]将是答案,因为
[
−
3
,
2
,
−
1
]
[-3,2,-1]
[−3,2,−1]的词序小于
[
−
2
,
3
,
−
1
]
[-2,3,-1]
[−2,3,−1]。
如果 c c c可以从 d d d中删除几个(可能是零个或全部)元素而得到,那么序列 c c c就是序列 d d d的子序列。
当且仅当以下条件之一成立时,序列 c c c在词法上小于序列 d d d:
-
c c c是 d d d的前缀,但 c ≠ d c\ne d c=d;
-
在 c c c和 d d d不同的第一个位置,序列 c c c中的元素小于 d d d中的相应元素。
分析:
考虑贪心求解,假设要构建的序列为 b b b,长度为 m m m,对于 b _ i b\_i b_i,需要找到合适的范围 [ a , b ] [a,b] [a,b],使其满足对于 [ b + 1 , n ] [b+1,n] [b+1,n],序列 a a a中不同元素的数量为 m − i m-i m−i个。为了解决这个问题,我们引入 l l l数组用于记录每个元素最后的坐标。这样可以将范围中的 b b b转化为 l _ 1 − l _ n l\_1-l\_n l_1−l_n中的最小值。此外,观察发现 a a a和 b b b都是非严格递增。因此,可以借助优先队列或单调栈解决。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 5e5 + 5;
const LL INF = 1e15;
const int MOD=1000000007;
LL l[N],a[N],b[N];
bool vis[N];
struct Node{
LL v,index;
bool operator<(const Node &u) const {
if (v!=u.v)
return v<u.v;
return index>u.index;
}
};
void solve(){
priority_queue<LL, vector<LL>, greater<LL>> lx;
priority_queue<Node>mx,mi;
LL n;
cin>>n;
for (LL i=1;i<=n;i++){
l[i]=INF;
vis[i]=0;
}
for(LL i=1;i<=n;i++){
cin>>a[i];
l[a[i]]=i;
}
for(LL i=1;i<=n;i++){
lx.push(l[i]);
}
for(LL i=1;i<=lx.top();i++){
mx.push({a[i],i});
mi.push({-a[i],i});
}
LL tmp=1,cnt=0;
while(!mi.empty()){
if(!(cnt & 1)){
b[++cnt]=mx.top().v;
vis[b[cnt]]=1;
tmp= mx.top().index + 1;
}
else{
b[++cnt]=-mi.top().v;
vis[b[cnt]]=1;
tmp= mi.top().index + 1;
}
while (!lx.empty() && lx.top() != INF && vis[a[lx.top()]]){
LL j=lx.top();
lx.pop();
for(LL k=j+1;k<=min(lx.top(),n);k++){
mx.push({a[k],k});
mi.push({-a[k],k});
}
}
while(!mx.empty() && (vis[mx.top().v] || mx.top().index<tmp))
mx.pop();
while(!mi.empty() && (vis[-mi.top().v] || mi.top().index<tmp))
mi.pop();
}
cout<<cnt<<endl;
for(LL i=1;i<=cnt;i++){
if(i==cnt)
cout<<b[i];
else
cout<<b[i]<<" ";
}
cout<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
E1.Deterministic Heap (Easy Version)(动态规划)
题意:
考虑一棵完美二叉树,大小为 2 n − 1 2^n-1 2n−1,节点编号为 1 1 1至 2 n − 1 2^n-1 2n−1,根节点为 1 1 1。对于每个顶点 v v v( 1 ≤ v ≤ 2 n − 1 − 1 1\le v\le 2^{n-1}-1 1≤v≤2n−1−1),顶点 2 v 2v 2v是它的左子顶点,顶点 2 v + 1 2v+1 2v+1是它的右子顶点。每个节点 v v v也有一个值 a _ v a\_v a_v。
定义操作 p o p \mathrm{pop} pop如下:
-
将变量 v v v初始化为 1 1 1;
-
重复以下过程,直到顶点
-
在 v v v的子顶点中,选择数值较大的一个,并将该顶点记为 x x x;如果它们的数值相等(即 a _ 2 v = a _ 2 v + 1 a\_{2v}=a\_{2v+1} a_2v=a_2v+1),则可以选择其中任意一个;
-
将 a _ x a\_x a_x赋值给 a _ v a\_v a_v(即 a _ v : = a _ x a\_v:=a\_x a_v:=a_x);
-
将 x x x赋值给 v v v(即 v : = x v:=x v:=x);
-
-
将 − 1 -1 −1赋值给 a _ v a\_v a_v(即 a _ v : = − 1 a\_v:=-1 a_v:=−1)。
如果存在唯一的操作方法,我们就说 p o p \mathrm{pop} pop操作是确定的。换句话说,只要在它们之间进行选择, a _ 2 v ≠ a _ 2 v + 1 a\_{2v}\neq a\_{2v+1} a_2v=a_2v+1就会成立。
如果每个顶点 v v v( 1 ≤ v ≤ 2 n − 1 − 1 1\le v\le 2^{n-1}-1 1≤v≤2n−1−1)的 a _ v ≥ a _ 2 v a\_v\ge a\_{2v} a_v≥a_2v和 a _ v ≥ a _ 2 v + 1 a\_v\ge a\_{2v+1} a_v≥a_2v+1都成立,那么这棵二叉树就叫做最大堆。
如果 p o p \mathrm{pop} pop操作在第一次执行时对堆是确定的,那么最大堆就是确定的。
最初,每个顶点 v v v都有 a _ v : = 0 a\_v:=0 a_v:=0( 1 ≤ v ≤ 2 n − 1 1\le v\le 2^n-1 1≤v≤2n−1),而你的目标是计算通过应用下面的 a d d \mathrm{add} add操作恰好 k k k次所产生的不同确定性最大堆的数量:
- 选择一个整数 v v v( 1 ≤ v ≤ 2 n − 1 1\le v\le 2^n-1 1≤v≤2n−1),并为 1 1 1和 v v v之间路径上的每个顶点 x x x添加 1 1 1到 a _ x a\_x a_x。
如果两个堆中有一个节点的值不同,则认为这两个堆是不同的。
由于答案可能比较大,请输出对 p p p取模的结果。
分析:
题目要求为满二叉树。 设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示层数为 i i i的二叉树,根节点值为 j j j的确定性二叉树的个数(根节点的值即为这颗子树进行的加操作) 发现可以只在根节点操作,即两儿子的值的和 ≤ j ≤j ≤j。
先求两儿子的值的和恰好为 j j j的个数,再通过前缀和处理得到两儿子的值的和 ≤ j ≤j ≤j的个数。记 C [ x ] [ i ] C[x][i] C[x][i]表示在层数为 i i i的二叉树中,根节点值为 x x x的个数,对于该树不必满足确定性。
在 2 i − 1 2^i−1 2i−1个节点中加操作 x x x次,隔板法得
C [ x ] [ i ] = ( x + 2 i − 2 2 i − 2 ) = ( x + 2 i − 2 x ) C[x][i]= \begin{pmatrix} x+2^i−2 \\ 2^i−2\\ \end{pmatrix} = \begin{pmatrix} x+2^i−2 \\ x\\ \end{pmatrix} C[x][i]=(x+2i−22i−2)=(x+2i−2x)
可以通过 x x x次乘法预处理 C [ x ] [ i ] C[x][i] C[x][i]
转移时枚举左子树根的值 k k k,右子树值为 j − k j−k j−k,易得转移: d p [ i ] [ j ] = ∑ _ k = 0 且 k ≠ j − k j d p [ i − 1 ] [ m a x k , j − k ] × C [ m i n k , j − k ] [ i − 1 ] dp[i][j]=\sum\_{k=0且k\neq j-k}^{j} dp[i−1][max{k,j−k}]×C[min{k,j−k}][i−1] dp[i][j]=∑_k=0且k=j−kjdp[i−1][maxk,j−k]×C[mink,j−k][i−1]
进一步枚举 m a x k , j − k max{k,j−k} maxk,j−k可以得到更简洁的公式: d p [ i ] [ j ] = ∑ _ k = ⌊ j 2 ⌋ + 1 j × d p [ i − 1 ] [ k ] × C [ j − k ] [ i − 1 ] dp[i][j]=\sum\_{k=\lfloor \frac{j}{2} \rfloor+1}^{j}×dp[i−1][k]×C[j−k][i−1] dp[i][j]=∑_k=⌊2j⌋+1j×dp[i−1][k]×C[j−k][i−1]
需要前缀和处理 d p [ i ] [ j ] dp[i][j] dp[i][j]。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N = 505;
int n,K;
LL mod;
LL jc[N], inv[N];
LL C[N][N];
LL qpow(LL a, LL b, LL mod){
LL ans = 1;
while(b){
if(b&1)
ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
void init(){
jc[1]=jc[0]=inv[1]=inv[0]=1;
for(int i=2;i<=500;++i)
jc[i]=jc[i-1]*i%mod;
inv[500]=qpow(jc[500],mod-2,mod);
for(int i=499;i>=2;--i)
inv[i]=inv[i+1]*(i+1)%mod;
}
void getC(int x, int i){
LL ans=inv[x];
LL tmp=(qpow(2,i-1,mod)-2+mod)%mod;
for(int i=1;i<=x;++i)
ans=ans*(tmp+i)%mod;
C[x][i]=ans;
}
LL dp[N][N],pd[N][N];
void add(LL &a,LL b){
a=(a+b>=mod)?(a+b-mod):(a+b);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T;
cin>>T;
while(T--){
cin>>n>>K>>mod;
init();
for(int x=0;x<=K;++x)
for(int i=2;i<=n;++i)
getC(x,i);
for(int j=0;j<=K;++j)
dp[1][j]=1;
for(int i=2;i<=n;++i){
for(int j=1;j<=K;++j){
dp[i][j]=pd[i][j]=0;
for(int k=0;k<=j;++k){
if(k==j-k)
continue;
add(pd[i][j],dp[i-1][max(k,j-k)]*C[min(k,j-k)][i]%mod);
}
add(pd[i][j],pd[i][j-1]);
add(dp[i][j],pd[i][j]);
}
}
cout<<dp[n][K]<<endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。