声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
下方链接为学习笔记目录链接(中转站)
学习笔记目录链接
ACM-ICPC在线模板
H a s h Hash Hash 表又称散列表,一般由 H a s h Hash Hash 函数与链表结构共同实现。
有一种称为开散列的解决方案是,建立一个邻接表结构,以 H a s h Hash Hash 函数的值域作为表头数组,映射后的值相同的原始信息被分到同一类。
例如,统计一个长度为 N N N的随机数序列 A A A中每一个数字分别出现了多少次,设计 H a s h Hash Hash 函数为 H ( x ) = ( x m o d P ) + 1 H(x)=(x\ mod\ P)+1 H(x)=(x mod P)+1 , P P P 为一个比较大的质数,显然,这个 H a s h Hash Hash函数将序列 A A A 分为 P P P 类,对于每一个 A [ i ] A[i] A[i] ,定位到 h e a d [ H ( A [ i ] ) ] head[H(A[i])] head[H(A[i])] 指向的表头,如果链表中不包含 A [ i ] A[i] A[i],插入新节点,如果已经存在,出现次数累加 1 1 1 ,由于是随机数 A [ i ] A[i] A[i]会均匀分散在每一个表头,整体的时间复杂度为 Θ ( N ) \Theta(N) Θ(N)。
设计Hash函数为 H ( a 1 , a 2 , ⋯ , a 6 ) = ( ∑ i = 1 6 a i + Π i = 1 6 a i ) m o d p H(a_1,a_2,\cdots,a_6) = (\sum^{6}_{i=1}a_i + \Pi^{6}_{i=1}a_i)\ mod\ p H(a1,a2,⋯,a6)=(∑i=16ai+Πi=16ai) mod p ,(累加和累乘)其中 p p p 是一个我们自己选择的一个大质数
然后我们依次把每个雪花插入 H a s h Hash Hash表中,在对应的链表中查找是否已经有相同的雪花。
作者的标程
#include
#include
#include
#include
#define ll long long
using namespace std;
const int N = 100006, P = 99991;
int n, a[6], b[6];
struct S {
int s[6];
};
vector<S> snow[N];
int H() {
int s = 0, k = 1;
for (int i = 0; i < 6; i++) {
(s += a[i]) %= P;
k = (ll)k * a[i] % P;
}
return (s + k) % P;
}
bool pd() {
for (int i = 0; i < 6; i++)
for (int j = 0; j < 6; j++) {
bool v = 1;
for (int k = 0; k < 6; k++)
if (a[(i+k)%6] != b[(j+k%6)]) {
v = 0;
break;
}
if (v) return 1;
v = 1;
for (int k = 0; k < 6; k++)
if (a[(i+k)%6] != b[(j-k+6)%6]) {
v = 0;
break;
}
if (v) return 1;
}
return 0;
}
bool insert() {
int h = H();
for (unsigned int i = 0; i < snow[h].size(); i++) {
memcpy(b, snow[h][i].s, sizeof(b));
if (pd()) return 1;
}
S s;
memcpy(s.s, a, sizeof(s.s));
snow[h].push_back(s);
return 0;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < 6; j++) scanf("%d", &a[j]);
if (insert()) {
cout << "Twin snowflakes found." << endl;
return 0;
}
}
cout << "No two snowflakes are alike." << endl;
return 0;
}
判断是否有相同雪花的方式就是直接暴力枚举就好
若只有旋转操作,可以用字符串的最小表示:
字符串长度为n,旋转n次,取字典序最小的那一种,即为字符串的最小表示。
现在有翻转操作,所以我们对原序列求最小表示,再对翻转后的序列求一个最小表示
#include
#include
#include
#include
#include
#include
#include
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
#define over(i,s,t) for(register int i=s;i<=t;++i)
#define lver(i,t,s) for(register int i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
const int N=1e5+7;
const int mod=1e9+7;
const ll INF=1e15+7;
const double EPS=1e-10;
int n;
int snows[N][6],id[N];
void get_min(int *b){//字符串的最小表示
static int a[12];
for(int i=0;i<12;i++)
a[i]=b[i%6];
int i=0,j=1,k;
while(i<6&&j<6){
for(k=0;k<6&&a[i+k]==a[j+k];k++);//每次直接找就完事了
if(k==6)break;//说明全部相等
if(a[i+k]>a[j+k]){//谁大往后跳,小的永远不动,也就是答案
i+=k+1;//i+k是跑到字符相等的位置,要再加1往后移一位
if(i==j)//如果相等就往后退
i++;
}
else {
j+=k+1;//一样
if(i==j)
j++;
}
}
k=min(i,j);
for(int i=0;i<6;++i)
b[i]=a[i+k];
}
bool cmp_array(int a[],int b[]){//比较a和b字典序谁最小
for(int i=0;i<6;++i){
if(a[i]<b[i])
return true;
else if(a[i]>b[i])
return false;
}
return false;//全部相等
}
bool cmp_val(int a,int b){//如果正反一比都是false说明全相等
for(int i=0;i<6;++i){
if(snows[a][i]<snows[b][i])
return true;
else if(snows[a][i]>snows[b][i])
return false;
}
return false;
}
int main()
{
scanf("%d",&n);
int snow[6],rsnow[6];
over(i,0,n-1){
for(int j=0,k=5;j<6;j++,k--){
scanf("%d",&snow[j]);//原序列
rsnow[k]=snow[j];//翻转序列
}
get_min(snow);
get_min(rsnow);
if(cmp_array(snow,rsnow))memcpy(snows[i],snow,sizeof snow);
else memcpy(snows[i],rsnow,sizeof rsnow);
id[i]=i;
}
sort(id,id+n,cmp_val);
for(int i=1;i<n;++i){
if(!cmp_val(id[i],id[i-1])&&!cmp_val(id[i-1],id[i])){//如果相等(这里神)
puts("Twin snowflakes found.");
return 0;
}
}
puts("No two snowflakes are alike.");
return 0;
}
将一个任意长度的字符串映射为一个非负整数,并且其冲突概率几乎为零。
取一个固定值P,将字符串看作P进制数,并为每一个字符分配一个大于0的数值,例如对于小写字母,令 a = 1 , b = 2 , c = 3 , ⋯ , z = 26 a=1,b=2,c=3,⋯,z=26 a=1,b=2,c=3,⋯,z=26,取一个固定值M,求出该 P P P进制数对 M M M的余数,作为该字符串的 H a s h Hash Hash值。
一般来说,我们取 P = 131 P=131 P=131或 13331 13331 13331 (质数) , 此时 H a s h Hash Hash值冲突的概率极低;同时,取 M = 2 64 M=2^{64} M=264 ,用 u n s i g n e d l o n g l o n g unsigned\ long\ long unsigned long long储存 H a s h Hash Hash值,这样的话算术溢出就相当于取模。
除非特殊构造的数据,上述 H a s h Hash Hash算法很难冲突;保险起见,可以多取几组 P P P和 M M M(例如大质数),多进行几组 H a s h Hash Hash运算,结果都相等时才认为字符串相等
对字符串的各种操作,都可以直接对 P P P进制数进行操作反映到 H a s h Hash Hash值上:
已知字符串S的 H a s h Hash Hash值是 H ( S ) H(S) H(S),那么在S后面添加一个字符c之后新的字符串的Hash值为 H ( S + c ) = ( H ( s ) ∗ P + v a l u e [ c ] ) m o d M H(S+c)=(\ H\ (s)∗P\ +\ value[c]\ )\ mod\ M H(S+c)=( H (s)∗P + value[c] ) mod M 。
已知字符串S的 H a s h Hash Hash值为 H ( S ) H(S) H(S) ,字符串 S + T S+T S+T的 H a s h Hash Hash值为 H ( S + T ) H(S + T) H(S+T),那么T的Hash值就是: H ( T ) = ( H ( S + T ) − H ( S ) ∗ P l e n g t h ( T ) ) m o d M 。 H(T)=(H(S+T)\ −\ H(S)\ ∗\ P ^{length(T)} )\ mod\ M。 H(T)=(H(S+T) − H(S) ∗ Plength(T)) mod M。
通过上面的操作,我们就可以在 Θ ( N ) \Theta(N) Θ(N)的时间内与处理所有字符串的 H a s h Hash Hash值,并在 Θ ( 1 ) \Theta(1) Θ(1)的时间内查询任意字串的 H a s h Hash Hash值。
#include
#include
#include
#include
#include
#include
#include
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
#define over(i,s,t) for(register int i=s;i<=t;++i)
#define lver(i,t,s) for(register int i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
const int N=1e6+7;
const int mod=1e9+7;
const ll INF=1e15+7;
const double EPS=1e-10;
const int p=131;//13331
char str[N];
ull h[N],power[N];
ull get(int l,int r){//要用ull
return h[r]-h[l-1]*power[r-l+1];
}
int n,m;
int main()
{
scanf("%s",str+1);
int len=strlen(str+1);
power[0]=1;//p的n次方
over(i,1,len){
h[i]=h[i-1]*p+str[i]-'a'+1;
power[i]=power[i-1]*p;
}
scanf("%d",&m);
while(m--){
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1)==get(l2,r2))
puts("Yes");
else puts("No");
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#define ls (p<<1)
#define rs (p<<1|1)
#define mid (l+r>>1)
#define over(i,s,t) for(register int i=s;i<=t;++i)
#define lver(i,t,s) for(register int i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
const int N=1e5+7;
const int mod=1e9+7;
const ll INF=1e15+7;
const double EPS=1e-10;
const int p=131;//13331
int n,m;
ull a[N];
char str[N];
ull get_hash(char s[]){
ull res=0;
int len=strlen(s);
over(i,0,len-1){
res=res*p+(ull)s[i];
}
return res;
}
int main()
{
scanf("%d",&n);
over(i,1,n){
cin>>str;
a[i]=get_hash(str);
}
int ans=1;
sort(a+1,a+1+n);
over(i,1,n-1){
if(a[i]!=a[i+1])
ans++;
}
printf("%d\n",ans);
return 0;
}
大佬的直接 h a s h hash hash的解法:
(据他说是 O ( n ) O(n) O(n),我看着像 O ( n l o g n ) O(nlogn) O(nlogn))
https://www.acwing.com/solution/AcWing/content/893/
下面是他的代码,我加了一些注释。
#include
#include
#include
#include
#include
#include
#define boost ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fo(v,a,b) for(int v=(a); v<=(b); v++)
#define fr(v,a,b) for(int v=(a); v>=(b); v--)
#define rng(v,a,b) for(int v=(a); v<(b); v++)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template<typename T> T& chmax(T& a, T b) { a = a > b ? a : b; return a;}
template<typename T> T& chmin(T& a, T b) { a = a < b ? a : b; return a;}
const int maxn=1e6+6,P=131;
char s[maxn],a[maxn*2];
ull H1[maxn*2],H2[maxn*2],g[maxn*2];
int main()
{
int T=0;
while(cin >> (s+1) && strcmp(s+1, "END")) {
cout << "Case " << ++T << ": ";
int len = strlen(s+1), tl=0;
a[++tl] = '#';//填上#号整个序列翻倍,这样奇数长度都变成偶数长度
fo(i,1,len) {
a[++tl] = s[i]; a[++tl] = '#';
}
a[tl+1] = '\0'; len=tl;
g[0] = 1;//p的n次方
fo(i,1,len) {//正序
H1[i] = H1[i-1]*P+a[i];//求hash数组
g[i] = g[i-1]*P;
}
fr(i,len,1)//逆序 ,因为是判断是否回文,所以必须再求逆序的,左边用正序,右边用逆序
H2[i] = H2[i+1]*P+a[i];
int ans=0,l;
fo(i,1,len) {//遍历一遍
l=ans;
if(i+l>=len || i-l<1) break;//超了就break
if(H1[i+l]-H1[i-1]*g[l+1] != H2[i-l]-H2[i+1]*g[l+1])
continue;//不相等说明a[i]=='#',已#号为分界线左右长度为分别长度为l的子串相等,长度为l的子串为回文子串
while(a[i+l+1] == a[i-l-1] && i+l+1<=len && i-l-1>0) l++;//相等l++,(一定是回文)
chmax(ans,l);//因为a数组被#扩充了一倍,所以长度不再是2*l,而是l。取最大的l
}
cout << ans << '\n';
}
return 0;
}
书中给出的做法:
枚举回文串中心的位置 i = [ 1 , N ] i = [1, N] i=[1,N],检查从中心往外左右两侧最长可以扩展到多长:
求出一个最大的数p使得 S [ i − p , i ] = r e v e r s e ( S [ i , i + p ] ) S[i - p, i] = reverse(S[i, i + p]) S[i−p,i]=reverse(S[i,i+p]),那么此回文串长度为 2 ∗ p + 1 2 * p + 1 2∗p+1
求出一个最大的数q使得 S [ i − q , i − 1 ] = = r e v e r s e ( S [ i , i + q − 1 ] ) S[i - q, i - 1] == reverse(S[i, i + q - 1]) S[i−q,i−1]==reverse(S[i,i+q−1]),那么此回文串的长度为 2 ∗ q 2 * q 2∗q。
根据上一道题目,我们已经知道如何通过 Θ ( N ) \Theta(N) Θ(N)的预处理使得可以在 Θ ( 1 ) \Theta(1) Θ(1)的时间内计算原字符串任意字串的 H a s h Hash Hash值;类似的,对原字符串倒着进行一遍处理,就能在 Θ ( 1 ) \Theta(1) Θ(1)的时间内计算原字符串任意字串的逆序的 H a s h Hash Hash值。
对于每一个位置i,可以使用二分的方法在 Θ ( log N ) \Theta(\log{N}) Θ(logN)的时间内找到p、q的位置;于是,本解法的总时间复杂度为 Θ ( N log N ) \Theta(N \log{N}) Θ(NlogN)
5年了,这题还是没人写得出来
下面这三个链接是一个有趣的故事
BZOJ3097:http://www.lydsy.com/JudgeOnline/problem.php?id=3097
BZOJ3098:http://www.lydsy.com/JudgeOnline/problem.php?id=3098
BZOJ3099:http://www.lydsy.com/JudgeOnline/problem.php?id=3099
typedef unsigned long long u64;
typedef pair<int, int> PII;
const int MaxN = 100000;
inline int hash_handle(const char *s, const int &n, const int &l, const int &base,const int &mod1, const int &mod2)
{
int li_n;
static PII li[MaxN];
u64 hash_pow_l;
u64 val;
hash_pow_l = 1;
for (int i = 1; i <= l; i++)
hash_pow_l = (hash_pow_l * base) % mod1;
li_n = 0;
val = 0;
for (int i = 0; i < l; i++)
val = (val * base + s[i] - 'a') % mod1;
li[li_n++].first = val;
for (int i = l; i < n; i++)
{
val = (val * base + s[i] - 'a') % mod1;
val = (val + mod1 - ((s[i - l] - 'a') * hash_pow_l) % mod1) % mod1;
li[li_n++].first = val;
}
hash_pow_l = 1;
for (int i = 1; i <= l; i++)
hash_pow_l = (hash_pow_l * base) % mod2;
li_n = 0;
val = 0;
for (int i = 0; i < l; i++)
val = (val * base + s[i] - 'a') % mod2;
li[li_n++].second = val;
for (int i = l; i < n; i++)
{
val = (val * base + s[i] - 'a') % mod2;
val = (val + mod2 - ((s[i - l] - 'a') * hash_pow_l) % mod2) % mod2;
li[li_n++].second = val;
}
sort(li, li + li_n);
li_n = unique(li, li + li_n) - li;
return li_n;
}
谁敢 h a c k hack hack我?
注:如果您通过本文,有(qi)用(guai)的知识增加了,请您点个赞再离开,如果不嫌弃的话,点个关注再走吧,日更博主每天在线答疑 ! 当然,也非常欢迎您能在讨论区指出此文的不足处,作者会及时对文章加以修正 !如果有任何问题,欢迎评论,非常乐意为您解答!( •̀ ω •́ )✧