ybt终于算是毕业了,不得不说ybt的变态题目实在太多了,当然没做出来的更变态。不愧是有变态oj

ybt 题目总结&吐槽 合集(上)

  • 第一章 基础算法
    • 二分
      • T1 飞离地球
    • DFS
      • T2 数独游戏
      • T3 虫食算
    • BFS
      • T4 荆轲刺秦王
      • T5 渡过河流
  • 第二章 字符串算法
    • 字符串处理
      • T6 判断溢出
    • KMP
      • T7 字符串题
      • T8 字符匹配
    • 字典树
      • T9 运动积分
    • AC自动机
      • T10 组合攻击
      • T11 字符串计数

第一章 基础算法

二分

T1 飞离地球

历史上第一道给我心态卡到大崩的题。

此题的最大难点并不在于算法本身,在于其背后十分讲究而且是非常致命的细节处理。
首先,不要忘了判负环的时候会半路跳出SPFA,必须确保队列和数组全部清空
考虑到SPFA的扫描范围是全图,虽然我们求的是1-n的最短路,但不保证这个过程中会不会跑到1-n以外的负环,而这种负环是可以被忽略的,为了方便,需要预处理不在1-n的任何路线上的点,并不允许SPFA经过这些点,这样就忽略了可能影响答案的负环。
总之此题细节不算多,但是并不好解释,一旦没有注意到,都是严重的漏洞(这题我光是思考为什么要做那些操作就花了一个多小时,之后成功把我心态卡崩了,严重怀疑自己不会最短路)。另外,这题真的应该出现在最短路,二分轻而易举就能看出来,完全没一点存在感,等到最短路的时候,已经忘了这题的存在了,一道神题当场失去意义…
完整代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define int long long
using namespace std;
struct yjx{int nxt,to,c;
}e[200001];
int cnt,ecnt = -1,dis[1001],vis[1001],head[1001],m,n,k;
bool go[1001],ind[1001],judge[1001];
queue<int> Q;
void save(int x,int y,int w){e[++ecnt].nxt = head[x];e[ecnt].to = y;e[ecnt].c = w;head[x] = ecnt;
}
void incheck(int x) {int i;go[x] = 1;for (i = head[x]; ~i; i = e[i].nxt) {if (!go[e[i].to])incheck(e[i].to);}
}
void check_dfs() {int i;incheck(1);for (i = 1; i <= n; i++) {if (!go[i])ind[i] = 0;}for (i = 1; i <= n; i++) {if (!ind[i])continue;memset(go, 0, sizeof(go));incheck(i);if (!go[n])ind[i] = 0;else++cnt;}
}
int S(int mid){int i,now,temp;while(!Q.empty()) Q.pop();memset(vis,0,sizeof(vis));memset(judge,0,sizeof(judge));for(i = 1;i <= n;i++) dis[i] = 1e9;dis[1] = 0;//judge[1] = 1; Q.push(1);while(!Q.empty()){now = Q.front();Q.pop();judge[now] = 0;for(i = head[now];~i;i = e[i].nxt){temp = e[i].to;if(dis[temp] > dis[now] + e[i].c + mid && ind[temp]){dis[temp] = dis[now] + e[i].c + mid;vis[temp] = max(vis[temp],1 + vis[now]);if(!judge[temp]){Q.push(temp);judge[temp] = 1;}if(vis[temp] >= n) return -1;}}}if(dis[n] >= 0 && dis[n] < 1e9) return dis[n];return -1;
}
signed main(){int i,j,x,y,z,t,l,r,mid,res;scanf("%lld",&t);for(i = 1;i <= t;i++){scanf("%lld %lld",&n,&m);ecnt = -1;memset(ind,1,sizeof(ind));memset(head,-1,sizeof(head));memset(e,0,sizeof(e));memset(go,0,sizeof(go));for(j = 1;j <= m;j++){scanf("%lld %lld %lld",&x,&y,&z);save(x,y,z);}check_dfs();if(!ind[n]){printf("-1\n");continue;}l = -1e6,r = 1e6 + 1;while(l < r){mid = (l + r) >> 1;if(~S(mid)) r = mid;else l = mid + 1;}if(l == 1e6 + 1){printf("-1\n");continue;}printf("%lld\n",S(l));}return 0;
}

DFS

T2 数独游戏

题意:给你一个未填完的数独,求一种填完数独的方案。
(题意简单到不需要放图片)
这题看似很简单,但实现起来很麻烦,毕竟数独81个格子,未填的大约也在60个左右,没有一些合理的优化肯定是过不去。
数独的性质就是同一行、同一列、同一九宫格内,一种数字只能填一次,那么这时候就可以记一下当前这格所在行、列、九宫格的状态(显然要状压),再看这一格能填什么,填不上任何数就不再搜索下去。以及每轮填数的时候优先填选择少的,能少出现一些非法情况,至少我认为这个优化不太容易想到。
代码不算难写,但是也不算好写,出现在例题2就比较离谱。这题放状压显然合理很多……
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
char s[81],ch;
int a[10],b[10],c[10],cnt[1 << 9],G[10][10],num[1 << 9];
bool vis[10][10];
int lowbit(int x){return x & -x;
}
int area(int x,int y){return x / 3 * 3 + y / 3;
}
void modify(int x,int y,int w){a[x] ^= (1 << w);b[y] ^= (1 << w);c[area(x,y)] ^= (1 << w);
}
bool dfs(int tot){if(tot == 81){//已经填完return 1;}int i,j,stat,p,x = 0,y = 0,mn = 1e9;for(i = 0;i < 9;i++){for(j = 0;j < 9;j++){if(~G[i][j]) continue;stat = (a[i] & b[j] & c[area(i,j)]);//取得这一格可以填的数的状态if(!cnt[stat]) return 0;if(mn > cnt[stat]){mn = cnt[stat],x = i,y = j;}}}stat = (a[x] & b[y] & c[area(x,y)]);for(i = stat;i;i -= lowbit(i)){p = num[lowbit(i)];G[x][y] = p;modify(x,y,p);if(dfs(tot + 1)) return 1;modify(x,y,p);//回溯G[x][y] = -1;}return 0;
}
int main(){int i,j,x,y,temp;for(i = 0;i < 9;i++) num[1 << i] = i;for(i = 0;i < (1 << 9);i++){for(j = i;j;j -= lowbit(j)){++cnt[i];//统计一个状态可以填的1的个数,__builtin_popcount()也行}}while(1){temp = 0;scanf("%s",s);if(s[0] == 'e') break;for(i = 0;i < 9;i++) a[i] = b[i] = c[i] = (1 << 9) - 1;//1为可填,0为不可填for(i = 0;i < 9;i++){for(j = 0;j < 9;j++){ch = s[i * 9 + j];if(ch >= '1' && ch <= '9'){G[i][j] = ch - '1';modify(i,j,G[i][j]);++temp;}else G[i][j] = -1;}}dfs(temp);for(i = 0;i < 9;i++){for(j = 0;j < 9;j++){printf("%d",G[i][j] + 1);}}printf("\n");}return 0;
}
/*
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end
*/

T3 虫食算

(洛谷P1092)

一道纯剪枝题,这题能出现在例题3,我已经不奇怪为啥数独能上例题2了。
首先我们肯定要模拟算竖式的过程,剪枝能剪掉的无非是一些不必再搜下去的非法情况。在从低位向高位运算的过程中,只需要用运算的基本法则进行剪枝就足够了,包括等式是否成立、最高位是否出现进位。另外,对于当前的填法,提前看一下未填的列,如果同一列的三个数都填完了,等式不论是否包括进位都不成立,方案一定不成立,直接返回。总之这题的搜索本来在字符串上就不好写,加上剪枝更是非常容易混乱,是否适合锻炼剪枝我不知道,但是显然非常适合锻炼心态。

这俩题花了我半个多下午才做完,不得不说,做深搜和广搜必须得选一个心态平和的时候。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
int n,num[31];
char s[4][31];
bool vis[31];
void dfs(int x,int y,int w){int i,c,c1,c2,c3;if(!x){if(!w){for(i = 1;i < n;i++) printf("%d ",num[i]);printf("%d\n",num[n]);   exit(0);}return;}for(i = x - 1;i >= 1;i--){//预判c1 = num[s[1][i] - 'A' + 1],c2 = num[s[2][i] - 'A' + 1],c3 = num[s[3][i] - 'A' + 1];if(c1 == -1 || c2 == -1 || c3 == -1) continue;if((c1 + c2) % n != c3 && (c1 + c2 + 1) % n != c3) return;}if(num[s[y][x] - 'A' + 1] == -1){//当前这一位置未填for(i = n - 1;i >= 0;i--){if(!vis[i]){if(y != 3){//不是结果,可以试填num[s[y][x] - 'A' + 1] = i;vis[i] = 1;dfs(x,y + 1,w);vis[i] = 0;num[s[y][x] - 'A' + 1] = -1;}else{//验证正确性c = num[s[1][x] - 'A' + 1] + num[s[2][x] - 'A' + 1] + w;if(c % n != i) continue;vis[i] = 1;num[s[3][x] - 'A' + 1] = i;dfs(x - 1,1,c / n);//进入下一位vis[i] = 0;num[s[3][x] - 'A' + 1] = -1;}}}}else{if(y != 3){dfs(x,y + 1,w);}else{c = num[s[1][x] - 'A' + 1] + num[s[2][x] - 'A' + 1] + w;if(c % n != num[s[3][x] - 'A' + 1]) return;dfs(x - 1,1,c / n);}}
}
int main(){int i;scanf("%d",&n);for(i = 1;i <= 3;i++){scanf("%s",s[i] + 1);}memset(num,-1,sizeof(num));dfs(n,1,0);return 0;
} 

BFS

T4 荆轲刺秦王

(洛谷P6474,数据比ybt强不少)

此题堪称整个BFS最恶心人的一道题,我做了大约两个半小时才调过去…

首先需要明确,BFS要保证每一个点只被限制下最优的情况走过一次时间复杂度才是合理的。对于此题,刺杀的速度是第一位的,但是我们并非需要保证每一个点只被最早经过它的那一次更新,因为使用两项技能的次数不同答案显然不同,这时候产生的不同情况并无优劣之分,也就是我们必须把这两项技能分别的使用次数也纳入到vis的两维当中(为了从逻辑上通顺,我觉得先考虑冲刺的情况再考虑正常移动的情况比较好)。这样一来此题相当于同一个人在两种规则下进行BFS。
另外,此题为了处理士兵的观察范围,如果是暴力,加上枚举点需要O(n4),这显然不行。考虑到这个形状是规则的,我们可以用前缀和进行更新,只需要枚举某一行距离这个士兵的距离,就能做到O(n3)预处理。预处理的时候注意不要把士兵所在的那一行算两遍。
此题我还犯了一个比较尴尬的错误,那就是对于一个if,不满足任何一项就会进入else,在使用if…else if…的时候要注意不能漏掉一些两种情况下都不可取的条件,这是个很基础的错误,但是在能横穿屏幕的一堆&&与||当中,实在是非常难发现。
总之此题最大的问题在于麻烦,预处理前缀和一堆雷区,判断关系又是一堆雷区,两段BFS,充分体现了BFS搬砖和细节多两大特点。这玩意儿真的是普及组的题吗

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<utility>
using namespace std;
typedef pair<int,pair<int,pair<int,pair<int,int> > > > pr;
const int N = 352;
int n,m,d,c1,c2,resinv = 1e9,resdash = 1e9,restime = 1e9,delta[N][N],sum[N][N],peo[N][N];
int dx[8] = {0,1,0,-1,1,1,-1,-1};
int dy[8] = {1,0,-1,0,1,-1,1,-1};
bool vis[16][16][N][N];
char s[2];
queue<pr> Q;
void bfs(int sx,int sy,int ex,int ey){int i,j,x,y,time,xa,ya,inv,dash;Q.push(make_pair(0,make_pair(0,make_pair(0,make_pair(sx,sy)))));//码风问题,不建议学习while(!Q.empty()){time = Q.front().first;  inv = Q.front().second.first;dash = Q.front().second.second.first;x = Q.front().second.second.second.first;y = Q.front().second.second.second.second;Q.pop();if(x == ex && y == ey){if(time <= restime && (inv + dash < resinv + resdash || inv + dash == resinv + resdash && inv < resinv)){restime = time,resinv = inv,resdash = dash;}if(time > restime) break;continue;}if(dash < c2){//可以冲刺for(i = 0;i < 4;i++){xa = x + dx[i] * d,ya = y + dy[i] * d;if(xa < 1 || xa > n || ya < 1 || ya > m || peo[xa][ya] || sum[xa][ya] && inv == c1) continue;if(sum[xa][ya] && !vis[inv + 1][dash + 1][xa][ya]){Q.push(make_pair(time + 1,make_pair(inv + 1,make_pair(dash + 1,make_pair(xa,ya)))));vis[inv + 1][dash + 1][xa][ya] = 1;}else if(!sum[xa][ya] && !vis[inv][dash + 1][xa][ya]){//注意,上面判掉了需要而不能隐身,在不用隐身的时候也别忘了不能在观察范围内的限制,非常容易忘Q.push(make_pair(time + 1,make_pair(inv,make_pair(dash + 1,make_pair(xa,ya)))));vis[inv][dash + 1][xa][ya] = 1;}}}for(i = 0;i < 8;i++){//如法炮制xa = x + dx[i],ya = y + dy[i];if(xa < 1 || xa > n || ya < 1 || ya > m || peo[xa][ya] || sum[xa][ya] && inv == c1) continue;if(sum[xa][ya] && !vis[inv + 1][dash][xa][ya]){Q.push(make_pair(time + 1,make_pair(inv + 1,make_pair(dash,make_pair(xa,ya)))));vis[inv + 1][dash][xa][ya] = 1;}else if(!sum[xa][ya] && !vis[inv][dash][xa][ya]){Q.push(make_pair(time + 1,make_pair(inv,make_pair(dash,make_pair(xa,ya)))));vis[inv][dash][xa][ya] = 1;}}}if(restime < 1e9){printf("%d %d %d\n",restime,resinv,resdash);}else printf("-1\n");
}
int main(){//freopen("1.in","r",stdin);//freopen("1.out","w",stdout);int i,j,k,l,r,temp,sx,sy,ex,ey;scanf("%d %d %d %d %d",&n,&m,&c1,&c2,&d);for(i = 1;i <= n;i++){for(j = 1;j <= m;j++){scanf(" %s",s);if(s[0] == 'S') sx = i,sy = j;else if(s[0] == 'T') ex = i,ey = j;else if(s[0] == '.') continue;else{sscanf(s,"%d",&temp);--temp;peo[i][j] = 1;for(k = 0;k <= temp;k++){l = max(1,j - temp + k),r = min(m,j + temp - k);if(i - k >= 1) ++delta[i - k][l],--delta[i - k][r + 1];if(i + k <= n && k) ++delta[i + k][l],--delta[i + k][r + 1];//前缀和±1要多加小心     }}}}for(i = 1;i <= n;i++){for(j = 1;j <= m;j++){sum[i][j] = sum[i][j - 1] + delta[i][j];//不要把行和列搞混了}}bfs(sx,sy,ex,ey);return 0;
}

T5 渡过河流

一道不同寻常的BFS,可以说是到目前为止恶心人程度最低同时技术含量最高的。
还是上道题那句话,我们要保证更新每一个点的是在限制下的最优情况。对于此题,我们为了使得更新每一个点的用竹筏个数最优,就不能直接的从距离上跑BFS,毕竟距离远的很可能更好(比如一个有一条从一侧到另一侧一顿绕的一条陆路,理论上在这条路上的答案是0,但按距离就可能反复渡水),要保证在以水陆分层的情况下,每一层都处理完了再处理下一层。
那么这个效果要怎么实现?可以想到的是,用两个队列,一个存本层扩展的点,一个存向下一层扩展的点,在一层遍历结束之后,把向下一层扩展的点加到本层扩展的点当中,两者分别判断点是否到达过/存储过,相当于一个二维的BFS。事实上也正是这样处理的。其实在明确BFS的性质条件下不难想出思路,但是能否写出合理的实现无疑是一个考验(不写成二维的写起来会非常非常恶心,不要问我为什么知道)。
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<utility>
using namespace std;
const int N = 1002;
typedef pair<int,int> pr;
queue<pr> Q,R;
int n,res[N][N],G[N][N],judge[N][N];
int dx[8] = {0,1,0,-1,1,1,-1,-1};
int dy[8] = {1,0,-1,0,1,-1,1,-1};
void bfs(int sx,int sy){int i,x,y,xa,ya;R.push(make_pair(sx,sy));while(!R.empty()){x = R.front().first,y = R.front().second;R.pop();for(i = 0;i < 8;i++){xa = x + dx[i],ya = y + dy[i];if(xa >= 0 && xa <= n + 1 && ya >= 0 && ya <= n + 1 && !judge[xa][ya]){//既未转移也未遍历if(G[x][y] == G[xa][ya]){judge[xa][ya] = 2;res[xa][ya] = res[x][y];R.push(make_pair(xa,ya));}else{judge[xa][ya] = 1;Q.push(make_pair(xa,ya));//存进转移队列}}}}
}
int main(){int i,j,q,x,y,temp = 0,step = 0;scanf("%d %d",&n,&q);for(i = 1;i <= n;i++){for(j = 1;j <= n;j++){scanf("%1d",&G[i][j]);}}Q.push(make_pair(0,0));while(!Q.empty()){//预处理,从边界向中间搜x = Q.front().first,y = Q.front().second;Q.pop();if(judge[x][y] == 2) continue;if(G[x][y] && !temp) ++step;//已经结束了一层陆地的转移,竹筏数+1res[x][y] = step;temp = G[x][y];bfs(x,y); }for(i = 1;i <= q;i++){scanf("%d %d",&x,&y);if(i ^ 1) printf(" ");printf("%d",res[x][y]);}printf("\n");return 0;
}

第二章 字符串算法

字符串处理

T6 判断溢出


此题实属毒瘤题。
很显然,我们是来判断溢出的,所以我们不能让自己的程序溢出(
A*B=C可能会溢出,然而我们可以稍加转化,把这个问题变成B=C/A.更具体的来说,如果上限除以当前这个数比以前累乘的结果小,说明乘这个数就会溢出。
这题最恶心人的一个问题在于输入,首先一个热知识,gets除了输入最后一行都会读入换行符,所以为了不影响sscanf的运用,必须用scanf,每一次scanf读入一个非数字字符串的时候组数加一(判断读入结束用EOF就行了),如果读到unsigned,再读入一个字符串,这就解决了上限取哪一个的问题。注意答案如果是never是在读到下一行的时候才输出的,所以读入结束的时候记得判断是否要输出never,老生常谈的问题了。
此题思路上不怎么难(判断溢出表现,比如乘完了比之前还小/为负大概也可以,但是确实有一点阴间,不太靠谱),但是写代码属实烦人,而且这题的样例有问题,一组数据直接变成一行,我一开始样例用gets慢慢拆,本来就不好拆,发现输入格式不对之后心态当场大崩,因为这时候几乎就没法用gets拆了。这题上这个合集很大程度上要怪这个坑人的样例。
代码如下:(我甚至认为此题都没有打注释解释的必要)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
using namespace std;
unsigned long long st[8] = {127,255,32767,65535,2147483647,4294967295ull,9223372036854775807ull,18446744073709551615ull};
char s[20001],s1[20001],s2[20001],s3[20001];
int cmp(char p[],char q[]){int i,x,y;x = strlen(p),y = strlen(q);if(x < y) return -1;if(x > y) return 1;for(i = 0;i < x;i++){if(p[i] > q[i]) return 1;else if(p[i] < q[i]) return -1;}return 0;
}
int main(){int i,j,n,x,y,t,cnt,p,ed,go,res = -1;unsigned long long limit,temp1 = 0,temp2 = 0;scanf("%d",&t);while(scanf("%s",s) != EOF){if(s[0] == 'i'){--t;if(s[3] == '8') limit = st[0];if(s[3] == '1') limit = st[2];if(s[3] == '3') limit = st[4];if(s[3] == '6') limit = st[6];if(res > 0){printf("%d\n",res);} if(res == 0) printf("never\n");cnt = 0,temp1 = 0,temp2 = 0,res = 0;}else if(s[0] == 'u'){scanf("%s",s); if(s[3] == '8') limit = st[1];if(s[3] == '1') limit = st[3];if(s[3] == '3') limit = st[5];if(s[3] == '6') limit = st[7]; if(res > 0){printf("%d\n",res);} if(res == 0) printf("never\n");cnt = 0,temp1 = 0,temp2 = 0,res = 0;}else{++cnt;sprintf(s2,"%llu",limit);if(res) continue;if(cmp(s2,s) < 0){res = cnt;continue;}sscanf(s,"%llu",&temp2);if(cnt == 1) temp1 = temp2;else{if(limit / temp2 < temp1){go = 2;res = cnt;continue;}else temp1 *= temp2;}}}if(res > 0){printf("%d\n",res);} if(res == 0) printf("never\n");return 0;
}

字符串算法(尤其是KMP及一些衍生的算法、匹配类)即使被经常吐槽套路化,但是思维难度在我心目中还是可以达到T0级别。

KMP

T7 字符串题


第一道代码难度低而思维难度高的题。
首先,根据观察定义,可以发现nxt[i]=i-pre[i],这是一个常用结论(我做的时候确实忘了,单是发现这个就花了我起码15分钟),因此我们就是要对于给定的nxt[i],求一个字典序最小的字符串。
进行一波分类讨论,当nxt[i]不为0的时候,显然s[i]=s[nxt[i]];否则,说明i并不能匹配出相同的前后缀,由于这是一个逆操作,我们先考虑一下怎么求nxt[i]:一直使j跳nxt,直到s[i+1]=s[j+1]或者j=0,而此时就对应j=0的情况,说明此前跳nxt的时候始终没有匹配到,这样一来从1一直到(i-1)经过的所有nxt的字符都不能填在这一位,我们排除掉这些字符,从剩下的选一个字典序最小的,就能保证整体字典序最小了。这过程中千万不要忘了匹配的是s[i+1]和s[j+1],所以跳回的时候是从nxt[i-1]开始跳的。
这题重在考察对KMP算法的熟悉程度,考虑到KMP本来原理就比较复杂,这题是一个逆运算,必须清晰地理解才可能做出来,所以难度不低,可以说是一道经典题。
代码如下:(真的简单到不需要解释)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 1;
int nxt[N],vis[27];
char s[N];
int main(){int i,j,n,x;scanf("%d",&n);nxt[0] = -1; for(i = 1;i <= n;i++){scanf("%d",&x);nxt[i] = i - x;} for(i = 1;i <= n;i++){if(nxt[i]){s[i] = s[nxt[i]];}else{j = nxt[i - 1];while(j >= 0){vis[s[j + 1] - 'a' + 1] = i;j = nxt[j];}for(j = 1;j <= 26;j++){if(vis[j] != i){s[i] = 'a' + j - 1;break;}}}}printf("%s\n",s + 1);return 0;
}

T8 字符匹配


这个题就比较麻烦一些了。
考虑到KMP本质上还是一位一位匹配的,因此要考虑怎么在匹配了一部分之后匹配下一个数字。确定一个数字是否能匹配进去,其实就是考虑等于它的、大小最接近的大于和小于它的数。如果和这几个数对应的位置关系可以匹配,那么是可以匹配的。所以一开始要先解决这个问题。
处理等于它的数很容易想到用栈解决,而处理它的前驱和后继可以加上链表,nxt[i]和pre[i]分别表示在1-i中a[i]的前驱和后继,这一来只需要倒序枚举i,每一个处理完之后把i从栈中删除,如果a[i]完全没有了,就直接把a[i]从链表里删掉。
这部分的代码如下:

for (i = 1; i <= k; i++) {if (stak[b[i]].empty())d1[i] = 0;elsed1[i] = i - stak[b[i]].top();stak[b[i]].push(i);}lst = 0;for (i = 1; i <= s; i++) {if (!stak[i].empty()) {pre[i] = lst;nxt[i] = 0;if (lst)nxt[lst] = i;lst = i;}}for (i = k; i >= 1; i--) {if (pre[b[i]])d2[i] = i - stak[pre[b[i]]].top();elsed2[i] = 0;if (nxt[b[i]])d3[i] = i - stak[nxt[b[i]]].top();elsed3[i] = 0;stak[b[i]].pop();if (stak[b[i]].empty()) {if (pre[b[i]])nxt[pre[b[i]]] = nxt[b[i]];if (nxt[b[i]])pre[nxt[b[i]]] = pre[b[i]];}}

在处理完这部分之后,就可以正常的进行KMP的匹配了,匹配的条件就是同时满足某个a[i]的这三种数不存在或者对应关系正确。总之在KMP用链表+栈预处理还是头一回见,不是很容易想到怎么实现,写起来也是比较麻烦的一道题(至少这题直接把我做崩了)。

字典树

T9 运动积分

一个难点完全不在于字典树的题。
排名为x的人获得的分数为x2,而这个x2等价于排在x的前面的人的两两自由组合(包括自己与自己组合)的个数。
对于x y两个人,他们在某一天的排名只与他们不同的最高位有关,因此可以把这些人分成m个组,记为f,每一个组内部的人在这2i~2i+1-1一天的得分相同,用字典树求f,这就减小了问题的规模。
对于每一个人x,考虑两个组f[i]和f[j],f[i]排在f[x]前面需要一位填1,f[j]排在f[x]前面又需要不同的一位填1,这一来剩下的位一共有(m-2)个,也就是有2(m-2)天f[i]和f[j]同时排在f[x]前面,加上一开头总结出来的性质,这一来产生的答案就是∑2∗f[i]∗f[j]∗2(m−2).\sum2*f[i]*f[j]*2^{(m-2)}.∑2∗f[i]∗f[j]∗2(m−2).所以其实最终做法就是枚举i和j,统计贡献就行了。
总而言之这个思路自己想基本上是想不到的,而且还不怎么太好解释(最起码题解就给我看的一头雾水),题解都看不懂就非常容易把人心态搞崩,一道实实在在的阴间题。
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
using namespace std;
const int N = 2e5 + 1;
const int mod = 1e9 + 7;
int tot = 1,trie[2][N << 5],a[N];
long long cnt[N],num[N << 5];
void insert(int id){int i,ch,p = 1;for(i = 30;i >= 0;i--){ch = ((a[id] >> i) & 1);if(!trie[ch][p]) trie[ch][p] = ++tot;p = trie[ch][p];++num[p];}
}
void query(int id){int i,ch,p = 1;for(i = 30;i >= 0;i--){ch = ((a[id] >> i) & 1);cnt[i] = num[trie[ch ^ 1][p]];//利用经过点的次数求f[i]p = trie[ch][p];}
}
int main(){int i,j,n,m;long long res,sum,ans = 0;scanf("%d %d",&n,&m);for(i = 1;i <= n;i++){scanf("%d",&a[i]);insert(i);}for(i = 1;i <= n;i++){query(i);res = 0,sum = 0;for(j = 0;j <= 30;j++){res = (res + (cnt[j] * cnt[j] % mod + cnt[j] % mod * sum % mod) % mod) % mod;sum = (sum + cnt[j]) % mod; //利用前缀和优化这个f[i]*f[j]累计的过程}ans ^= res * 2 * (long long)(1 << (m - 2)) % mod;}printf("%lld\n",ans);
} 

AC自动机

T10 组合攻击

(洛谷P3041)
这题充分证明了,dp配合别的什么算法都会瞬间变难。
这道题不同于一般的AC自动机,我们只知道模式串,因此就需要对于一个建好的字典树跑dp来求得分。设dp[i][j]dp[i][j]dp[i][j]表示前i个字符,到字典树的j点得到的最大得分,那么这就变成一个比较类似于树形dp的问题,用父节点加上子节点的价值更新子节点的dp值,毕竟不要忘了字典树也是树,fail树也是树,在AC自动机的dp中适当迁移树形dp会使思路容易很多
另外在统计增加的价值的时候,需要加上它的子串的价值,这可以通过一直跳fail,看fail是否也是一个完整单词来实现。
以及这地方我dp写的比较奇妙,是从0开始走的,所以要多给一步去走。
dp部分代码如下:

int c(int now,int val){while(now) val += num[now],now = fail[now];return val;
}
int solve(){int i,j,k,temp,ret = 0;vis[0][0] = 1;for(i = 0;i <= l;i++){for(j = 0;j <= tot;j++){if(!vis[i][j]) continue;//vis确保一个点最多走一次,类似记搜for(k = 1;k <= 3;k++){temp = trie[j][k];dp[i + 1][temp] = max(dp[i + 1][temp],c(temp,dp[i][j]));vis[i + 1][temp] = 1;}}}for(i = 0;i <= tot;i++){ret = max(ret,dp[l + 1][i]);}return ret;
}

T11 字符串计数

(洛谷P5357)

吐槽一句,我在学AC自动机的时候直接去学了二次加强版(我以为只是数据大小问题),看到题解代码的那一刻,我大为震撼…

如果只是考虑字符串是否存在,我们跑的过程中看一下哪些终点被经过就足够了;现在要统计出现次数,其实也同理,无非是把bool变成int。
但是这两者的一个区别在于,如果只考虑存在与否,那每个点经过一次足够了,但为了统计出现次数一个点要经过不止一次,这时候最差的时间复杂度能达到O(ST)O(ST)O(ST)(因为fail最少只会向上跳一层),无法通过。
为此,我们还是思考怎么使一个点只更新一遍。再次考虑到fail树,我们重复经过同一个点,其实就相当于从fail树的子节点反复地跑到父节点,如果可以直接上传信息到父节点(就像一般的图论那样)而不反复走过父节点就可以了。考虑到总是深度大的跳fail到深度小的,我们需要先处理深度大的,后处理深度小的,这可以用拓扑实现,建边自然是点指向它的fail。
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 2e6 + 1;
struct yjx{int son[27],ed,fail,res;
}trie[N];
int n,tot = 1,vis[N],ans,in[N],num[N];
char s[N];
queue<int> Q;
void insert(int id){int i,ch,m,p = 1;m = strlen(s + 1);for(i = 1;i <= m;i++){ch = s[i] - 'a' + 1;if(!trie[p].son[ch]) trie[p].son[ch] = ++tot;p = trie[p].son[ch];}if(!trie[p].ed) trie[p].ed = id;num[id] = trie[p].ed;
}
void Getfail(){int i,now,temp,p;for(i = 1;i <= 26;i++) trie[0].son[i] = 1;Q.push(1);while(!Q.empty()){now = Q.front();Q.pop();p = trie[now].fail;for(i = 1;i <= 26;i++){temp = trie[now].son[i];if(!temp){trie[now].son[i] = trie[p].son[i];continue;}trie[temp].fail = trie[p].son[i];++in[trie[temp].fail];Q.push(temp);}}
}
void query(){int i,m,p = 1;m = strlen(s + 1);for(i = 1;i <= m;i++){p = trie[p].son[s[i] - 'a' + 1];++trie[p].res;//不跳fail,只需要对经过的位置增加答案}
}
void topo(){int i,now,temp;for(i = 1;i <= tot;i++){if(!in[i]) Q.push(i);}while(!Q.empty()){now = Q.front();Q.pop();temp = trie[now].fail;--in[temp];if(!in[temp]) Q.push(temp);trie[temp].res += trie[now].res;//上传贡献vis[trie[now].ed] = trie[now].res;//统计是通过统计末尾实现,还是要传到末尾}
}
int main(){int i;scanf("%d",&n);for(i = 1;i <= n;i++){scanf("%s",s + 1);insert(i);}Getfail();scanf("%s",s + 1);query();topo();for(i = 1;i <= n;i++) printf("%d\n",vis[num[i]]);return 0;
}

ybt 神(bian)奇(tai)题目总结合集(上)相关推荐

  1. ybt 神(bian)奇(tai)题目总结合集(下)

    ybt 题目总结&吐槽 合集(下) 第五章 动态规划 树形DP T24 权值统计 T25 树的合并 状压DP T26 涂抹果酱 T27 炮兵阵地 T28 最短路径 T29 图的计数 单调队列 ...

  2. ybt 神(bian)奇(tai)题目总结合集(中)

    ybt 题目总结&吐槽 合集(中) 第三章 图论 最短路 T12 汽车加油 强连通分量 T13 软件安装 T14 宫室宝藏 第四章 数据结构 树状数组 T15 区间修改区间查询 RMQ T16 ...

  3. 神乎奇技的播放软体-MPlayer

    神乎奇技的播放软体-MPlayer MPlayer是一款非常好用万能视频播放软件,几乎可以播放所有当前流行的视频格式(*.avi;*.as*;*.mp*;*.dat;*.m*v;*.n*v;*.qt; ...

  4. ptaa乘以b_PTA|团体程序设计天梯赛-练习题目题解锦集(C/C++)(持续更新中……)...

    C++ CPP C++语言开发 PTA|团体程序设计天梯赛-练习题目题解锦集(C/C++)(持续更新中--) PTA|团体程序设计天梯赛-练习题目题解锦集(持续更新中) 实现语言:C/C++:     ...

  5. PTA|团体程序设计天梯赛-练习题目题解锦集(C/C++)(持续更新中……)

    PTA|团体程序设计天梯赛-练习题目题解锦集(持续更新中) 实现语言:C/C++:      欢迎各位看官交流讨论.指导题解错误:或者分享更快的方法!! 题目链接:https://pintia.cn/ ...

  6. 英语与计算机工作总结,2017年上学期英语教师个人工作总结与2017年上学期计算机教学工作总结合集.doc...

    2017年上学期英语教师个人工作总结与2017年上学期计算机教学工作总结合集 2017年上学期英语教师个人工作总结与2017年上学期计算机教学工作总结合集 2017年上学期英语教师个人工作总结 20* ...

  7. java看不起c语言,为什么我感觉Java比C语言难呢?总觉得逻辑上没有C语言好理解。比如各种继承介面。包之间的关系。...

    为什么我感觉Java比C语言难呢?总觉得逻辑上没有C语言好理解.比如各种继承介面.包之间的关系.以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我 ...

  8. 【PAT】乙级题目解答合集(c++)

    [PAT]乙级题目解答合集(c++) 本篇文章为对PAT乙级1001-1095的题目解答的汇总 1001 害死人不偿命的(3n+1)猜想 (15 分) 1002 写出这个数 (20 分) 1003 我 ...

  9. K8 SEO全能营销及时更新/定时采集让你的网站总能跟上最新资讯

    K8 SEO全能营销及时更新/定时采集让你的网站总能跟上最新资讯 可视化采集规则编写+抓包发布规则,配合P语言,让您精准采集/发布: K8 SEO全能营销 2559发布

最新文章

  1. 不同函数之间的跳转setjmp和longjmp
  2. java自动雨刷系统,安装雨量传感器实现自动大灯/自动雨刷(详细方法)多图!!
  3. Django--ORM操作
  4. WP博客wordpress,robots.txt写法
  5. 第六章 计算机网络与i教案,大学计算机基础教案第6章计算机网络基础与应用.docx...
  6. saltstack中grains简介
  7. 百度google关键字优化的小技巧
  8. 文昌帝君 -- 《文昌帝君阴骘文》
  9. Golang slice高级应用
  10. http响应返回的状态码
  11. android按钮切换颜色,togglebutton
  12. 网页设计中色彩的应用
  13. python用递归法将一个整数n转化为字符串_Python学习之旅 —— 基础篇(五)字符串格式化、递归、生成器迭代器...
  14. 五笔字根表识别码图_五笔字根识别码学习
  15. 大华摄像机调试以及保存视频
  16. html实现多文件打包下载 (mp4文件)
  17. 网站SEO的七个技巧
  18. 《软件测试的艺术》读书笔记(一)
  19. python你已经是个成熟的软件了_你已经是个成熟的系列表情包大全_支付宝微信等软件中招_软吧...
  20. 天云大数据_【案例分享】天云大数据最佳实践系列之——信用评分模型

热门文章

  1. c++ 构造函数+初始化列表
  2. NAS网络存储是什么
  3. 使用Scratch制作项目《弹珠游戏》
  4. 小程序Promise不支持finally解决方案
  5. Hadoop中解除 Name node is in safe mode的方法
  6. 使用scrapy创建一个项目爬取网易云音乐的所有歌手的相关资料
  7. 哈尔滨遭受龙卷风袭击 已造成40人伤亡(图)
  8. PS教程:仙气十足的摄影后期技巧
  9. 如何设计出完美的动画技术架构
  10. 经典论文翻译导读之《A Bloat-Aware Design for Big Data Applications》