P1473 [USACO2.3] 零的数列 Zero Sum
第一眼看到这道题想到的是动态规划,可能是最近一直在看这种题,但是我也不会怎么写,看到题解区的dfs我才想到可以搜索,可是要怎么搜呢,这是一个棘手的问题,题目还有进位操作,但是拆分下来也就只有三个操作,加减拆,所以不妨以这三种状态遍历搜索
#include<bits/stdc++.h>
using namespace std;
int n,a[11];
char s[5]=" +-";
bool check(){
int ans=0,t;
for(int i=1;i<=n;i++){
if(a[i]==0)continue;
t=i;
for(int j=i+1;j<=n;j++){
if(a[j]!=0)break;
t=t*10+j;
}
if(a[i]==1)ans+=t;
else ans-=t;
}
if(ans==0)return true;
else return false;
}
void dfs(int k){
if(k==n+1){
if(check()){
printf("1");
for(int i=2;i<=n;i++)printf("%c%d",s[a[i]],i);
printf("\n");
}
}
else{
for(int i=0;i<=2;i++){
a[k]=i;
dfs(k+1);
// a[k]=2;
}
}
}
int main(){
scanf("%d",&n);
a[1]=1;
dfs(2);
return 0;
}
P8673 [蓝桥杯 2018 国 C] 迷宫与陷阱
求最短路径无疑是用bfs,但是这题有个无敌道具,有了无敌道具可以无视陷阱
在结构体内新加一个变量 magic,表示当前还剩几步的无敌效果。在遍历过程中,如果遇到“X”,并且不剩无敌了,就不能走;如果遇到“%”,magic 变为 k。其他情况,并且不为“#”时,把点入队。
这里要加一个剪枝:加一个 int 型的数组vis,入队时需要 vis[x][y] < magic
,即如果当前节点已经被访问过,且之前到达该节点时的无敌状态剩余步数比现在要多,则不需要再次访问该节点。因为如果我们之前已经访问过该节点并且使用了更多的无敌步数,那么这条路径一定不是最优解。初始时要赋值为全 −1−1。
这是题解的处理无敌时间的方法,我觉得可以记忆一下,因为以前从没有用过也不会用,好的方法确实是精妙
#include<bits/stdc++.h>
using namespace std;
struct node{
int x,y,step,magic;
};
int vis[1009][1009];
char maps[1009][1009];
int dx[5]={0,0,1,-1},dy[5]={1,-1,0,0},n,k;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>maps[i][j];
}
}
queue<node>que;
memset(vis,-1,sizeof(vis));
vis[1][1]=0;
que.push((node){1,1,0,0});
while(!que.empty()){
node a=que.front();
que.pop();
if(a.x==n&&a.y==n){
printf("%d",a.step);
return 0;
}
for(int i=0;i<4;i++){
int tx=a.x+dx[i],ty=a.y+dy[i];
if(maps[tx][ty]=='X'&&a.magic==0)continue;
int magic=max(a.magic-1,0);
if(maps[tx][ty]=='%')magic=k;
if(tx>=1&&ty>=1&&tx<=n&&ty<=n&&vis[tx][ty]<magic&&maps[tx][ty]!='#'){
vis[tx][ty]=magic;
que.push((node){tx,ty,a.step+1,magic});
}
}
}
printf("-1");
return 0;
}
P8630 [蓝桥杯 2015 国 B] 密文搜索
我觉得这道题的难点应该是在于对题意的理解,测试的时候看了好几遍也没有看懂,看了题解才知道这题考的是什么东西,用map直接就可以秒了,不过正解是哈希的做法,可惜我还不会,只能用stl容器了。我们看到给出的 n 个小串可能是被打乱了再成为大串的子串的,所以我们可以先把给定的 n 个串每个串都按字符大小重新排序成一个按字典序排好的小串,然后考虑在枚举的时候把大串取出八个字符排序一下,然后再与上面排完序的 n 个小串比较是否相等即可。
对于这个比较是否相同的过程完全可以直接判或者用 map
来实现
#include<bits/stdc++.h>
using namespace std;
int main(){
string str1,str2;
int n;
map<string,int>maps;
cin>>str1;
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>str2;
sort(str2.begin(),str2.end());
maps[str2]++;
}
long long cnt=0;
for(int i=0;i<str1.size()-7;i++){
str2=str1.substr(i,8);
sort(str2.begin(),str2.end());
cnt+=maps[str2];
}
printf("%lld",cnt);
return 0;
}
P2386 放苹果
本题的正解应该是动态规划,因为这种思维性的题一看就很有规律,奈何水平不够加上数据不大,那么好,我们直接开搜,反正N,M<=10,我们用dfs把所有情况遍历就完事了,每个盘能放0到当前剩余苹果值,但是由于不能有重复的情况,比如说0,0,1和0,1,0是一种,这时就要在dfs的参数里面放一个当前值每次从当前值开始搜索,这样保持一个单调递增的数量就能保证不重复
#include<bits/stdc++.h>
using namespace std;
int k,cnt;
void dfs(int pos,int now,int pp){
if(pos==k&&pp==0){
cnt++;
return;
}
if(pos==k)return;
for(int i=now;i<=pp;i++){
dfs(pos+1,i,pp-i);
}
}
int main(){
int n;
scanf("%d",&n);
int m;
for(int i=0;i<n;i++){
cnt=0;
scanf("%d %d",&m,&k);
dfs(0,0,m);
printf("%d\n",cnt);
}
return 0;
}
P3370 【模板】字符串哈希
这道题可以用stl容器的map或set直接过,不过既然是哈希的模板题,还是来了解一下吧
一、首先理解字符串操作的意义:
其实字符串操作的意义是很浅显的,比如百度的推荐搜索,所以字符串操作很重要啊
再来考虑时间:如果百度对于一个人的一次“常搜”推荐需要10s,那么对于全国网友来说,同时上网的人群基数很高,那么如果服务器性能不好的话,就会产生错误。
所以啊,打造高效的字符串算法是很有必要滴!
二、言归正传,浅析字符串哈希
哈希其实是所有字符串操作中,笔者认为最简单的操作了(except输入输出qwq)。哈希的过程,其实可以看作对一个串的单向加密过程,并且需要保证所加的密不能高概率重复(就像不能让隔壁老王轻易地用它家的钥匙打开你家门一样qwq),通过这种方式来替代一些很费时间的操作。
比如,最常见的,当然就是通过哈希数组来判断几个串是否相同(洛谷P3370)。此处的操作呢,很简单,就是对于每个串,我们通过一个固定的转换方式,将相同的串使其的“密”一定相同,不同的串 尽量 不同。
此处有人指出:那难道不能先比对字符串长度,然后比对ASCLL码之和吗?事实上显然是不行的(比如ab和ba,并不是同一个串,但是如是做却会让其认为是qwq)。这种情况就叫做hash冲突,并且在如此的单向加密哈希中,hash冲突的情况在所难免。
而我们此处介绍的,即是最常见的一种哈希:进制哈希。进制哈希的核心便是给出一个固定进制base,将一个串的每一个元素看做一个进制位上的数字,所以这个串就可以看做一个base进制的数,那么这个数就是这个串的哈希值;则我们通过比对每个串的的哈希值,即可判断两个串是否相同
#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
ll base=131;
ll a[10010];
char s[10010];
ll prime=234556;
ll mod=35897654678978910;
ll hashe(char s[]){
int len=strlen(s);
ll ans=0;
for(int i=0;i<len;i++){
ans=(ans*base+(ll)s[i])%mod+prime;
}
return ans;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);
a[i]=hashe(s);
}
sort(a+1,a+1+n);
int cnt=1;
for(int i=1;i<n;i++){
if(a[i]!=a[i+1])cnt++;
}
printf("%d",cnt);
return 0;
}
既然说了有map的做法我肯定也会试一下的,毕竟也算ac代码
#include<bits/stdc++.h>
using namespace std;
int main(){
map<string,int>maps;
int n;
scanf("%d",&n);
string str;
long long cnt=0;
for(int i=0;i<n;i++){
cin>>str;
maps[str]++;
if(maps[str]==1)cnt++;
}
printf("%lld",cnt);
return 0;
}
当然,再好的哈希也会有冲突,此时有两种做法可以解决或者降低哈希冲突的可能性
1、无错哈希
其实原理很简单,就是我们要记录每一个已经诞生的哈希值,然后对于每一个新的哈希值,我们都可以来判断是否和已有的哈希值冲突,如果冲突,那么可以将这个新的哈希值不断加上一个大质数,直到不再冲突(比如somebody’s birthday qwq)。
先贴代码:
for(int i=1;i<=m;i++)//m个串
{
cin>>str;//下一行的check为bool型
while(check[hash(str)])hash[i]+=19260817;
hash[i]+= hash(str) ;
}
正如下图):
但是,这种方法类似桶查找,但是桶查找的弊端2就会很恶心——数据过大,check数组无能为力来支持上亿个空间(弊端1是由于数据具有跳跃性,浪费最后的统计次数,但在此不是特别明显,就当我皮了一下qwq)
2、多重哈希
这其实就是你用不同的两种或多种方式哈希,然后分别比对每一种哈希值是否相同——显然是增加了空间和时间,但也确实增加了其正确性。
下面皮一个哈希自动机;
//哈希自动机,需要二维hash数组
for伪代码排序,用来使哈希值单调(更好判断相/不同的数量)
for(int i=1;i<=m;i++){
check=1;
for(int j=1;j<=qwq;j++)//皮一下
if(hash[j][i]==hash[j][i+1]){check=0;break;}
if(check)ans++;//此为判断相同个数
}