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

写题不如打雀,今天把握了牌浪一波冲上贪之间,直接起飞

posted @ 2022-12-20 22:41  空気力学の詩  阅读(139)  评论(0编辑  收藏  举报