A.Make All Equal(思维)
题意:
给你一个循环数组a_1,a_2,…,a_na\_1,a\_2,\ldots,a\_na_1,a_2,…,a_n。
你最多可以对aaa执行n−1n-1n−1次以下操作:每次操作中,你可以选择任意两个相邻的元素,并恰好删除其中一个元素。首、尾元素也视作两个相邻的元素。
你的目标是找出使aaa中所有元素相等所需的最少运算次数。
分析:
观察题目,其实不难发现,保留一种数字之后,删除操作就可以删除任意其余数字。 找出这些数的最大出现次数,总数减去这个值即为答案。
代码:
#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(构造)
题意:
有一个长度为nnn的整数序列aaa,其中每个元素的初始值为−1-1−1。
美雪有两台打字机,第一台打字机从左往右写字母,指针最初指向111,另一台打字机从右往左写字母,指针最初指向nnn。
美雪会选择其中一台打字机进行以下操作,直到aaa变成[1,2,…,n][1,2,\ldots,n][1,2,…,n]的排列。
-
写数:将数组aaa中不存在的最小正整数写入元素a_ia\_ia_i,iii是指针指向的位置。这种操作只有在a_i=−1a\_i=-1a_i=−1时才能执行。
-
回车:将指针返回到初始位置(例如,第一台打字机为111,第二台打字机为nnn)。
-
移动指针:将指针移动到下一个位置,让iii成为该操作前指针所指向的位置,如果美雪使用的是第一台打字机,则为i:=i+1i:=i+1i:=i+1,否则为i:=i−1i:=i-1i:=i−1。只有在操作之后,1≤i≤n1\le i\le n1≤i≤n成立时,才能执行此操作。
你的任务是构造长度为nnn的任意排列ppp,使得无论美雪使用哪台打字机,a=pa=pa=p所需的最小回车操作次数都相同。
分析:
观察测试样例,可以发现偶数长度无解。下面考虑奇数长度的情况。
我们假设排列为升序,那么正序需要操作一次,倒序需要操作nnn次,那么正序和倒序所需要的操作数就为n+1n+1n+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选择了一棵有nnn个节点、索引从111到nnn的秘密树,并要求你使用以下类型的查询来猜测它:
- “? a b”——Misuki会告诉你哪个节点xxx使∣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)是节点xxx和yyy之间的距离。如果存在多个这样的节点,那么Misuki会告诉您哪个节点最小化了d(a,x)d(a,x)d(a,x)。
用最多15n15n15n次查询找出Misuki秘密树的结构!
分析:
分析每次询问得到的结果,可以发现每次都会给出a,ba,ba,b路径上的中点,设为midmidmid,那么我们继续询问a,mida,mida,mid与mid,bmid,bmid,b,直到mid==amid==amid==a或者mid==bmid==bmid==b。如果找到了答案,将他们存到ansansans即可。注意需要记录父节点以防重复询问。
代码:
#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_na\_1,a\_2,\ldots,a\_na_1,a_2,…,a_n。设SSS是aaa的所有可能的非空子序列的集合,且没有重复的元素。你的目标是找出SSS中最长的序列。如果有多个序列,请找出将奇数位置上的项乘以−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]。
如果ccc可以从ddd中删除几个(可能是零个或全部)元素而得到,那么序列ccc就是序列ddd的子序列。
当且仅当以下条件之一成立时,序列ccc在词法上小于序列ddd:
-
ccc是ddd的前缀,但c≠dc\ne dc=d;
-
在ccc和ddd不同的第一个位置,序列ccc中的元素小于ddd中的相应元素。
分析:
考虑贪心求解,假设要构建的序列为bbb,长度为mmm,对于b_ib\_ib_i,需要找到合适的范围[a,b][a,b][a,b],使其满足对于[b+1,n][b+1,n][b+1,n],序列aaa中不同元素的数量为m−im-im−i个。为了解决这个问题,我们引入lll数组用于记录每个元素最后的坐标。这样可以将范围中的bbb转化为l_1−l_nl\_1-l\_nl_1−l_n中的最小值。此外,观察发现aaa和bbb都是非严格递增。因此,可以借助优先队列或单调栈解决。
代码:
#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)(动态规划)
题意:
考虑一棵完美二叉树,大小为2n−12^n-12n−1,节点编号为111至2n−12^n-12n−1,根节点为111。对于每个顶点vvv(1≤v≤2n−1−11\le v\le 2^{n-1}-11≤v≤2n−1−1),顶点2v2v2v是它的左子顶点,顶点2v+12v+12v+1是它的右子顶点。每个节点vvv也有一个值a_va\_va_v。
定义操作pop\mathrm{pop}pop如下:
-
将变量vvv初始化为111;
-
重复以下过程,直到顶点
-
在vvv的子顶点中,选择数值较大的一个,并将该顶点记为xxx;如果它们的数值相等(即a_2v=a_2v+1a\_{2v}=a\_{2v+1}a_2v=a_2v+1),则可以选择其中任意一个;
-
将a_xa\_xa_x赋值给a_va\_va_v(即a_v:=a_xa\_v:=a\_xa_v:=a_x);
-
将xxx赋值给vvv(即v:=xv:=xv:=x);
-
-
将−1-1−1赋值给a_va\_va_v(即a_v:=−1a\_v:=-1a_v:=−1)。
如果存在唯一的操作方法,我们就说pop\mathrm{pop}pop操作是确定的。换句话说,只要在它们之间进行选择,a_2v≠a_2v+1a\_{2v}\neq a\_{2v+1}a_2v=a_2v+1就会成立。
如果每个顶点vvv(1≤v≤2n−1−11\le v\le 2^{n-1}-11≤v≤2n−1−1)的a_v≥a_2va\_v\ge a\_{2v}a_v≥a_2v和a_v≥a_2v+1a\_v\ge a\_{2v+1}a_v≥a_2v+1都成立,那么这棵二叉树就叫做最大堆。
如果pop\mathrm{pop}pop操作在第一次执行时对堆是确定的,那么最大堆就是确定的。
最初,每个顶点vvv都有a_v:=0a\_v:=0a_v:=0(1≤v≤2n−11\le v\le 2^n-11≤v≤2n−1),而你的目标是计算通过应用下面的add\mathrm{add}add操作恰好kkk次所产生的不同确定性最大堆的数量:
- 选择一个整数vvv(1≤v≤2n−11\le v\le 2^n-11≤v≤2n−1),并为111和vvv之间路径上的每个顶点xxx添加111到a_xa\_xa_x。
如果两个堆中有一个节点的值不同,则认为这两个堆是不同的。
由于答案可能比较大,请输出对ppp取模的结果。
分析:
题目要求为满二叉树。 设dp[i][j]dp[i][j]dp[i][j]表示层数为iii的二叉树,根节点值为jjj的确定性二叉树的个数(根节点的值即为这颗子树进行的加操作) 发现可以只在根节点操作,即两儿子的值的和≤j≤j≤j。
先求两儿子的值的和恰好为jjj的个数,再通过前缀和处理得到两儿子的值的和≤j≤j≤j的个数。记C[x][i]C[x][i]C[x][i]表示在层数为iii的二叉树中,根节点值为xxx的个数,对于该树不必满足确定性。
在2i−12^i−12i−1个节点中加操作xxx次,隔板法得
C[x][i]=(x+2i−22i−2)=(x+2i−2x)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)
可以通过xxx次乘法预处理C[x][i]C[x][i]C[x][i]
转移时枚举左子树根的值kkk,右子树值为j−kj−kj−k,易得转移: dp[i][j]=∑_k=0且k≠j−kjdp[i−1][maxk,j−k]×C[mink,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]
进一步枚举maxk,j−kmax{k,j−k}maxk,j−k可以得到更简洁的公式: dp[i][j]=∑_k=⌊j2⌋+1j×dp[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]
需要前缀和处理dp[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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。
1311

被折叠的 条评论
为什么被折叠?



