「CSP-S模拟赛」2019第四场
「CSP-S模拟赛」2019第四场
这场考试还是同一个感觉:听音乐误事啊…
把 码出来之后,听音乐听到不想做题,但是 又是一个注重思考的题…然后,我暴力都没码出来。
其实这次题的 还是可做的,下次 好像就是 CSP 了 不要那么浪了…
T1 「JOI 2014 Final」JOI 徽章
题目
考场思考(正解)
一道签到题。
考虑暴力枚举被修改的点,而这个点被修改之后,最多只会多产生 个徽章,暴力匹配就好。
#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
题目
考场思考(正解)
其实,我们这道题没有必要破环为链。
那么我们怎么做呢?我们允许我们的搜索,或者是 中存在 的区间。
而这样的存在,其实就是下面这样的。
而我们怎么执行 与 的移动呢?
看下面这段代码 其实就一行 :
inline int getPos(const int p){return (p+N-1)%N+1;}
这样,我们首先保证了这个位置是处在 之间,不会越界。
接下来,怎么做?
我成绩太差了,只能考虑暴力…
考虑定义一个大法师(dfs)函数:
inline int dfs(const int l,const int r,const int cost,const bool Now,const int tot)
这个函数表示我们已经选了区间 (不保证 )的全部的蛋糕,而此时我们得到的价值是 ,现在是 的回合( 表示 的回合)时,还有 个部分的蛋糕没有选的时候(其实 这一维可以省掉,但是我懒得写了 嘿嘿嘿… )
那么,我们可以很简单地打出一个暴力,如下:
#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
*/
但是,如果就仅仅是这个代码,很遗憾,你只能得 。
怎么朝更高的得分奋斗?
很容易想到——记忆化
但是如果你用一个三维状态 来记忆代码中的 的话,发现正确性不能保证?
至于为什么可以自己去推一下。
考虑倒着进行记忆化。
定义状态 :先手为 时选区间 能取到的最大值。
那么我们可以在每次 之后得到这个记忆化
f[getPos(l-1)][getPos(r+1)][Now]=Max(ans1,ans2)-cost;
什么意思?
假设我们已经搜到区间 ,根据大法师的定义,我们已经取完了区间 中的蛋糕。
那么显然,没有取的蛋糕的区间就是 。
而 为最终我们选完所有蛋糕(即区间 )后的最大取值,再减去我们当前搜索的区间(即区间 ),然后就可以得到选区间 中蛋糕能得到的最大值。
附代码 因为是记忆化搜索,所以跑得还是有点慢
#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」数三角形
题目
考场思考
听歌听到兴头上,不要打扰我…
虽然写道这里我也在听歌…
正解
其实这道题还是可做的。
其实我与 机房大佬 的思路如出一辙
因为我们都看了同一篇博客
回到正题,这道题怎么做?
子方法 1
先想怎么得到一点部分分。
考虑暴力
暴力枚举 的横纵坐标。
其实很好实现,时间复杂度
我考试的时候居然连这个都没打
子方法 2 (正解)
现在应该朝更高的得分去奋斗。
转换思考方向,我们要找的这些三角形有怎样的特点呢?
先看一个网格中的三角形:
似乎它和我们平常看到的三角形没啥不同的。
但是如果你这样看呢?
不难看出, 被矩形 完全包围。
但是这里要否决一种情况,如下:
这个三角形不能说是被整个矩形完全包围的,还有更小的矩阵更 适合
它。
用这样的眼光去看每一个三角形,不难看出,每一个三角形都被某一个矩形所围起来。
调转思路,我们要求大矩阵 中有多少个这样的三角形,是否就是被包含在 中的每一个小矩阵中所含的三角形个数之和?
正确性显然,证明此处不给出。
那么,引出下一个问题:如何快速求出一个矩阵 中含有多少个被其完全包围的三角形?
这里分开讨论:
情况 1
形如以下被包围的三角形:
其实也可以叫做:
有且只有一个顶点与矩形顶点重合
就用上图情况来说, 的位置有多少个?
显然: 个 (不与顶点重合)
有四个顶点,共 个。
情况 2
这里不再附图,直接描述
有且只有两个点与矩形顶点重合,并且这两个点不是矩形对角线
这样的情况有多少?先给出一张初始图:
先假设有一个点已与矩形顶点重合,不妨假设 已与 重合。
再假设 与 重合
那么显然, 只能在线段 上动,共有 个合法位置 (不能与顶点重合)。
那么,如果 与 重合时呢?
这时 只能在 上动,公共 个合法位置。
而每种情况最多出现两次,共 种情况。
情况 3
形如以下的三角形:
我们把它叫做
有且只有两个点与矩形顶点重合,且这两个点构成矩形对角线
这样的情况其实有些复杂,因为这个 点显然可以随便取 除非它跑出矩形去或者在 DF 这条线上
首先,不考虑它跑到 线段上去了。
那么这样的点有多少个?
显然有 个,为什么 ?它不能与矩形顶点重合。
那么,现在考虑在 上有多少个点。
首先,我们假设当 时在 上,那么就有:
接下来怎么化简?
对于任意一条线段,假若我们把它平移到某个端点与原点重合时,它所经过的整点的数量是不变的。
那么我们假设把 平移到 上面去,那么就有:
而其中 是整数。
接下来,这 有哪些取值?
这样打开:
那么,显然可以看出:
-
当 时,
-
当 时,
-
当 时,
… -
当 时,
要问 有多少组取值?显然 种。
但是,去掉端点,就只有 种。
但是每个矩形一共有两条对角线 不可能有只有一条对角线的长方形吧
所以一共有 种三角形。
注意我这里所用的是长度,因为我们之前为了处理方便,将 点假定为了原点。
情况 4
当三个点都与矩形重合时,有多少种情况呢?
对于每一个矩形来说,应该都是固定的吧。
一共有 个。
一共的情况,就只有这四种,那么,我们将这些贡献全部加起来,得到最后用 枚举 ,再将这些贡献加起来即可。
注:这只是单个矩形的贡献,一共有 个长度为 ,宽度为 的矩形。
#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;
}