Codeforces Round #840 (Div. 2) and Enigma 2022 - Cybros LNMIIT
Preface
有史以来打的最爆炸的一场,只写了AB
一个原因是最近由于得了新冠导致疏于锻炼,脑子和手都有点不在状态
另一个原因就是没去想C去开一眼感觉很naive的D(事实确实很naive),结果因为下标爆负了死活调不出来
达成七连掉成就,距离八连掉役满只差一场了233
A. Absolute Maximization
只需判断二进制下每一位是否既有\(0\)又有\(1\),是的话答案这一位就是\(1\),否则为\(0\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=515;
int t,n,a[N],c[10][2];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (i=0;i<10;++i) c[i][0]=c[i][1]=0;
for (i=1;i<=n;++i) for (j=0;j<10;++j) ++c[j][(a[i]>>j)&1];
int ans=0; for (i=0;i<10;++i) if (c[i][0]&&c[i][1]) ans|=(1<<i);
printf("%d\n",ans);
}
return 0;
}
B. Incinerate
话说B题怎么会出细节这么多的模拟来搞心态的,刚开始写挂一个很隐蔽的错误还好后面看出来了
不难发现只要把怪按血量排序后从小到大击杀即可,考虑求出当前杀死剩下的血量最低的怪物所需要的回合数
不难发现这就是一个递减的等差数列求和,求前多少项大于等于某个数的问题,刚开始还在考虑解二次方程的精度问题,后来ztc提醒我直接二分找即可
由于公差是一段后缀的怪物的力量的最小值,可以预处理,总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=100005;
struct data
{
int hp,atk;
friend inline bool operator < (const data& A,const data& B)
{
return A.hp<B.hp;
}
}a[N]; int t,n,k,sfx[N];
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i,j; for (scanf("%lld%lld",&n,&k),i=1;i<=n;++i) scanf("%lld",&a[i].hp);
for (i=1;i<=n;++i) scanf("%lld",&a[i].atk); sort(a+1,a+n+1);
for (sfx[n]=a[n].atk,i=n-1;i;--i) sfx[i]=min(sfx[i+1],a[i].atk);
bool flag=1; int sum=0; for (i=1;i<=n&&flag;i=j)
{
if (k<=0) flag=0;
int del=sfx[i],min_hp=a[i].hp-sum,l=1,r=k/del+1,ret=-1;
while (l<=r)
{
int mid=l+r>>1;
if (mid*k-mid*(mid-1)/2LL*del>=min_hp) ret=mid,r=mid-1; else l=mid+1;
}
if (!~ret) flag=0;
sum+=ret*k-ret*(ret-1)/2LL*del; k-=(ret-1)*del;
for (j=i;j<=n&&flag;++j) if (a[j].hp>sum) break; k-=sfx[j];
}
puts(flag?"YES":"NO");
}
return 0;
}
C. Another Array Problem
这个确实是彩笔了没想到结论,直接GG
首先上结论,当\(n>3\)时,答案为\(n\times \max\limits_{1\le i\le n} a_i\),即我们总可以把整个序列都变成某个数
由于连续对区间\([l,r]\)做两次操作就会使得\([l,r]\)全部变成\(0\),然后对一个端点为\(0\),令一个端点为\(x\)的区间操作就可以把这个区间的数变成\(x\)
那么我们设最大值的下标为\(k\),不妨设\(k>2\)(否则镜像操作即可),那么我们可以先把\([1,k-1]\)全部变成\(0\),然后把\([1,k]\)全部变成\(a_k\)
然后再把\([2,n]\)全部变成\(0\),再把\([1,n]\)全部变成\(a_k\)即可
那么只要考虑\(n=2\)和\(n=3\)的特殊情况,分类讨论下即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],mx;
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),mx=0,i=1;i<=n;++i) scanf("%d",&a[i]),mx=max(mx,a[i]);
if (n>3) { printf("%lld\n",1LL*n*mx); continue; }
if (n==2) { printf("%d\n",max(a[1]+a[2],2*abs(a[1]-a[2]))); continue; }
printf("%lld\n",max(1LL*a[1]+a[2]+a[3],3LL*max(max(a[1],a[3]),max(abs(a[1]-a[2]),abs(a[2]-a[3])))));
}
return 0;
}
D. Valid Bitonic Permutations
原来真是个\(O(n)\)题我大呼坑爹,而且由于我刚开始写的时候没有先给\(x,y\)定序导致细节有点多,把下标搞负了直接爆炸
首先不难发现双调序列的峰值一定是\(n\),那么我们不妨枚举\(n\)所在的位置\(p\),此时剩下两种情况(以下默认\(x<y\))
第一种是\(i,j\)在\(p\)的同一侧,由于\(x<y\)所以一定是\(i<j<p\)
我们设\(F(l,r,x)\)表示从数字区间\([l,r]\)里取出\(x\)个数的方案数,即\(F(l,r,x)=C_{r-l+1}^x\)
则这部分的方案数为\(F(1,x-1,i-1)\times F(x+1,y-1,j-i-1)\times F(y+1,n-1,p-j-1)\)
另一种情况就是\(i,j\)在\(p\)的不同侧,即\(i<p<j\)
这部分的定序就需要一点技巧了,我们先选出小于\(x\)的数放在\(i\)左边,即\(F(1,x-1,i-1)\)
然后此时\([1,x-1]\)中剩下的\((x-1)-(i-1)\)个数只能放在\(j\)的右边了,并且一定是按照顺序排好放在右侧的
因此接下来只要在\([x+1,y-1]\)中取\(n-j-((x-1)-(i-1))\)个数放在\(j\)右侧即可,即\(F(x+1,y-1,n-j-((x-1)-(i-1)))\)
最后就是在\([y+1,n]\)中取\(j-p-1\)个数放在\(p\)和\(j\)之间,即\(F(y+1,n,j-p-1)\),而剩下的数只能按顺序放在\(i\)和\(p\)之间了
因此这部分的方案数为\(F(1,x-1,i-1)\times F(x+1,y-1,n-j-((x-1)-(i-1)))\times F(y+1,n,j-p-1)\)
注意当\(x=n\or y=n\)时要特判下,因为\(p\)的位置已经确定了
最后复杂度就是枚举\(n\)的位置,为\(O(n)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,mod=1e9+7;
int t,n,i,j,x,y,ans,C[N][N];
inline int sum(CI x,CI y)
{
return x+y>=mod?x+y-mod:x+y;
}
inline void init(CI n)
{
RI i,j; for (C[0][0]=i=1;i<=n;++i)
for (C[i][0]=j=1;j<=i;++j) C[i][j]=sum(C[i-1][j],C[i-1][j-1]);
}
inline int F(CI l,CI r,CI t)
{
if (r-l+1<0||t<0) return 0; return C[r-l+1][t];
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (init(100),scanf("%d",&t);t;--t)
{
scanf("%d%d%d%d%d",&n,&i,&j,&x,&y); ans=0;
if (x==n)
{
if (i==1||i==n) puts("0"); else
printf("%d\n",1LL*F(y+1,n-1,j-i-1)*F(1,y-1,n-j)%mod);
continue;
}
if (y==n)
{
if (j==1||j==n) puts("0"); else
printf("%d\n",1LL*F(x+1,n-1,j-i-1)*F(1,x-1,i-1)%mod);
continue;
}
for (RI p=2;p<=n-1;++p) if (p!=i&&p!=j)
{
if (j<p)
{
if (x>y) continue;
ans=sum(ans,1LL*F(x+1,y-1,j-i-1)*F(1,x-1,i-1)%mod*F(y+1,n-1,p-j-1)%mod);
} else if (p<i)
{
if (x<y) continue;
ans=sum(ans,1LL*F(y+1,x-1,j-i-1)*F(1,y-1,n-j)%mod*F(x+1,n-1,i-p-1)%mod);
} else
{
if (x<y) ans=sum(ans,1LL*F(1,x-1,i-1)*F(x+1,y-1,n-j-((x-1)-(i-1)))%mod*F(y+1,n-1,j-p-1)%mod);
else ans=sum(ans,1LL*F(1,y-1,n-j)*F(y+1,x-1,i-1-((y-1)-(n-j)))%mod*F(x+1,n-1,p-i-1)%mod);
}
}
printf("%d\n",ans);
}
return 0;
}
E. Node Pairs
完全想不到的思路,还是我太菜太菜
首先考虑怎么求第一问的答案,从点双我们很容易想到SCC,那么考虑通过枚举SCC的大小来转移DP
设\(f_{i}\)表示组成i-reachable最少需要多少个点,考虑枚举其中的一个SCC的大小\(j\),这样有转移\(f_i=\min_\limits{\frac{j(j-1)}{2}\le i} f_{i-\frac{j(j-1)}{2}}+j\)
这样第一问的答案就是\(f_p\),考虑第二问,显然答案的上界为\(\frac{f_p(f_p-1)}{2}-p\),即所有的除去\(p\)对reachable的点对都是unidirectional的情形
下面考虑一种构造方案使得上界能取到,不妨设得到\(f_p\)的\(j\)的转移序列为\(a_1,a_2,\cdots,a_k\)(即这\(f_p\)个点分为\(k\)个SCC,每个的大小就是\(a_i\))
考虑将\([1,a_1]\)号点放在一个SCC内,\([a_1+1,a_1+a_2]\)号点放在另一个SCC内,以此类推
此时我们再将所有小号点\(u\)向大号点\(v\)连一条有向边\(u\to v\)即可构造出上界的情况
总复杂度就是DP的复杂度\(O(p\sqrt p)\)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
const int N=200005;
using namespace std;
int n,f[N];
int main()
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
for (f[i]=1e9,j=1;1LL*j*(j-1)/2LL<=i;++j)
f[i]=min(f[i],f[i-1LL*j*(j-1)/2LL]+j);
return printf("%d %lld",f[n],1LL*f[n]*(f[n]-1)/2LL-n),0;
}
Postscript
写题不如打雀,今天把握了牌浪一波冲上贪之间,直接起飞