「CSP-S模拟赛」2019第四场


这场考试还是同一个感觉:听音乐误事啊…
T1、T2T1、T2T1T2 码出来之后,听音乐听到不想做题,但是 T3T3T3 又是一个注重思考的题…然后,我暴力都没码出来。
其实这次题的 T3T3T3 还是可做的,下次 好像就是 CSP 了 不要那么浪了…


T1 「JOI 2014 Final」JOI 徽章

题目

点这里

考场思考(正解)

一道签到题。
考虑暴力枚举被修改的点,而这个点被修改之后,最多只会多产生 444 个徽章,暴力匹配就好。

#include<cstdio>

#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
// #define FILEOI

#define cg (c=getchar())
template<class T>inline void qread(T& x){
	x=0;char c;bool f=0;
	while(cg<'0'||'9'<c)if(c=='-')f=1;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int qread(){
	int x=0;char c;bool f=1;
	while(cg<'0'||'9'<c)if(c=='-')f=0;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	return f?x:-x;
}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}
template<class T>void fwrit(const T x){
	if(x<0)return (void)(putchar('-'),fwrit(-x));
	if(x>9)fwrit(x/10);putchar(x%10^48);
}

const int MAXN=1000;
const int MAXM=1000;
const char letter[]={'J','O','I'};

int N,M,ians,tot[MAXN+5][MAXM+5],ans;
char sig[5][5],flg[MAXN+5][MAXM+5];

inline void init(){
	qread(N,M);
	rep(i,1,N)scanf("%s",flg[i]+1);
	// fwrit(N),putchar(' '),fwrit(M),putchar('\n');
	scanf("%s",sig[1]+1);
	scanf("%s",sig[2]+1);
	/*
	rep(i,1,N){
		rep(j,1,M)putchar(flg[i][j]);
		putchar('\n');
	}
	rep(i,1,2){
		rep(j,1,2)putchar(sig[i][j]);
		putchar('\n');
	}
	*/
}

inline void alter(const int i,const int j){
	++tot[i][j];
	++tot[i][j+1];
	++tot[i+1][j];
	++tot[i+1][j+1];
}

inline int chk(const int i,const int j){
	int ret=0;
	ret+=(flg[i][j]!=sig[1][1]);
	ret+=(flg[i][j+1]!=sig[1][2]);
	ret+=(flg[i+1][j]!=sig[2][1]);
	ret+=(flg[i+1][j+1]!=sig[2][2]);
	// printf("chk(%d,%d)==%d\n",i,j,ret);
	return ret;
}

inline bool inside(const int i,const int j){
	return 0<i&&i<=N&&0<j&&j<=M;
}

inline void find_change(){
	int tmp;char memo;
	rep(i,1,N)rep(j,1,M){
		tmp=ians-tot[i][j],memo=flg[i][j];
		rep(k,0,2)if(letter[k]!=memo){
			tmp=ians-tot[i][j];
			flg[i][j]=letter[k];
			if(inside(i-1,j-1)&&chk(i-1,j-1)==0)++tmp;
			if(inside(i-1,j)&&inside(i,j+1)&&chk(i-1,j)==0)++tmp;
			if(inside(i,j-1)&&inside(i+1,j)&&chk(i,j-1)==0)++tmp;
			if(inside(i+1,j+1)&&chk(i,j)==0)++tmp;
			ans=Max(ans,tmp);
		}
		flg[i][j]=memo;
	}
}

signed main(){
#ifdef FILEOI
	freopen("badge.in","r",stdin);
	freopen("badge.out","w",stdout);
#endif
	init();
	rep(i,1,N-1)rep(j,1,M-1)if(chk(i,j)==0)++ians,alter(i,j);
	ans=ians;
	// fwrit(ans);
	find_change();
	fwrit(ans),putchar('\n');
	return 0;
}

T2 「JOI 2015 Final」分蛋糕 2

题目

点这里

考场思考(正解)

其实,我们这道题没有必要破环为链
那么我们怎么做呢?我们允许我们的搜索,或者是 DPDPDP 中存在 l>rl>rl>r 的区间。
而这样的存在,其实就是下面这样的。
在这里插入图片描述
而我们怎么执行 LLLRRR 的移动呢?
看下面这段代码 其实就一行

inline int getPos(const int p){return (p+N-1)%N+1;}

这样,我们首先保证了这个位置是处在 [1,N][1,N][1,N] 之间,不会越界。
接下来,怎么做?
我成绩太差了,只能考虑暴力…
考虑定义一个大法师(dfs)函数

inline int dfs(const int l,const int r,const int cost,const bool Now,const int tot)

这个函数表示我们已经选了区间 [l,r][l,r][l,r] (不保证 l≤rl\le rlr)的全部的蛋糕,而此时我们得到的价值是 costcostcost,现在是 NowNowNow 的回合(Now=0Now=0Now=0 表示 IOIIOIIOI 的回合)时,还有 tottottot 个部分的蛋糕没有选的时候(其实 tottottot 这一维可以省掉,但是我懒得写了 嘿嘿嘿…
那么,我们可以很简单地打出一个暴力,如下:

#include<cstdio>

#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
// #define FILEOI
#define int long long

#define cg (c=getchar())
template<class T>inline void qread(T& x){
	x=0;char c;bool f=0;
	while(cg<'0'||'9'<c)if(c=='-')f=1;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int qread(){
	int x=0;char c;bool f=1;
	while(cg<'0'||'9'<c)if(c=='-')f=0;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	return f?x:-x;
}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}
template<class T>void fwrit(const T x){
	if(x<0)return (void)(putchar('-'),fwrit(-x));
	if(x>9)fwrit(x/10);putchar(x%10^48);
}

const int MAXN=2000;
int N,ans,a[MAXN+5];
int f[MAXN+5][MAXN+5][2];
inline int getPos(const int p){
	return (p+N-1)%N+1;
}

inline void init(){
	qread(N);
	rep(i,1,N)qread(a[i]);
}

inline int dfs(const int l,const int r,const int cost,const bool Now,const int tot){
	if(tot==0)return cost;
	int ans1=0,ans2=0;
	if(Now==0){
		if(a[getPos(l-1)]<a[getPos(r+1)])ans1=dfs(l,getPos(r+1),cost,1,tot-1);
		else ans1=dfs(getPos(l-1),r,cost,1,tot-1);
	}
	else{
		ans1=dfs(getPos(l-1),r,cost+a[getPos(l-1)],0,tot-1);
		ans2=dfs(l,getPos(r+1),cost+a[getPos(r+1)],0,tot-1);
	}
	return Max(ans1,ans2);
}

signed main(){
#ifdef FILEOI
	freopen("divide.in","r",stdin);
	freopen("divide.out","w",stdout);
#endif
	init();
	for(int i=1;i<=N;++i){
		int ret=dfs(i,i,a[i],0,N-1);
		ans=Max(ans,ret);
	}
	fwrit(ans),putchar('\n');
	return 0;
}
/*
f[i][j]:可选的蛋糕从 i -> j

2 8 1 10 9

  j\i  1  2  3  4  5
    1        8
    2           1
    3     12 12    10
    4  9  2  11
    5     2

*/

但是,如果就仅仅是这个代码,很遗憾,你只能得 15pts15pts15pts
怎么朝更高的得分奋斗?
很容易想到——记忆化
但是如果你用一个三维状态 f[i][j][0∣1]f[i][j][0|1]f[i][j][01] 来记忆代码中的 l,r,Nowl,r,Nowl,r,Now 的话,发现正确性不能保证?
至于为什么可以自己去推一下。
考虑倒着进行记忆化。
定义状态 f[l][r][0∣1]f[l][r][0|1]f[l][r][01]:先手为 0∣10|101 时选区间 [l,r][l,r][l,r] 能取到的最大值。
那么我们可以在每次 dfsdfsdfs 之后得到这个记忆化

f[getPos(l-1)][getPos(r+1)][Now]=Max(ans1,ans2)-cost;

什么意思?
假设我们已经搜到区间 [l,r][l,r][l,r],根据大法师的定义,我们已经取完了区间 [l,r][l,r][l,r] 中的蛋糕。
那么显然,没有取的蛋糕的区间就是 [getPos(l−1),getPos(r+1)][getPos(l-1),getPos(r+1)][getPos(l1),getPos(r+1)]
Max(ans1,ans2)Max(ans1,ans2)Max(ans1,ans2) 为最终我们选完所有蛋糕(即区间 [1,N][1,N][1,N])后的最大取值,再减去我们当前搜索的区间(即区间 [l,r][l,r][l,r]),然后就可以得到选区间 [getPos(l−1),getPos(r+1)][getPos(l-1),getPos(r+1)][getPos(l1),getPos(r+1)] 中蛋糕能得到的最大值。
附代码 因为是记忆化搜索,所以跑得还是有点慢

#include<cstdio>

#define rep(i,__l,__r) for(register int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(register int i=__l,i##_end_=__r;i>=i##_end_;--i)
// #define FILEOI
#define int long long

#define cg (c=getchar())
template<class T>inline void qread(T& x){
	x=0;char c;bool f=0;
	while(cg<'0'||'9'<c)if(c=='-')f=1;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int qread(){
	int x=0;char c;bool f=1;
	while(cg<'0'||'9'<c)if(c=='-')f=0;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	return f?x:-x;
}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}
template<class T>void fwrit(const T x){
	if(x<0)return (void)(putchar('-'),fwrit(-x));
	if(x>9)fwrit(x/10);putchar(x%10^48);
}

const int MAXN=2000;
int N,ans,a[MAXN+5],f[MAXN+5][MAXN+5][2];
//f[i][j][0|1] : 区间 [i,j] 是还没有被选过时, 现在是 IOI/JOI 的情况时能选到的最大值

inline int getPos(const int p){return (p+N-1)%N+1;}

inline void init(){
	qread(N);
	rep(i,1,N)qread(a[i]);
}

inline int dfs(const int l,const int r,const int cost,const bool Now,const int tot){
	if(f[getPos(l-1)][getPos(r+1)][Now])return f[getPos(l-1)][getPos(r+1)][Now]+cost;
	if(tot==0)return cost;
	int ans1=0,ans2=0;
	if(Now==0){
		if(a[getPos(l-1)]<a[getPos(r+1)])ans1=dfs(l,getPos(r+1),cost,1,tot-1);
		else ans1=dfs(getPos(l-1),r,cost,1,tot-1);
	}
	else{
		ans1=dfs(getPos(l-1),r,cost+a[getPos(l-1)],0,tot-1);
		ans2=dfs(l,getPos(r+1),cost+a[getPos(r+1)],0,tot-1);
	}
	f[getPos(l-1)][getPos(r+1)][Now]=Max(ans1,ans2)-cost;
	return Max(ans1,ans2);
}

signed main(){
#ifdef FILEOI
	freopen("divide.in","r",stdin);
	freopen("divide.out","w",stdout);
#endif
	init();
	for(int i=1;i<=N;++i){
		int ret=dfs(i,i,a[i],0,N-1);
		ans=Max(ans,ret);
	}
	fwrit(ans),putchar('\n');
	return 0;
}

T3 「CQOI2014」数三角形

题目

点这里

考场思考

听歌听到兴头上,不要打扰我…
虽然写道这里我也在听歌…

正解

其实这道题还是可做的。

膜拜 luoguluoguluogu 大佬 orz or2 orz
膜拜 机房大佬 JZM\text{JZM}JZM orz or2 orz

其实我与 机房大佬 JZM\text{JZM}JZM 的思路如出一辙
因为我们都看了同一篇博客
回到正题,这道题怎么做?
子方法 1
先想怎么得到一点部分分。
考虑暴力
暴力枚举 p1,p2,p3p_1,p_2,p_3p1,p2,p3 的横纵坐标。
其实很好实现,时间复杂度 O(N3M3)O(N^3M^3)O(N3M3)
我考试的时候居然连这个都没打
子方法 2 (正解)
现在应该朝更高的得分去奋斗。
转换思考方向,我们要找的这些三角形有怎样的特点呢?
先看一个网格中的三角形:
在这里插入图片描述
似乎它和我们平常看到的三角形没啥不同的。
但是如果你这样看呢?
在这里插入图片描述
不难看出,△BCE\triangle BCEBCE 被矩形 ABCDABCDABCD 完全包围。
但是这里要否决一种情况,如下:
在这里插入图片描述
这个三角形不能说是被整个矩形完全包围的,还有更小的矩阵更 适合 它。
用这样的眼光去看每一个三角形,不难看出,每一个三角形都被某一个矩形所围起来。
调转思路,我们要求大矩阵 (N,M)(N,M)(N,M) 中有多少个这样的三角形,是否就是被包含在 (N,M)(N,M)(N,M) 中的每一个小矩阵中所含的三角形个数之和?
正确性显然,证明此处不给出。
那么,引出下一个问题:如何快速求出一个矩阵 (i,j)(i,j)(i,j) 中含有多少个被其完全包围的三角形?
这里分开讨论:


情况 1
形如以下被包围的三角形:
在这里插入图片描述
其实也可以叫做:

有且只有一个顶点与矩形顶点重合

就用上图情况来说,F、DF、DFD 的位置有多少个?
显然:(i−1)(j−1)(i-1)(j-1)(i1)(j1)(不与顶点重合)
有四个顶点,共 4(i−1)(j−1)4(i-1)(j-1)4(i1)(j1) 个。


情况 2
这里不再附图,直接描述

有且只有两个点与矩形顶点重合,并且这两个点不是矩形对角线

这样的情况有多少?先给出一张初始图:
在这里插入图片描述
先假设有一个点已与矩形顶点重合,不妨假设 DDD 已与 CCC 重合。
再假设 HHHEEE 重合
那么显然,GGG 只能在线段 ABABAB 上动,共有 i−1i-1i1 个合法位置 (不能与顶点重合)
那么,如果 GGGBBB 重合时呢?
这时 HHH 只能在 AEAEAE 上动,公共 j−1j-1j1 个合法位置。
而每种情况最多出现两次,共 2[(i−1)+(j−1)]2[(i-1)+(j-1)]2[(i1)+(j1)] 种情况。


情况 3
形如以下的三角形:
在这里插入图片描述
我们把它叫做

有且只有两个点与矩形顶点重合,且这两个点构成矩形对角线

这样的情况其实有些复杂,因为这个 PPP 点显然可以随便取 除非它跑出矩形去或者在 DF 这条线上
首先,不考虑它跑到 DFDFDF 线段上去了。
那么这样的点有多少个?
显然有 (i+1)(j+1)−4(i+1)(j+1)-4(i+1)(j+1)4 个,为什么 −4-44 ?它不能与矩形顶点重合。
那么,现在考虑在 DFDFDF 上有多少个点。
首先,我们假设当 P(x,y)P(x,y)P(x,y) 时在 DFDFDF 上,那么就有:
Dy−FyDx−Fx=y−Fyx−Fx\frac{D_y-F_y}{D_x-F_x}=\frac{y-F_y}{x-F_x}DxFxDyFy=xFxyFy接下来怎么化简?
对于任意一条线段,假若我们把它平移到某个端点与原点重合时,它所经过的整点的数量是不变的。
那么我们假设把 FFF 平移到 OOO 上面去,那么就有:
DyDx=yx\frac{D_y}{D_x}=\frac{y}{x}DxDy=xy而其中 x,yx,yx,y 是整数。
接下来,这 x,yx,yx,y 有哪些取值?
这样打开:
p×gcd(Dx,Dy)q×gcd(Dx,Dy)=yx\frac{p\times gcd(D_x,D_y)}{q\times gcd(D_x,D_y)}=\frac{y}{x}q×gcd(Dx,Dy)p×gcd(Dx,Dy)=xy
那么,显然可以看出:

  • x=qx=qx=q 时,y=py=py=p

  • x=q×2x=q\times 2x=q×2 时,y=p×2y=p\times 2y=p×2

  • x=q×3x=q\times 3x=q×3 时,y=p×3y=p\times 3y=p×3

  • x=q×gcd(Dx,Dy)=Dxx=q\times gcd(D_x,D_y)=D_xx=q×gcd(Dx,Dy)=Dx 时,y=p×gcd(Dx,Dy)=Dyy=p\times gcd(D_x,D_y)=D_yy=p×gcd(Dx,Dy)=Dy

要问 x,yx,yx,y 有多少组取值?显然 gcd(Dx,Dy)gcd(D_x,D_y)gcd(Dx,Dy) 种。
但是,去掉端点,就只有 gcd(Dx,Dy)−1gcd(D_x,D_y)-1gcd(Dx,Dy)1 种。
但是每个矩形一共有两条对角线 不可能有只有一条对角线的长方形吧
所以一共有 2×(gcd(lenx,leny)−1)2\times (gcd(len_x,len_y)-1)2×(gcd(lenx,leny)1) 种三角形。
注意我这里所用的是长度,因为我们之前为了处理方便,将 FFF 点假定为了原点。


情况 4
当三个点都与矩形重合时,有多少种情况呢?
对于每一个矩形来说,应该都是固定的吧。
一共有 444 个。


一共的情况,就只有这四种,那么,我们将这些贡献全部加起来,得到w=4(i−1)(j−1)+2[(i−1)+(j−1)]+2[gcd(i,j)−1]+4=6ij−2×gcd(i,j)w=4(i-1)(j-1)+2[(i-1)+(j-1)]+2[gcd(i,j)-1]+4=6ij-2\times gcd(i,j)w=4(i1)(j1)+2[(i1)+(j1)]+2[gcd(i,j)1]+4=6ij2×gcd(i,j)最后用 O(NM)O(NM)O(NM) 枚举 i、ji、jij,再将这些贡献加起来即可。
注:这只是单个矩形的贡献,一共有 (N−lenx+1)(M−leny+1)(N-len_x+1)(M-len_y+1)(Nlenx+1)(Mleny+1) 个长度为 lenxlen_xlenx,宽度为 lenylen_yleny 的矩形。

#include<cstdio>

#define rep(i,__l,__r) for(register int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define dep(i,__l,__r) for(register int i=__l,i##_end_=__r;i>=i##_end_;--i)
// #define FILEOI
#define int long long

#define cg (c=getchar())
template<class T>inline void qread(T& x){
	x=0;char c;bool f=0;
	while(cg<'0'||'9'<c)if(c=='-')f=1;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int qread(){
	int x=0;char c;bool f=1;
	while(cg<'0'||'9'<c)if(c=='-')f=0;
	for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
	return f?x:-x;
}
#undef cg
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline void getInv(int inv[],const int r,const int MOD)
{inv[0]=inv[1]=1;for(int i=2;i<=r;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
template<class T>void fwrit(const T x){
	if(x<0)return (void)(putchar('-'),fwrit(-x));
	if(x>9)fwrit(x/10);putchar(x%10^48);
}

int m,n,ans;

inline void init(){qread(m,n);}

inline int calc(const int i,const int j){return 6*i*j-2*gcd(i,j);}

signed main(){
#ifdef FILEOI
	freopen("triangle.in","r",stdin);
	freopen("triangle.out","w",stdout);
#endif
	init();
	rep(i,1,m)rep(j,1,n)ans+=(m-i+1)*(n-j+1)*calc(i,j);
	fwrit(ans),putchar('\n');
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值