您的位置:首页 > 其它

纪中训练 day3 【NOIP普及组】模拟赛D组 解题报告

2018-01-28 08:06 495 查看

☞第一题 反射☜

大意

一个(n*m)<=1000*1000的矩阵,每个格子有一个类似指向标的东西,遇到它就按照当前的情况向它所对应的方向走。问从矩阵的四条边上的各点出发,做多能走几格。

思路

这题真的好多细节,需要考虑八种情况——4个方向*两种镜子。用dfs能对九个点,最后一个点是因为运行太多次导致程序崩了,所以把dfs改装成while语句来递归就行了。(当然也可以用bfs)

代码

#include<cstdio>
#define r(i,a,b) for(int i=a;i<=b;i++)
using namespace std;int n,m;char c;
char b[1001][1001];
int nans,ans;
int max(int x,int y){return x>y?x:y;}
int dfs(int x,int y,char c) //x,y表示位置,c表示往哪个方向走
{
nans=0;
while (x>=1&&y>=1&&x<=n&&y<=m)
{
if(c=='O'&&b[x][y]=='/') {y++;c='R';nans++;continue;}
if(c=='O'&&b[x][y]=='\\'){y--;c='L';nans++;continue;}//注意,C++的\是\\。
if(c=='D'&&b[x][y]=='/') {y--;c='L';nans++;continue;}
if(c=='D'&&b[x][y]=='\\'){y++;c='R';nans++;continue;}
if(c=='L'&&b[x][y]=='/') {x++;c='D';nans++;continue;}
if(c=='L'&&b[x][y]=='\\'){x--;c='O';nans++;continue;}
if(c=='R'&&b[x][y]=='/') {x--;c='O';nans++;continue;}
if(c=='R'&&b[x][y]=='\\'){x++;c='D';nans++;continue;}
break;
}
return nans;
}
void read(int &f)
{
f=0;char c;bool d=0;
while (c=getchar(),c<'0'||c>'9') if (c=='-') d=1;f=f*10+c-48;
while (c=getchar(),c>='0'&&c<='9') f=f*10+c-48;
if (d) f*=-1;
}
int main()
{
freopen("mirror.in","r",stdin);
freopen("mirror.out","w",stdout);
read(n);read(m);
for (int i=1;i<=n;i++,getchar())
for (int j=1;j<=m;j++)
b[i][j]=getchar();
r(j,1,m)//第一行和最后一行的格子枚举
{
if (j==1)
ans=max(ans,max(max(dfs(1,1,'R'),dfs(n,1,'R')),max(dfs(1,1,'D'),dfs(n,1,'O'))));//因为四个顶点的格子可以从两个方向出发,所以是四种情况
if (j==m)
ans=max(ans,max(max(dfs(1,m,'L'),dfs(n,m,'L')),max(dfs(1,m,'D'),dfs(n,m,'O'))));//同上
if (j>1&&j<m)
ans=max(ans,max(dfs(1,j,'D'),dfs(n,j,'O')));//其它
}
r(i,1,n)
{
if(i==1)
ans=max(ans,max(max(dfs(1,1,'R'),dfs(n,1,'R')),max(dfs(1,1,'D'),dfs(n,1,'O'))));//同理
if(i==n)
ans=max(ans,max(max(dfs(1,m,'L'),dfs(n,m,'L')),max(dfs(1,m,'D'),dfs(n,m,'O'))));//同上
if(i>1&&i<n)
ans=max(ans,max(dfs(i,1,'R'),dfs(i,m,'L')));//这是从左右出发
}
printf("%d",ans);
}


☞第二题 自动匹配☜

大意

有w<=30000串字符串,n<=1000次询问。将这些字符串按字典序排序之后,问第ki个前缀为zi的字符串原来的位置。

思路

暴力模拟20-30分,加优化50分,二分AC。

代码

模拟+map库优化 50分
#include<bits/stdc++.h>
#define r(i,a,b) for(int i=a;i<=b;i++)
using namespace std;int w,n,wh,j,k,l,r,mid;
string s[30001];string fi;
map<string,int>m;//存每个字符串对应的序号
map<char,int>mm;//求以字符c开头的在排序后的串的位置,可以节省循环
bool hy(string a,string b)//判断b是否是a的前缀
{
if (a.find(b,0)==0) return true;else return false;
}
void read(int &f)
{
f=0;char c;bool d=0;
while (c=getchar(),c<'0'||c>'9') if (c=='-') d=1;f=f*10+c-48;
while (c=getchar(),c>='0'&&c<='9') f=f*10+c-48;
if (d) f*=-1;
}
int main()
{
freopen("auto.in","r",stdin);
freopen("auto.out","w",stdout);
read(w);read(n);//输入
for(int i=1;i<=w;i++)
{
cin>>s[i];
m[s[i]]=i;mm[s[i][0]]=30001;
}
sort(s+1,s+1+w);
for(int i=1;i<=w;i++) mm[s[i][0]]=min(mm[s[i][0]],i);
for(int i=1;i<=n;i++)
{
cin>>wh>>fi;k=0;
l=mm[fi[0]];r=w;
mid=(l+r)>>1;
if(!hy(s[mid],fi)) r=mid-1;//减少一半循环(当时做题的时候还不是很熟二分,下一个代码是别人教的二分)
r(j,l,r)
if(hy(s[j],fi))//如果是前缀
{k++;if(k==wh) {printf("%d\n",m[s[j]]);break;}}//累加,到了wh输出
if(k<wh) printf("-1\n");//如果不够输出-
}
}

代码

二分  AC#include<bits/stdc++.h>//万能库
#define r(i,a,b) for(int i=a;i<=b;i++)
using namespace std;int w,n,wh,j,k,l,r,mid;
string s[30001],a[30001];string fi;
map<string,int>m;//存序号
void read(int &f)
{
f=0;char c;bool d=0;
while (c=getchar(),c<'0'||c>'9') if (c=='-') d=1;f=f*10+c-48;
while (c=getchar(),c>='0'&&c<='9') f=f*10+c-48;
if (d) f*=-1;
}
int main()
{
freopen("auto.in","r",stdin);
freopen("auto.out","w",stdout);
read(w);read(n);
for(int i=1;i<=w;i++)
{
cin>>s[i];
m[s[i]]=i;
}
sort(s+1,s+1+w);//排序
for(int i=1;i<=n;i++)
{
cin>>wh>>fi;k=0;
l=1;r=w;
while(l<=r)//二分
{
mid=(l+r)>>1;
if(s[mid]<fi) l=mid+1;else r=mid-1;
}
if(!l||l+wh-1>w||s[l+wh-1].find(fi,0)) puts("-1");/*没找到或不够或越出都输出-1*/else
if(!s[l+wh-1].find(fi,0)) printf("%d\n",m[s[l+wh-1]]);//输出
}
}

☞第三题 道路堵塞☜

大意

一个n<=250个点,m<=25000条边的无向图。给出每个边的权值,问一条边权值乘2后的最短路,与原来最短路的差。

思路

90分思想:两遍最短路,一遍保存路径+最短路,接着存路径中找到最长的一条,将这条边乘2,再来一边最短路。(如floyed+floyed(勉强过90),dij+floyed(比较快),dij+dij(更快),spfa+floyed(速度比dij+dij慢),spfa+dij(次快),spfa+spfa(可以达到0ms90分))时间复杂度依次为O(2N³),O(N²+N³),O(2N²),O(kE+N³),O(kE+N²),O(2kE)
100分思想:之所以没有求出剩下的那个点,是因为上述算法忽略了一种情况——多条最短路!这样的话把一条边乘2得到的不一定就是最优解了,所以只能用n²+1遍spfa(1遍算原值,改变每两个点之间的最短路),时间复杂度O((N²+N)kE)

代码

90分
#include<cstdio>
#include<iostream>
#define INF 2147483648/4
#define N 501
#define M 25001
#define r(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int fa[2*M];int l[251][251],c,d,dij
,minn,k;
int x,y,z,n,m,maxn,ans,sum,now,tot,u,dis[N+10],team[2*N],father
;
struct node{int next,to,w;}a[2*M];
void add(int u,int v,int w)//建图
{
a[++tot].next=fa[u];
a[tot].to=v;
a[tot].w=w;
fa[u]=tot;
}
int spfa()
{
int head=0,tail=1;bool vis[251]={0,1};
r(i,1,n) dis[i]=INF;dis[1]=0;team[1]=1;
while(head!=tail)
{
head=head%N+1;
u=team[head];vis[u]=true;
for(int i=fa[u];i;i=a[i].next)
{
int v=a[i].to,w=a[i].w;
if(dis[u]+w<dis[v])
{
dis[v]=dis[u]+w;father[v]=u;//保存路径
if(!vis[v])
{
tail=tail%N+1;
team[tail]=v;
vis[v]=true;
}
}
}
vis[u]=false;
}
return dis
;
}
int dijkstra()
{
r(i,1,n) dij[i]=l[1][i];
dij[1]=0;bool vis[N*100]={0};vis[1]=1;
r(i,1,n-1)
{
minn=INF;
r(j,1,n){
if(!vis[j]&&dij[j]<minn) {minn=dij[j];k=j;}}
vis[k]=true;
if(k==n||!k) break;
r(j,1,n){
if(dij[k]+l[k][j]<dij[j]) dij[j]=dij[k]+l[k][j];}
}
return dij
-now;
}
int floyed()
{
r(k,1,n)
r(i,1,n)
r(j,1,n)
if(i!=j&&j!=k&&i!=k)
l[i][j]=min(l[i][j],l[i][k]+l[k][j]);
return l[1]
-now;
}
int main()
{
freopen("rblock.in","r",stdin);
freopen("rblock.out","w",stdout);
scanf("%d %d",&n,&m);r(i,0,250) r(j,0,250) l[i][j]=INF;
r(i,1,m)
{
scanf("%d %d %d",&x,&y,&z);if(x==y) continue;
add(x,y,z);
add(y,x,z);
l[x][y]=l[y][x]=z;
}
now=spfa();
for(int i=n;i;i=father[i]){
if(l[i][father[i]]==INF) l[i][father[i]]=0;
if(l[i][father[i]]>sum)
{
sum=l[i][father[i]];c=i;d=father[i];//寻找最长的那条边
}}
l[c][d]=l[d][c]=sum*2;//改变最长的那条边
printf("%d",floyed());//改成dij也可
}

代码

AC#include<cstdio>
#include<iostream>
#include<map>
#define INF 2147483648/4
#define N 501
#define M 25001
#define r(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int l

,c,d;
map<int,int>fa;
int x,y,z,n,m,tot,u,team[2*N],father
;int dis[N+10],ans,now,k,t;
int g

;
int spfa()
{
int head=0,tail=1;bool vis[N*100]={0,1};
r(i,1,n) dis[i]=INF;
dis[1]=0;team[1]=1;
while(head!=tail)
{
head=head%N+1;
u=team[head];vis[u]=true;
r(i,1,g[u][0])//这里不用领接表是因为等下方便找边
{
int v=g[u][i],w=l[u][v];
if(dis[u]+w<dis[v])
{
dis[v]=dis[u]+w;father[v]=u;
if(!vis[v])
{
tail=tail%N+1;
team[tail]=v;
vis[v]=true;
}
}
}
vis[u]=false;
}
return dis
;
}
int main()
{
freopen("rblock.in","r",stdin);
freopen("rblock.out","w",stdout);
scanf("%d %d",&n,&m);r(i,0,N-1) r(j,0,N-1) l[i][j]=INF;
r(i,1,m)
{
scanf("%d %d %d",&x,&y,&z);if(x==y) {l[x][y]=l[y][x]=0;continue;}
g[x][++g[x][0]]=y;
g[y][++g[y][0]]=x;
l[y][x]=l[x][y]=z;
}
now=spfa();
r(i,1,n)
r(j,i+1,n)//枚举所有边,不存在的也枚举,这样其实是时间换空间,因为其实直接一个m也可以,不过要多开一个数组
{
l[i][j]=l[j][i]=l[i][j]<<1;//乘2
t=spfa();//spfa
ans=max(ans,t-now);//存最大差
l[i][j]=l[j][i]=l[i][j]>>1;//除以2
}
printf("%d",ans);//输出
}

☞第四题 密码编码☜

大意

假设一个字符串把左边或右边连续的字符串复制(至少复制一个,也不能全都复制),再放到字符串的左边或右边,形成一个新的字符串。现在给出一个改变过的心字符串,问有几种方法可以变成这种字符串。答案%2014
思路:dfs+map优化。

代码

#include<bits/stdc++.h>
using namespace std;
string s;
map<string,int>m;
int dfs(string s)
{
if(m[s]) return m[s];//map库存储答案
int len=s.size(),l,now=0;
for(int l=0;(l+1)*2<len;l++)
{
int u=len-l-1;
string a=s.substr(0,l+1),b=s.substr(l+1,l+1),c=s.substr(u,l+1);
string d=s.substr(l+1,u),e=s.substr(len-l-l-2,l+1),f=s.substr(0,u);//慢慢理解去吧,再见
if(a==b) now+=dfs(d);
if(a==c) now+=dfs(d);
if(c==e) now+=dfs(f);
if(c==a) now+=dfs(f);
}
if(len>1) now++;now%=2014;//记得%2014
m[s]=now;//存储答案
return now;
}
int main()
{
freopen("scode.in","r",stdin);
freopen("scode.out","w",stdout);
ios::sync_with_stdio(0);
cin>>s;
cout<<dfs(s)-1;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: