T1扫雷游戏(水题)
原题链接:P2670 扫雷游戏
洛谷难度评级:入门难度
——本题思路——
按照扫雷地图的生成原理,先把地雷位置标记在地图上,然后对它周围的八个位置加1(不能是地雷)
——源码——
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char map[105][105];
int dx[8]={-1,-1,-1,0,0,1,1,1},dy[8]={-1,0,1,-1,1,-1,0,1};
int n,m;
void _markmap(int x,int y)
{
int X,Y;
for(int i=0;i<8;++i)
{
X=x+dx[i];
Y=y+dy[i];
if(X>=0&&X<n&&Y>=0&&y<m&&map[X][Y]!='*')
map[X][Y]+=1;
}
}
int main()
{
char k;
while(cin>>n>>m)
{
int i,j;
memset(map,0,sizeof(map));
for(i=0;i<n;++i)
for(j=0;j<m;++j)
{
cin>>k;
if(k=='*')
{
map[i][j]='*';
_markmap(i,j);
}
}
for(i=0;i<n;++i)
{
for(j=0;j<m;++j)
{
if(map[i][j]!='*')
map[i][j]+='0';
cout<<map[i][j];
}
cout<<endl;
}
}
return 0;
}
T2寻找道路(反向BFS)
原题链接:P2296 寻找道路
洛谷难度评级:普及+/提高
——本题思路——
本题的审题很重要,什么叫做“路径上的所有点的出边所指向的点都直接或间接与终点连通”,可以这么理解:某个点所指向的路都能通到终点,那么这个点就是合法的。以样例二为例,节点②有一条出边指向节点⑥,但是节⑥不能联通到终点,所以节点②有一条出边是不合法的,那么节点②也是不合法的。
所以在判定最短路线前,首先要得知有哪些路线是合法的,也就是走哪些点是合法的。
可以这么考虑:合法点一定能连通到终点 推出 不与终点连通的点一定是不合法点并且与不合法点连接的也是不合法点。于是,自然而然地,想到一种从终点往起点搜索的方法(DFS或BFS随意),用于排除不合法点。
重边:重边不影响反向搜索时判断连通性,以下给出几种重边情况。
自环:自环不影响该点与终点的连通性,在读入时可以无视。
解决完路线合法问题,接下来要求最优解
求路线的最优解最基本的就是BFS,而本题的每条边也没有权值,视作长度等效,所以用BFS完全可以。
——源码——
#include<iostream>
#include<vector>
#include<cstring>
#include<cstdio>
using namespace std;
vector<int> wire1[10001];
vector<int> wire2[10001];
int q[10001];
int flag[10001];
int delflag[10001];
int m,n,s,t;
void del()//第一次从终点BFS去除无效点
{
int i,j;
int cq1=1,cq2=1,ad;
while(1)
{
ad=0;
for(i=cq1;i<=cq2;++i)
{
for(j=0;j<wire2[q[i]].size();++j)
{
if(delflag[wire2[q[i]][j]]!=1)
{
ad+=1;
q[cq2+ad]=wire2[q[i]][j];
delflag[wire2[q[i]][j]]=1;
}
}
}
if(ad==0)
break;
cq1=cq2+1;
cq2+=ad;
}
}
int BFS()//第二次BFS找最优解
{
int i,j;
int step=0;
int end=1;
int cq1=1,cq2=1,ad;
while(end)
{
ad=0;
step+=1;
for(i=cq1;i<=cq2;++i)
{
for(j=0;j<wire1[q[i]].size();++j)
{
if(flag[wire1[q[i]][j]]!=1)
{
ad+=1;
q[cq2+ad]=wire1[q[i]][j];
flag[wire1[q[i]][j]]=1;
}
if(wire1[q[i]][j]==t)
{
end=0;
break;
}
}
if(end==0)
break;
}
if(ad==0)
{
step=-1;
break;
}
cq1=cq2+1;
cq2+=ad;
}
return step;
}
int main()
{
while(cin>>n>>m)
{
int i,j;
int sw;
int fr,to,ans=0;
memset(q,0,sizeof(q));
memset(flag,0,sizeof(flag));
memset(delflag,0,sizeof(delflag));
for(i=1;i<=n;++i)
{
wire1[i].clear();
wire2[i].clear();
}
for(i=0;i<m;++i)
{
cin>>fr>>to;
if(fr==to) continue;//自环的处理
wire1[fr].push_back(to);
wire2[to].push_back(fr);//建立反向图
}
cin>>s>>t;
q[1]=t;
delflag[t]=1;
del();
memset(q,0,sizeof(q));
for(i=1;i<=n;++i)
if(delflag[i]==0)
{
flag[i]=1;
for(j=0;j<wire2[i].size();++j)
flag[wire2[i][j]]=1;
}
q[1]=s;
flag[s]=1;
ans=BFS();
cout<<ans<<endl;
}
return 0;
}
T3字串变换(暂时无思路)
原题链接:P1032 字串变换
洛谷难度评级:普及+/提高
T4过河(离散化+dp)
原题链接:P1052 过河
洛谷难度评级:提高+/省选-
——本题思路——
这题很容易就想到用dp解决
符合最优子结构:
桥上某一点k的解是前面k-S到k-T这一段中最少踩石头数加上k点的石头数,而k点又构成k+S到k+T的最优解之一。
状态转移方程:
vis[k]表示k点是否有石头。
本题最根结的问题就产生了,桥的长度达到109,如果存储这些石子的位置?
本题最多有100个石子,可以看出石子间的平均距离大约为107,而青蛙一次最多跳10步。这样的数据就是提醒我们可以压缩石子间的距离。举个例子,假设一个石子位于1,另一个距离最近的石子位于100,而每次可以跳7到10步。实际上青蛙可以跳到56到100步的任意位置,那么其实此时石子位于56的位置也是可以的,不影响最终答案。以下给出证明:
假设跳的范围是s到t(t>s),那么能到的位置为s+ns+m(s+1)+…,n,m=0,1,2,3,4… 等效于s+ns+m(s+1),当步数为s(s+1)后(假设n=s,m=0),此时第s(s+1)+1的位置可以取n=s-1且m=1;同理,一直到(s+1)(s+1)-1是取n=0且m=s。当(s+1)(s+1)时取n=s且m=1,类似于上一个周期(可用数学归纳法详细证明)。
通过在数轴上作出可以到达的位置,也不难发现规律。连续点的个数不断增加,到s(s+1)以后完全连一起。
——源码——
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int stone[101];
int s,t,m;
int vis[15000];
int dp[15000];
int l;
int main()
{
int i,j,ans=0,L=0,dl,de;
stone[0]=0;
cin>>l>>s>>t>>m;
for(i=1;i<=m;++i)
cin>>stone[i];
if(s==t)
{
for(i=1;i<=m;++i)
if(stone[i]%s==0)
++ans;
}
else
{
sort(stone+1,stone+m+1);
de=min(l-stone[m],s*(s+1));
L=0;
for(i=1;i<=m;++i)
{
dl=min(stone[i]-stone[i-1],s*(s+1));
L+=dl;
vis[L]=1;
}
L+=de;
for(i=1;i<=L+t;++i)
{
dp[i]=0xffffff-10;
for(j=s;j<=t;++j)
if(i>=j)
dp[i]=min(dp[i],dp[i-j]+vis[i]);
}
ans=0xffffff;
for(i=L;i<=L+t;++i)
ans=min(ans,dp[i]);
}
printf("%d\n",ans);
return 0;
}
T5反素数(打表)
原题链接:P1463 [POI2002][HAOI2007]反素数
洛谷难度评级:提高+/省选-
——本题思路——
打表!打表!打表!
(发出弱鸡的叫声)
花几个小时用一个很low的代码把所有反素数找出来:
#include<stdio.h>
int main()
{
long long n,i,j,ans,k,max,maxk;
max=0;
maxk=0;
for(k=1;k<=2000000000;++k)
{
ans=1;
for(i=2;i*i<=k;i++)
{
if(k%i==0)
{
if(i!=1&&i!=k)
++ans;
if((i*i)!=k&&(k/i)!=1&&(k/i)!=k)
++ans;
}
}
if(k!=1)
ans+=1;
if(max<ans)
{
max=ans;
maxk=k;
printf("%lld\n",maxk);
}
}
return 0;
}
于是找到如下反素数:
1,2,4,6,12,24,36,48,60,120,180,240,360,720,840,1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,45360,50400,55440,83160,110880,166320,221760,277200,332640,498960,554400,665280,720720,1081080,1441440,2162160,2882880,3603600,4324320,6486480,7207200,8648640,10810800,14414400,17297280,21621600,32432400,36756720,43243200,61261200,73513440,110270160,122522400,147026880,183783600,245044800,294053760,367567200,551350800,698377680,735134400,1102701600,1396755360.
只要把它们存进数组就行了…
#include<iostream>
using namespace std;
int ans[]={1,2,4,6,12,
24,36,48,60,120,
180,240,360,720,840,
1260,1680,2520,5040,7560,
10080,15120,20160,25200,27720,
45360,50400,55440,83160,110880,
166320,221760,277200,332640,498960,
554400,665280,720720,1081080,1441440,
2162160,2882880,3603600,4324320,6486480,
7207200,8648640,10810800,14414400,17297280,
21621600,32432400,36756720,43243200,61261200,
73513440,110270160,122522400,147026880,183783600,
245044800,294053760,367567200,551350800,698377680,
735134400,1102701600,1396755360,2000000001};
int main()
{
long long n;
while(cin>>n)
{
int i=0;
while(1)
{
if(n>=ans[i]&&n<ans[i+1])
{
cout<<ans[i]<<endl;
break;
}
++i;
}
}
return 0;
}