【洛谷】CYJian的水题大赛 解题报告
\(T1\):八百标兵奔北坡
这应该是一道较水的送分题吧。
理论上来说,正解应该是DP。但是,.前缀和优化暴力就能过。
放上我比赛时打的暴力代码吧(\(hl666\)大佬说这种做法的均摊复杂度为\(O(logn)\),总复杂度应为\(O(nlogn)\),可以接受):
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define N 1000
#define M 1000
using namespace std;
int n,m,Q,sum[N+5][M+5],a[N+5][M+5];
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0;int f=1;char ch;
while(!isdigit(ch=tc())) if(ch=='-') f=-1;
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
x*=f;
}
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline bool check(int x,int y1,int y2)//利用前缀和,判断出第x行y1~y2范围内是否有山
{
return sum[x][min(y2,m)]-sum[x][max(y1-1,0)];
}
int main()
{
register int i,j;
for(read(n),read(m),read(Q),i=1;i<=n;++i)
for(j=1;j<=m;++j)
read(a[i][j]);
for(i=1;i<=n;++i)
for(j=1;j<=m;sum[i][j]+=sum[i][j-1],++j)//统计每一行的前缀和
if(a[i][j]>=max(max(a[i-1][j],a[i+1][j]),max(a[i][j-1],a[i][j+1]))),sum[i][j]=1;//若当前坐标有一坐山,则令sum[i][j]为1
while(Q--)
{
int x,y;bool could=false;read(x),read(y);
for(i=x;i;--i)
if(check(i,y-x+i,y+x-i))//判断是否可行
{
write(x-i),putchar('\n'),could=true;//可以就输出答案,并标记有答案
break;
}
if(!could) puts("Pool Babingbaboom!");//若标记无答案,则输出"Pool Babingbaboom!"
}
return 0;
}
\(T2\):灰化肥,会挥发
这道题一看到数据范围就会想到状压\(DP\)吧!(当然,也不乏有某些大佬会想到用模拟退火来做)
设用\(f[i][j]\)来表示在第\(i\)个谷仓,经过的谷仓集合为\(j\)时走过的最短路径,则$$f[i][j]=max(f[i][j],f[k][(j)xor (1<<(i-1))]+w[k][i])$$其中\(w[k][i]\)表示\(k\)与\(i\)的距离。
我们可以BFS预处理出两两仓库间的距离,然后\(DP\)即可
注:只可惜,我在比赛过程中不停爆0,比赛结束之后,请教\(hl666\)大佬才知道洛谷数据有\(BUG\),有字符串的题目不能打读优,结果白白爆\(0\),也算是一个教训吧!可怜我打了两个多小时的代码
代码如下:
#include<bits/stdc++.h>
#define R 500
#define C 500
#define N 16
using namespace std;
int n,r,c,cnt=0,H,T,x[R*C+5],y[R*C+5],Step[R*C+5],To[30],w[N+5][N+5],vis[R+5][C+5]={0},f[N+5][(1<<N)+5];
char ch[R+5][C+5];
struct Warehouse//存储每个谷仓的信息
{
char Name;
int x,y;
}s[N+5];
string S[N+5][(1<<N)+5];
//不能打读优QwQ
/*inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0;int f=1;char ch;
while(!isdigit(ch=tc())) if(ch=='-') f=-1;
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
x*=f;
}*/
inline void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline void BFS(int Now)//BFS预处理
{
while(H<=T)
{
if(ch[x[H]][y[H]]>='A'&&ch[x[H]][y[H]]<='Z') w[Now][To[ch[x[H]][y[H]]-'A']]=Step[H];
if(x[H]>1&&!vis[x[H]-1][y[H]]&&ch[x[H]-1][y[H]]!='*') vis[x[H]-1][y[H]]=1,x[++T]=x[H]-1,y[T]=y[H],Step[T]=Step[H]+1;//向上走
if(x[H]<r&&!vis[x[H]+1][y[H]]&&ch[x[H]+1][y[H]]!='*') vis[x[H]+1][y[H]]=1,x[++T]=x[H]+1,y[T]=y[H],Step[T]=Step[H]+1;//向下走
if(y[H]>1&&!vis[x[H]][y[H]-1]&&ch[x[H]][y[H]-1]!='*') vis[x[H]][y[H]-1]=1,x[++T]=x[H],y[T]=y[H]-1,Step[T]=Step[H]+1;//向左走
if(y[H]<c&&!vis[x[H]][y[H]+1]&&ch[x[H]][y[H]+1]!='*') vis[x[H]][y[H]+1]=1,x[++T]=x[H],y[T]=y[H]+1,Step[T]=Step[H]+1;//向右走
++H;
}
}
int main()
{
register int i,j,k;
for(cin>>r>>c>>n,i=1;i<=r;++i)
for(j=1;j<=c;++j)
{
cin>>ch[i][j];
if(ch[i][j]>='A'&&ch[i][j]<='Z') s[To[ch[i][j]-'A']=++cnt]=(Warehouse){ch[i][j],i,j};//将每一个谷仓的位置与名称存储下来
}
for(i=1;i<=n;++i)//对每个谷仓进行预处理
{
memset(vis,0,sizeof(vis));
vis[x[H=T=0]=s[i].x][y[0]=s[i].y]=1,Step[0]=0;
BFS(i);
}
for(i=1;i<=n;++i) for(j=1;j<=(1<<n)-1;++j) f[i][j]=1e9;//DP预处理
f[To[0]][1<<(To[0]-1)]=0,S[To[0]][1<<(To[0]-1)]="A";
for(j=1;j<=(1<<n)-1;++j)
for(i=1;i<=n;++i)
{
if(!(j&(1<<(i-1)))||!(j^(1<<(i-1)))) continue;
for(k=1;k<=n;++k)
if((i^k)&&(j&(1<<(k-1)))&&((f[k][j^(1<<(i-1))]+w[k][i]<f[i][j])||(f[k][j^(1<<(i-1))]+w[k][i]==f[i][j]&&S[k][j^(1<<(i-1))]+(s[i].Name)<S[i][j]))) f[i][j]=f[k][j^(1<<(i-1))]+w[k][i],S[i][j]=S[k][j^(1<<(i-1))]+(s[i].Name);
}
int Min=1e9;string ans;
for(i=1;i<=n;++i)
if(f[i][(1<<n)-1]<Min||(f[i][(1<<n)-1]==Min&&S[i][(1<<n)-1]<ans)) Min=f[i][(1<<n)-1],ans=S[i][(1<<n)-1];//统计答案
return write(Min),putchar('\n'),cout<<ans,0;
}
\(T3\):红鲤鱼与绿鲤鱼
很明显,这是一道简(fan)单(ren)的数学题。(\(ykh\)大佬\(3min\)切了此题)。
读题,我们可以发现,无论红鲤鱼的出现时间如何变化,罚时始终是一样的。所以,我们只需要管绿鲤鱼出现的时间,因为只有这个才会影响答案。
仔细考虑一下,我们可以推出下面这个式子:$$\frac{5(2A+B+1)+5C_{B+A-1}^{B-1}(A+B+1)\frac{A+B}{2}}{2}$$
然后,即可暴力去求答案。代码如下:
#include<bits/stdc++.h>
#define ULL unsigned long long
#define YKH 998244853
using namespace std;
ULL n,m;
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(ULL &x)
{
x=0;ULL f=1;char ch;
while(!isdigit(ch=tc())) if(ch=='-') f=-1;
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
x*=f;
}
inline void write(ULL x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline ULL Inv(ULL x,ULL y)
{
ULL res=1;
while(y)
{
if(y&1) (res*=x)%=YKH;
(x*=x)%=YKH,y>>=1;
}
return res;
}
int main()
{
read(n),read(m),n%=YKH;
register ULL i;ULL ans1=((n+m+1)%YKH*(n+m)%YKH)%YKH*(Inv(2,YKH-2)%YKH)%YKH,ans2=1,t=1;
for(i=n+1;i<n+m;++i) (ans2*=i%YKH)%=YKH;
for(i=2;i<m;++i) (ans2*=Inv(i%YKH,YKH-2))%=YKH;
for(i=n+1;i<=n+m;++i) (t*=i%YKH)%=YKH;
for(i=2;i<=m;++i) (t*=Inv(i%YKH,YKH-2))%=YKH;
(ans2*=5ll)%=YKH;
ULL ans=((ans1*ans2)%YKH*(Inv(t%YKH,YKH-2)%YKH))%YKH;
write((ans+5LL*(2LL*n%YKH+m+1)%YKH+YKH)%YKH);
return 0;
}
但是,我们会发现,这个代码会\(TLE\),只能得\(70\)分。怎么办呢?这时就要用卡常大法。
终于,在我的不懈努力下, 我改到了\(95\)分。代码如下:
#include<cstdio>
#include<cctype>
using namespace std;
unsigned long long n,m;
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(unsigned long long &x)
{
x=0;char ch;
while(!isdigit(ch=tc()));
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(unsigned long long x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline unsigned long long Inv(unsigned long long x,unsigned long long y)
{
unsigned long long res=1;
while(y)
{
if(y&1) (res*=x)%=998244853;
(x*=x)%=998244853,y>>=1;
}
return res;
}
int main()
{
read(n),read(m),n%=998244853;
register unsigned long long i;unsigned long long ans1=((n+m+1)%998244853*(n+m)%998244853)%998244853*(Inv(2,998244851)%998244853)%998244853,ans2=1,t=1,x;
for(i=n+1;i<n+m;++i) (ans2*=i)%=998244853,(t*=i)%=998244853;
for(i=2;i<m;++i) (ans2*=x=Inv(i,998244851))%=998244853,(t*=x)%=998244853;
(t*=(n+m)*(Inv(m,998244851))%998244853)%=998244853,(ans2+=ans2<<2)%=998244853;
unsigned long long ans=((ans1*ans2)%998244853*(Inv(t%998244853,998244851)%998244853))%998244853;
write((ans+(10*n%998244853+5*m+5)%998244853+998244853)%998244853);
return 0;
}
此时,我才发现,貌似是我的方法本身有问题。(虽然\(hl666\)跟我用同样的方法,他却AC了,大概是因为我天生自带大常数吧!)
冷静一会儿,我们便可以发现,原式可以转化为$$5(2A+B+1)+5B(A+B+1)$$即$$5AB+5B^2+10A+10B+5$$这样就可以快速求出答案了。(可惜我比赛时没想到)
代码如下:
#include<cstdio>
#include<cctype>
using namespace std;
unsigned long long n,m;
inline char tc()
{
static char ff[100000],*A=ff,*B=ff;
return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(unsigned long long &x)
{
x=0;char ch;
while(!isdigit(ch=tc()));
while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(unsigned long long x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
inline unsigned long long Inv(unsigned long long x,unsigned long long y)
{
unsigned long long res=1;
while(y)
{
if(y&1) (res*=x)%=998244853;
(x*=x)%=998244853,y>>=1;
}
return res;
}
int main()
{
read(n),read(m),n%=998244853;
register unsigned long long i;
unsigned long long ans=((n+m+1)*(n+m)%998244853*(Inv(2,998244851)%998244853)%998244853)*5%998244853;
unsigned long long res=ans*m%998244853*Inv(n+m,998244851)%998244853;
write((res+(10*n%998244853+5*m+5)%998244853+998244853)%998244853);
return 0;
}