【分析】我感觉这题应该有一个比较好的贪心策略,就是“S”形走。我感觉这样挺靠谱的,然后也就这么敲了。当然这个是比较感性的,比较靠直觉,但是还是敲了。。。
【代码】
#include
using namespace std;
#define oo 1000000007
#define M 1005
int n,m,lim,ans=-1,now=1;
char mp[M][M];
int to[M][M];
int main(){
scanf("%d %d",&n,&m);
for(int i=n;i>=1;i--){
scanf("%s",mp[i]+1);
to[0][i]=oo;
for(int j=1;j<=m;j++){
if(mp[i][j]!='J')continue;
if(to[0][i]==oo)to[0][i]=j;
to[1][i]=max(to[1][i],j);
lim=max(lim,i);
}
}
for(int i=1;i<=lim;i++){
if(i==lim){
if(i%2==1)ans+=to[1][i]-now;
else ans+=now-to[0][i];
}
else if(i%2==0){
int p=min(to[0][i],to[0][i+1]);
if(now>p){
ans+=now-p;
now=p;
}
}
else{
int p=max(to[1][i],to[1][i+1]);
if(p>now){
ans+=p-now;
now=p;
}
}
}
ans+=lim;
printf("%d\n",ans);
return 0;
}
【分析】
题目比较清楚。题目求一个最小值,而且答案是具有单调性的,这就想到了二分。
二分时间。但是怎么判这种情况是否合法呢?如果摩托车只是横着或者竖着,当然是 O(n) 记录在数组中判一下。但是对角线怎么判呢?是否也能 O(n) 判呢?既然是对角线,假如外面有一个巨大的正方形,我能够算出这个点算出解析式y=x+k,然后知道最后落在哪。
敲完代码,又突然发现不用二分。。。直接for过来就好了。
#include
using namespace std;
#define M 1000005
#define N 2000005
int X[M],Y[M],cnt1[N],cnt2[N];
int n,k;
int main(){
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
int x,y;
scanf("%d %d",&x,&y);
X[x]++,Y[y]++;
cnt1[x+y+M]++,cnt2[x-y+M]++;
bool f=X[x]>=k|Y[y]>=k|cnt1[x+y+M]>=k|cnt2[x-y+M]>=k;
if(f){
printf("%d\n",i);
return 0;
}
}
puts("-1");
return 0;
}
【分析】
都已经是D题了,这道题肯定不会这么简单!然后就开始打表找规律了。然后 然后就放弃了。
之后就当是模拟题写了,因为怎么搞都不会超时啊。因为我最讨厌的就是这种模拟题,所以我调了2个小时。(一共也才3个小时)。这也体现出我的代码功底还不到位。
这里贴的是我的一位学长的代码,以及他对模拟这题一些想法。
【代码】
/*
理解好题目后不妨称作是一道模拟题。
从前向后扫描这个数,如果遇到了一个位置与上一个位置的奇偶性相同,就开始修改。
修改共有两种方法,改大和改小,为了和原数尽可能接近,改大的数之后的部分用0和1交替补充,改小的用8和9交替补充。最后再用高精减比较距离。修改的过程中有一些特殊情况,比如进位等稍微考虑一下可以发现,存在进位,退位等情况的修改都不会得到更优的答案,所以可以忽略。
总体思路还是比较简单的,就是高精有点麻烦。
*/
const int M=1005,P=1e7;
int n,flag=-1;//0b 1s
int xp[10];
char A[M],B[M],S[M];
void make_big(){
int p=1,k=(A[0]-'0')&1;
B[0]=A[0];
while(p1;
if(((A[p]-'0')&1)!=k)break;
B[p]=A[p];p++;
}
if(A[p]=='9'){flag=1;return;}
if(p1;
while(++p1;B[p]=k+'0'; }
}
void make_small(){
int p=1,k=(A[0]-'0')&1;
S[0]=A[0];
while(p1;
if(((A[p]-'0')&1)!=k)break;
S[p]=A[p];p++;
}
if(A[p]=='0'){flag=0;return;}
if(p1;
while(++p1;S[p]=k+'8'; }
}
void init(){
int t=1;
for(int i=0;i<=7;i++){
xp[i]=t;t*=10;
}
}
struct BigInt{
int num[150];
BigInt(){memset(num,0,sizeof(num));}
void push(char *str){
for(int i=0;iint t=str[n-i]-'0';
num[i/8]+=xp[i%8]*t;//压位
}
}
};
BigInt minus(BigInt a,BigInt b){
BigInt res;
for(int i=0;i<150;i++){
res.num[i]=a.num[i]-b.num[i];
if(res.num[i]<0){
res.num[i]+=P;a.num[i+1]-1;
int k=i+1;while(k<150&&a.num[k]<0)a.num[k++]+=P;
}
}return res;
}
int cmp(BigInt a,BigInt b){
for(int i=149;i>=0;i--){
if(a.num[i]>b.num[i])return 1;
if(a.num[i]return -1;
}return 0;
}
bool check(){
if(flag!=-1)return flag;
BigInt a,b,s;
a.push(A);b.push(B);s.push(S);
int t=cmp(minus(b,a),minus(a,s));
if(!t)printf("%s ",S);
if(t>0)return 1;
else return 0;
}
int main(){
init();
gets(A);n=strlen(A);
make_big();make_small();
if(check())printf("%s\n",S);
else printf("%s\n",B);
return 0;
}
【分析】
这题比赛的时候没有写出来真的是太失误了,因为我们做过类似的!
然而因为浪费了大多数时间在D题上,所以最后没有几分钟来调代码了,只是交了最暴力的50分的代码。
我们有这样一个习惯,比如枚举一维,贪心另一维。对于一个点(x,y),我们可以枚举答案是在第i行,那么答案一定产生在最靠近y的地方,那么我们可以对所有的点进行预处理,l[i][j]表示在第i行,列小于等于j的最大值。r[i][j]也就同理。那么答案一定是从l[i][y]和r[i][y]中产生的。
至于要加入(x,y)这个点也只需要将x行的数组更新一遍就好了。
最后的复杂度就是 O((n+m)q) 了。
【代码】
#include
#define mem(a,b) memset(a,b,sizeof(a));
using namespace std;
#define M 505
int l[M][M];
int r[M][M];
int n,m,g;
char a[M];
void Max(int &x,int y){
if(x==-1||xvoid Min(int &x,int y){
if(x==-1||x>y)x=y;
}
int main(){
mem(l,-1);
mem(r,-1);
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",a+1);
for(int j=1;j<=m;j++){
if(a[j]!='x')continue;
for(int k=j;k<=m;k++)Max(l[i][k],j);
for(int k=1;k<=j;k++)Min(r[i][k],j);
}
}
scanf("%d",&g);
while(g--){
int x,y;
scanf("%d %d",&x,&y);
int d=-1;
for(int i=1;i<=n;i++){
int p=l[i][y];
if(p!=-1)Min(d,(x-i)*(x-i)+(y-p)*(y-p));
int q=r[i][y];
if(q!=-1)Min(d,(x-i)*(x-i)+(y-q)*(y-q));
}
printf("%d\n",d);
for(int i=y;i<=m;i++)Max(l[x][i],y);
for(int i=1;i<=y;i++)Min(r[x][i],y);
}
return 0;
}
我之前说的类似的题目是有很多圆和很多点,求有多少个点没有被圆覆盖。那道题是用multiset来维护的。
【代码】
#include
using namespace std;
multiset<int>st[100005];
int n,m,x,y,R,cnt;
int main(){
scanf("%d",&n);
cnt=n;
for(int i=1;i<=n;i++){
scanf("%d %d",&x,&y);
st[y].insert(x);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d %d %d",&x,&y,&R);
int s=max(0,y-R);
int t=min(y+R,10000);
for(int j=s;j<=t;j++){
multiset<int>::iterator it,it1;
int h=y-j;
int d=sqrt(R*R-h*h);
int l=max(x-d,0),r=min(x+d,10000);
it=st[j].lower_bound(l);
while(it!=st[j].end()&&*it<=r){
it1=it;
it1++;
st[j].erase(it);
it=it1;
cnt--;
}
}
}
printf("%d\n",cnt);
return 0;
}
比赛的时候还是看了一下题目的。然后就蒙逼了。
赛后就学习了AC自动机和fail树。
我所理解的AC自动机,就是我们在对一个母串匹配的一个巨大的优化:
我们在通常匹配的时候就是两层for,只要一不满足,母串的i++,子串的j=0,重新进行匹配,这样之前所做的努力就全部清空了。我们所希望的,就是子串之前已经匹配出的串能在母串中找到相同的地方,然后继续匹配。
那么AC自动机和fail树做的就是这个工作了。
然后,对于fail上的一个节点,如果它有一个子孙在自动机上走的时候被遍历到了,那么这个点权值也需要增加。而且一个点被遍历了一次,那么它的所有祖先权值都需要增加,这里可以使用树状数组+dfs序来解决。但是如果一个点有多个子孙都被遍历到了,那么这个点的权值就会出错。
对于一个点,它如果有多个子孙被遍历过,应该只算1次。
观察每两个点,他们的LCA及以上就应当被减去重复的权值,同样使用树状数组+DFS序。
但是如果每两个点都减一遍的话肯定又会多减去很多权值。
再仔细观察之后发现,对于一个节点内所有被遍历的儿子,只需要把每两个相邻的儿子LCA及以上减去权值就好了。
最后询问的时候,我们找到询问串的末尾是那一个节点,询问这个节点及其子树内的权值和即可。
小C说这道题很巧妙,因为在敲完Build_Trie、Build_AC、Build_Fail之后,这道题才刚刚开始。因为这里要用到fail树的性质、dfs序、LCA以及树状数组的更新和查询。
因为对于第一次接触AC自动机和fail树,而且fail树的这个性质特别的玄学,博主真的不能展开讲,所以大家还是在网上搜索吧。估计搜不到,我这里只讲这道题的fail树怎么处理。
【代码】
#include
#define lowbit(x) x&-x
using namespace std;
#define M 2000005
int ls[M],rs[M],dep[M];
int n,q,id,dfn,hav;
int pos[100005];
int pre[M][26];
int fa[25][M];
char str[M];
int two[25];
int fail[M];
int head[M];
int Q[M];
struct node{
int to,nx;
}e[M];
struct Bit{
int num[M];
Bit(){
memset(num,0,sizeof(num));
}
void add(int x,int v){
while(x<=id+1){
num[x]+=v;
x+=lowbit(x);
}
}
int query(int x){
int res=0;
while(x){
res+=num[x];
x-=lowbit(x);
}
return res;
}
}T;
void Add_edge(int p,int f){
fa[0][p]=f;
e[++hav]=(node){p,head[f]},head[f]=hav;
}
void Build_AC(){
int l=0,r=-1;
for(int i=0;i<26;i++){
if(pre[0][i])Q[++r]=pre[0][i];
}
while(l<=r){
int x=Q[l++];
Add_edge(x,fail[x]);
for(int i=0;i<26;i++){
int t=pre[x][i];
if(t){
Q[++r]=t;
fail[t]=pre[fail[x]][i];
}else pre[x][i]=pre[fail[x]][i];
}
}
}
void dfs(int x,int d){//Build_faild
ls[x]=++dfn;
dep[x]=d;
for(int i=head[x];i;i=e[i].nx){
int to=e[i].to;
dfs(to,d+1);
}
rs[x]=dfn;
}
int LCA(int x,int y){
if(dep[y]>dep[x])swap(x,y);
for(int i=0;i<22;i++){
if(two[i]&(dep[x]-dep[y]))x=fa[i][x];
}
if(x==y)return x;
for(int i=21;i>=0;i--){
if(fa[i][x]!=fa[i][y]){
x=fa[i][x];
y=fa[i][y];
}
}
return fa[0][x];
}
bool cmp(int x,int y){
return ls[x]int now[M];
void Run_word(){
scanf("%s",str);
int len=strlen(str);
int sz=0,cur=0;
for(int i=0;iint c=str[i]-'a';
cur=pre[cur][c];
if(cur)now[sz++]=cur;
}
sort(now,now+sz,cmp);
for(int i=0;i1);
if(i)T.add(ls[LCA(now[i],now[i-1])],-1);
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<=22;i++)two[i]=1<for(int i=1;i<=n;i++){
scanf("%s",str);
int cur=0;
int len=strlen(str);//Build_Trie
for(int j=0;jint c=str[j]-'a';
if(!pre[cur][c])pre[cur][c]=++id;
cur=pre[cur][c];
}
pos[i]=cur;
}
Build_AC();
dfs(0,0);
for(int i=1;i<=20;i++){
for(int j=1;j<=id;j++){
fa[i][j]=fa[i-1][fa[i-1][j]];
}
}
scanf("%d",&q);
while(q--){
int f;
scanf("%d",&f);
if(f==1)Run_word();
else{
int x;
scanf("%d",&x);
int Id=pos[x];
printf("%d\n",T.query(rs[Id])-T.query(ls[Id]-1));
}
}
return 0;
}
这场比赛如果策略得当的话,是能A掉4题。
但是又一次地死在了代码量大的模拟题上,我最不擅长的模拟题。
这也许是我的compare大法使用过多的后遗症吧。其实compare大法是既耗时,又耗脑,还没有好处的事情。因为你没有自己调过那些小细节。既然没有真正调出那些小细节,那就说明你没有完全把握住那些小细节。下次比赛中随便再来一个小细节,你没有调出来,那么题目就过不掉,也没有别人的代码给你比较。
我以后再想用compare大法的时候,我应该要想起曾经这些比赛摔的跤,自己扎扎实实地独立地调出代码。
同时,敲代码的时候绝对不能想到什么就敲什么,一些题目的小细节一定要提前做好规划,既能精简思维,又能提高敲代码的速度,做到显示屏上无代码,而心中有代码。
愿自己以后能够快速地A掉万恶的模拟题。