20221004校内赛
T1 CF1237D Balanced Playlist
镜子在银河系外围的一圈恒星附近生活,现在它要演奏这些恒星。
这些恒星一共有 \(N\) 颗,按顺时针编号为 \(1\) ~ \(N\) ,编号为 \(i\) 的恒星质量为 \(m_i\) 。
具体地,它会从其中一颗恒星开始,顺时针循环演奏恒星。也就是说,若当前演奏的恒星编号是 \(i\) ,那么下一次就会演奏 \(i+1\) ,若当前演奏的是 \(N\) 号恒星,下一次就会演奏 \(1\) 号,不断循环。
但是,如果轮到一颗恒星时,该恒星的质量的两倍严格小于之前演奏过的恒星的最大质量,那么它就不会演奏这颗恒星,并且立即停止演奏。
对于每一个 \(i\) ,请编程求出:以恒星 \(i\) 为起点演奏时,总共会演奏多少颗恒星,一颗恒星如果被重复演奏,那么每一次都要统计进来。
显然,这道题里出现了环,而在环上操作一般不太方便。根据题意,我们容易发现,在环上最多走两圈就可以得出答案。结合这条性质,保险起见,将原 \(m\) 数组复制三遍,得到一个长度为 \(3n\) 的 \(m\) 数组,化环为链。
接下来,只考虑当前的 \(i\),求出 一个 \(a\) 数组,\(a[i]\) 表示对于 \(m[i]\) 这个数,顺时针方向走最多能经过多少个星球(即满足起点 \(m[i]\) 到终点 \(m[k]\) 之间,不存在一个 \(m[j] * 2 < m[i]\),\(a[i]\) 记录这条路径的长度。这里采用二分的方法来求 \(a[i]\),求的过程中,需要快速求得某个区间的最大值,因此还要求出一个 \(st\) 表。
for(int i = 1 ; i <= n; i++){
cin >> m[i];
m[i + 2 * n] = m[i + n] = m[i];//复制数组,化环为链
st[i + n][0] = st[i + 2 * n][0] = m[i];
st[i][0] = m[i];
}
for(int i = 1; i <= 25; i++){
for(int j = 1; j + (1 << i) - 1 <= 3 * n; j++){
st[j][i] = min(st[j][i - 1],st[j + (1 << i - 1)][i - 1]);//递推求st表
}
}
for(int i = 1; i <= 3 * n; i++){
int l = 0,r = 2 * n + 1;
while(l + 1 < r){//二分
int mid1 = (l + r) / 2;
int k = log(mid1) / log(2);//用于st表求最大值
if(min(st[i][k],st[i + mid1 - (1 << k)][k]) * 2 >= m[i]){//判断当前枚举的这段距离是否合法
l = mid1;
}
else r = mid1;
}
a[i] = l - 1;
}
然后要求出最后的 \(ans\) 数组。我们根据求出的 \(a\) 数组求出从 \(i\) 开始第一个乘 \(2\) 小于 \(m[i]\) 的数的位置,针对它建立一棵线段树,查找其中的最小值,即可求出答案。
完整代码:
#include<bits/stdc++.h>
#define ll i << 1
#define rr (i << 1) + 1
#define mid (tl + tr) / 2
using namespace std;
const int MAXN = 3 * 5e5;
int a[MAXN + 15],n,m[MAXN + 15],st[MAXN + 25][25];
int tree[MAXN * 5 + 20];
void build(int i, int tl,int tr){
if(tl == tr){
tree[i] = a[tl] + tl;
return;
}
build(ll,tl,mid);
build(rr,mid + 1,tr);
tree[i] = min(tree[ll],tree[rr]);
}
int query(int i,int tl,int tr,int l,int r){
if(l > r)return MAXN;
if(tl == l && tr == r)return tree[i];
return min(query(ll,tl,mid,l,min(mid,r)),query(rr,mid + 1,tr,max(l,mid + 1),r));
}
int log2(int k){
int ret = 0,now = 1;
while(now < k / 2){
ret++;
now *= 2;
}
return ret;
}
int main(){
// freopen("song4.in","r",stdin);
// freopen("ans","w",stdout);
cin >> n;
memset(st,0x3f,sizeof st);
int mn = MAXN,mx = -MAXN;
for(int i = 1 ; i <= n; i++){
cin >> m[i];
m[i + 2 * n] = m[i + n] = m[i];//复制数组,化环为链
st[i + n][0] = st[i + 2 * n][0] = m[i];
st[i][0] = m[i];
}
for(int i = 1; i <= 25; i++){
for(int j = 1; j + (1 << i) - 1 <= 3 * n; j++){
st[j][i] = min(st[j][i - 1],st[j + (1 << i - 1)][i - 1]);//递推求st表
}
}
for(int i = 1; i <= 3 * n; i++){
int l = 0,r = 2 * n + 1;
while(l + 1 < r){//二分
int mid1 = (l + r) / 2;
int k = log(mid1) / log(2);//用于st表求最大值
if(min(st[i][k],st[i + mid1 - (1 << k)][k]) * 2 >= m[i]){//判断当前枚举的这段距离是否合法
l = mid1;
}
else r = mid1;
}
a[i] = l - 1;
}
build(1,1,3 * n);
for(int i = 1; i <= n; i++){
int k = query(1,1,3 * n,i,i + a[i]);
int ans = min(k - i + 1,i + a[i]);
if(ans >= 2 * n)cout << "-1 ";
else cout << ans << " ";
}
}
//4
//11 5 2 7
T2 CF1237E Balanced Binary Search Trees
给定 \(N\) ,询问有多少个以 \(1\)~\(N\) 为结点编号的大小为 \(N\) 的二叉树,满足:
- 除了最底下那层,其他层都是满的。
- 左儿子与父亲的编号奇偶性不同,右儿子与父亲的编号奇偶性相同。
- 这是棵二叉查找树。
输出方案数对给定模数 \(M\)
怎么说呢,一道结论题,结论也挺离谱的,推导的过程更离谱。
可以看到,题目中的限制特别强,于是得到以下性质:
- 根节点的奇偶性与 \(N\) 的奇偶性相同。因为是二叉查找树,右儿子编号总大于根节点,因此一直往右走可以走到 \(N\) 节点,且题目中要求右儿子奇偶性与根节点奇偶性相同。
- 右子树的大小总为偶数。令根节点的编号为 \(mid\),那么右子树中最小的节点编号为 \(mid + 1\) 显然与 \(mid\) 奇偶性不同,又因为最大节点编号 \(N\) 与 \(mid\) 奇偶性相同,则可得出右子树大小始终为偶数。
- 左右子树的大小必须尽可能相等。这个不太好证明……可以自己打表或者手算找规律……
接下来进行分类讨论:
令 \(f(x)\) 表示当节点总数为 \(x\) 时完美二叉树的方案总数。
当 \(N\) 为偶数时,应当保证左子树大小为奇数,右子树大小为偶数。可得:
当 \(N\) 为奇数时,应当保证左子树大小和右子树大小都为偶数,且大小相等。将 \(N\) 表示为 \(4 * k + 1\),可得:
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m;
bool check(int now){
if(now == 0)return 0;
if(now == 1 || now == 2)return 1;
if(now % 2 == 0)return check((now - 1) / 2 + 1) * check((now - 1) / 2);
else{
bool flag = 0;
for(int i = 1; i * 4 + 1 <= now; i++){
if(i * 4 + 1 == now){
flag = 1;
return check((now - 1) / 2) * check((now - 1) / 2);
}
}
return flag;
}
}
int main(){
cin >> n >> m;
cout << check(n);
}