AtCoder Grand Contest 021

Preface

又是熟悉的EF不会节奏,不过这场主要D太简单,EF难度又太大(看Standing的惨淡通过)

然后我C题细节写萎了调了大半个上午,实在太菜了的说


A - Digit Sum 2

显然可以枚举从哪位(i)以下全部填(9),这一位为(a_i-1),之前的高位保持不变

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=20;
int len,a[N],ans,cur; long long n;
int main()
{
	RI i; scanf("%lld",&n); while (n) a[++len]=n%10,n/=10;
	for (i=1;i<=len;++i) ans+=a[i]; for (i=len;i;--i) if (a[i])
	ans=max(ans,cur+a[i]-1+(i-1)*9),cur+=a[i];
	return printf("%d",ans),0;
}

B - Holes

看到大家都是拿(O(nlog n))的凸包做的我就很方啊,这数据范围直接暴力做不香么

我们考虑先枚举每个点,考虑选择的点在何处时可以对这个点产生贡献,很显然我们可以把这个点的和其他点的连线的中垂线找出来,我们发现这个线形成的图形包含该点的这一侧就是能产生贡献的面积

由于(R)可以看做(infty),因此当这个这个面积有限是答案就是(0),这个可以枚举一条直线并判断下其他直线和它的位置关系即可

当面积无限时,由于无限比无限可以把多余的有限面积剔除掉,因此我们只需要找出所有中垂线的交角中最小的即可

然后我们发现两条中垂线和两条原线段之间是一个双垂直,因此所求交角最小等价于找两条原线段的交角最大,直接大力枚举两条线段即可

最后设找出的角为( heta)(frac{pi - heta}{2pi})即为答案,总复杂度(O(n^3))

(当然知道这个之后很容易看出只有凸包上的点有解,并且求出凸包后交角就一定是两条相邻的线段了)

#include<cstdio>
#include<cmath>
#include<iostream>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=105;
const double pi=acos(-1);
struct Point
{
	int x,y;
	inline Point(CI X=0,CI Y=0) { x=X; y=Y; }
	friend inline Point operator - (const Point& A,const Point& B)
	{
		return Point(A.x-B.x,A.y-B.y);
	}
}p[N],l[N]; int n,m;
typedef Point Vector;
inline int Dot(const Vector& A,const Vector& B)
{
	return A.x*B.x+A.y*B.y;
}
inline int Cross(const Vector& A,const Vector& B)
{
	return A.x*B.y-A.y*B.x;
}
signed main()
{
	RI i,j,k; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld%lld",&p[i].x,&p[i].y);
	if (n==2) return puts("0.5
0.5"),0;
	for (i=1;i<=n;++i)
	{
		for (m=0,j=1;j<=n;++j) if (i!=j) l[++m]=p[j]-p[i];
		bool flag=0; for (j=1;j<=m&&!flag;++j)
		{
			bool f1=0,f2=0; for (k=1;k<=m;++k) if (j!=k&&Dot(l[j],l[k])<0)
			{
				if (Cross(l[j],l[k])>0) f1=1; else f2=1;
			}
			if (f1&&f2) flag=1;
		}
		if (flag) { puts("0.0"); continue; }
		double mx_ag=0; for (j=1;j<=m;++j) for (k=1;k<=m;++k) if (j!=k)
		mx_ag=max(mx_ag,acos(1.0*Dot(l[j],l[k])/sqrt(Dot(l[j],l[j]))/sqrt(Dot(l[k],l[k]))));
		printf("%.10lf
",1.0*(pi-mx_ag)/(2.0*pi));
	}
	return 0;
}

C - Tiling

细节写挂虚度一早上的光阴,最后陈指导帮我调得比我还熟悉我的代码了

首先我们容易想到如果我们把一个(2 imes 2)的格子看做一个单位格子来填两个相同的牌

手玩一些小数据我们发现当(n,m)均为偶数时这样显然是正确的

同时我们发现若为(n,m)中有一个奇数的话可以先把有奇数的那(行)列填了,然后再用上面的方法

那么显然有个问题就是当(n,m)均为奇数的时候上面的那种方法不一定能得到最优的填法

例如我们发现当(n=3,m=3,A=2,B=2)时:

<>^
^.v
v<>

显然是一种合法的构造方法,因此我们现在可以把这个矩阵的左上角的一个(3 imes 3)的矩阵填成如上图所述,然后再对剩下的部分处理

由于(n,m)为奇数,在减去(3)之后剩下的都是偶数,因此用上面的构造方法是正确的

但是要注意下割补后有第三行和第一列要贪心地先定好方案,不然中间可能会填出空隙

#include<cstdio>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int n,m,A,B,TA,TB,x,y; char a[N][N];
inline void paint1(CI sx,CI sy,CI tx,CI ty) //<>
{
	x=sx; y=sy; while (A&&x<=tx&&y<=ty)
	{
		if (y+1<=ty&&a[x][y]=='.'&&a[x][y+1]=='.') { a[x][y]='<',a[x][y+1]='>'; if (++x>tx) y+=2,x=sx; if (!--A) break; }
		if (y+1<=ty&&a[x][y]=='.'&&a[x][y+1]=='.') a[x][y]='<',a[x][y+1]='>',--A; if (++x>tx) y+=2,x=sx; 
	}
}
inline void paint2(CI sx,CI sy,CI tx,CI ty) //^v
{
	x=sx; y=sy; while (B&&x<=tx&&y<=ty)
	{
		if (x+1<=tx&&a[x][y]=='.'&&a[x+1][y]=='.') { a[x][y]='^',a[x+1][y]='v'; if (++y>ty) x+=2,y=sy; if (!--B) break; }
		if (x+1<=tx&&a[x][y]=='.'&&a[x+1][y]=='.') a[x][y]='^',a[x+1][y]='v',--B; if (++y>ty) x+=2,y=sy;
	}
}
int main()
{
	RI i,j; for (scanf("%d%d%d%d",&n,&m,&A,&B),TA=A,TB=B,i=1;i<=n;++i)
	for (j=1;j<=m;++j) a[i][j]='.'; if (2*(A+B)>n*m) return puts("NO"),0;
	if (n&1) for (i=1;i+1<=m&&A;i+=2) a[1][i]='<',a[1][i+1]='>',--A;
	if (m&1) for (i=n&1?2:1;i+1<=n&&B;i+=2) a[i][1]='^',a[i+1][1]='v',--B;
	paint1(n&1?2:1,m&1?2:1,n,m); paint2(n&1?2:1,m&1?2:1,n,m);
	if (!(n&1)||!(m&1)) goto print; else
	{
		if (!A&&!B) goto print; A=TA; B=TB;	if (n<3||m<3||A<=1||B<=1) goto print;
		for (i=1;i<=n;++i) for (j=1;j<=m;++j) a[i][j]='.';
		a[1][1]=a[3][2]='<'; a[1][2]=a[3][3]='>'; A-=2;
		a[2][1]=a[1][3]='^'; a[3][1]=a[2][3]='v'; B-=2;
		for (i=4;i+1<=m&&A;i+=2) a[3][i]='<',a[3][i+1]='>',--A;
		for (i=4;i+1<=n&&B;i+=2) a[i][1]='^',a[i+1][1]='v',--B;
		paint2(1,4,2,m); paint1(1,4,2,m); paint1(4,2,n,m); paint2(4,2,n,m);
	}
	print:
		if (A||B) return puts("NO"),0; 
		for (puts("YES"),i=1;i<=n;++i) puts(a[i]+1); return 0;
}

D - Reversed LCS

作为D题实在简单,和C换换还差不多

容易发现我们现在要求的就是修改(k)次得到的最长回文子序列

显然可以直接DP,(f_{l,r,k})表示([l,r])中修改(k)次的最长回文子序列长度,转移无非是匹配端点或是跳过

复杂度(O(n^3))

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=305;
int n,k,f[N][N][N]; char s[N];
inline int DP(CI l,CI r,CI k)
{
	if (k<0) return -1e9; if (l>r) return 0; if (l==r) return 1; if (f[l][r][k]) return f[l][r][k];
	return f[l][r][k]=max(DP(l+1,r-1,k-(s[l]!=s[r]))+2,max(DP(l+1,r,k),DP(l,r-1,k)));
}
int main()
{
	return scanf("%s%d",s+1,&k),n=strlen(s+1),printf("%d",DP(1,n,k)),0;
}

E - Ball Eat Chameleons

由于这里要统计的不同的球的序列个数而并非史莱姆的,因此我们可以枚举红球个数(a),蓝球个数就是(b=k-a)

首先我们发现有解的条件是红球个数大于等于蓝球个数且红球个数大于(n),然后我们考虑一种喂史莱姆的策略:

对于红球,如果([1,n))中有史莱姆没被喂过红球就喂给它一个,对于蓝球如果([1,n))中有史莱姆被喂过红球没被喂过蓝球就给他个蓝球

剩下球不管红蓝全部喂给(n)即可,不难发现这样一定是最优的

再来考虑怎样的序列合法,设红球为(1),蓝球为(-1),设喂给(n)的红球数量为(t=a-(n-1))

很显然当(t>b)时任何序列都是合法的,否则一个合法的序列需要满足任意一个前缀和(ge -(t-1))(即第(n)个史莱姆吃的蓝球减红球个数永远不能大于等于(t),否则就GG了)

这个问题很经典,就是坐标系上走路的问题,我们把目标点向限制直线(需要转化为碰到就不合法)做对称点后就可以减去不合法方案了

最后注意要特判(a=b)的情况,因为此时整个序列的最后一个一定是蓝球,因此只要也只能考虑前(k-1)个球

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005,mod=998244353;
int n,k,t,fact[N],inv[N],ans;
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
	return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
	RI a,b; for (scanf("%d%d",&n,&k),init(k),a=max((k+1)>>1,n);a<=k;++a)
	b=k-a,t=min(a-(n-1)-1,b),(ans+=(C(k-(a==b),a)-C(k-(a==b),a+t+1)+mod)%mod)%=mod;
	return printf("%d",ans),0;
}

F - Trinity

神仙题。根本想不到的说

首先我们很自然可以想到从左向右DP,但是我们发现此时我们需要记录每一行是否有黑色方格的情况,因此直接GG

我们发现对于行只有一个计数限制,因此如果我们强制每一行均存在至少一个黑色方格,设(f_{i,j})表示(i)(j)列的矩阵,每行均有黑格子的方案数

考虑可以选择一些行空着,最后答案即为(sum_{i=1}^n C_n^i imes f_{i,m})

对于(f_{i,j}),考虑新增一列,枚举这一列多出的空行个数(k)来转移到(f_{i+k,j+1})

  • (k=0),没有新的空行产生,那么相当于在第(j+1)列选(0/1/2)个端点,贡献就是(C_i^2+i+1=frac{i(i+1)}{2}+1)
  • (k>0),产生了(k)个新的空行。那么我们需要求(b_{j+1}-1,c_{j+1}+1)不同的方案,显然这两个格子都是白色的,因此不能出现在多出的(k)行,因此贡献为(C_{i+k+2}^{k+2})(即在([b_j-1,c_j+1])(i+k+2)行中选(k+2)行,把最上/下面的一行作为(b_{j+1}-1)(c_{j+1}+1),中间的(k)行作为新的(k)行,因为这些行和之前的行可以交换)

我们发现DP的转移只和(i)有关,并且如果令(F(x)=sum_{i=0}^n frac{f_i}{i!},H(x)=sum_{i=0}^nfrac{1}{(k+2)!},G(x)=sum_{i=0}^nfrac{f_i}{(i+2)!}),每次(j+1,k>0)时的转移方程等价于(F(x) imes H(x)=G(x))

直接用NTT优化即可,(k=0)可以直接转移,复杂度(O(nmlog n))

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=8005,mod=998244353;
int n,m,lim,ans,fact[N],inv[N],f[N<<2],g[N<<2],h[N<<2];
inline int sum(CI x,CI y)
{
	return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
	return x-y<0?x-y+mod:x-y;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
	return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
namespace Poly
{
	int p,rev[N<<2];
	inline void init(CI n)
	{
		for (lim=1,p=0;lim<=n;lim<<=1,++p);
		for (RI i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<p-1);
	}
	inline void NTT(int *f,CI opt)
	{
		RI i,j,k; for (i=0;i<lim;++i) if (i<rev[i]) swap(f[i],f[rev[i]]);
		for (i=1;i<lim;i<<=1)
		{
			int D=quick_pow(3,~opt?(mod-1)/(i<<1):mod-1-(mod-1)/(i<<1));
			for (j=0;j<lim;j+=(i<<1))
			{
				int W=1; for (k=0;k<i;++k,W=1LL*W*D%mod)
				{
					int x=f[j+k],y=1LL*f[i+j+k]*W%mod; f[j+k]=sum(x,y); f[i+j+k]=sub(x,y);
				}
			}
		}
		if (!~opt)
		{
			int Div=quick_pow(lim); for (i=0;i<lim;++i) f[i]=1LL*f[i]*Div%mod;
		}
	}
};
int main()
{
	RI i,j; for (scanf("%d%d",&n,&m),init(n+2),i=1;i<=n;++i) h[i]=inv[i+2];
	for (Poly::init(n<<1),Poly::NTT(h,1),f[0]=g[0]=i=1;i<=m;++i)
	{
		for (Poly::NTT(g,1),j=0;j<lim;++j) g[j]=1LL*g[j]*h[j]%mod;
		for (Poly::NTT(g,-1),j=0;j<=n;++j)
		f[j]=sum(1LL*g[j]*fact[j+2]%mod,1LL*f[j]*(j*(j+1)/2+1)%mod),g[j]=1LL*f[j]*inv[j]%mod;
		for (j=n+1;j<lim;++j) g[j]=0;
	}
	for (i=0;i<=n;++i) ans=sum(ans,1LL*f[i]*C(n,i)%mod); return printf("%d",ans),0;
}

Postscript

这个礼拜做完20After的目标应该算是完成了,争取CSP前做完16After吧

辣鸡老年选手AFO在即
原文地址:https://www.cnblogs.com/cjjsb/p/13906423.html