1.暴力哈希 顺序逆序字串映射关系(40分)
ybt
未通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 632KB | 2MS |
测试点2 | 答案正确 | 632KB | 1MS |
测试点3 | 答案正确 | 632KB | 1MS |
测试点4 | 答案正确 | 632KB | 1MS |
测试点5 | 答案正确 | 628KB | 1MS |
测试点6 | 答案正确 | 628KB | 2MS |
测试点7 | 答案正确 | 628KB | 1MS |
测试点8 | 答案正确 | 636KB | 1MS |
测试点9 | 答案正确 | 632KB | 1MS |
测试点10 | 答案正确 | 632KB | 1MS |
测试点11 | 答案正确 | 628KB | 1MS |
测试点12 | 答案正确 | 624KB | 1MS |
测试点13 | 答案正确 | 636KB | 1MS |
测试点14 | 答案正确 | 640KB | 2MS |
测试点15 | 答案正确 | 656KB | 2MS |
测试点16 | 答案正确 | 652KB | 2MS |
测试点17 | 答案正确 | 632KB | 1MS |
测试点18 | 答案正确 | 660KB | 2MS |
测试点19 | 答案正确 | 664KB | 2MS |
测试点20 | 答案正确 | 664KB | 2MS |
测试点21 | 答案正确 | 680KB | 2MS |
测试点22 | 运行超时 | 16692KB | 995MS |
测试点23 | 答案正确 | 844KB | 18MS |
测试点24 | 答案正确 | 828KB | 14MS |
LOJ
LUOGU
反对称过程演示:
反对称
原串00001111
取反11110000
对称00001111
反对称
原串010101
取反101010
对称010101
非反对称
原串1001
取反0110
对称0110
首先关心超时问题,查找次数计算:
8
11001011
字串长度是2,共有8-1=7个,如下
11 10 00 01 10 01 11
字串长度是3,共有8-2=6个,如下
110 100 001 010 101 011
字串长度是4,共有8-3=5个,如下
1100 1001 0010 0101 1011
字串长度是5,共有8-4=4个,如下
11001 10010 00101 01011
字串长度是6,共有8-5=3个,如下
110010 100101 001011
字串长度是7,共有8-6=2个,如下
1100101 1001011
字串长度是8,共有8-6=1个,如下
11001011
总个数 (8-1+1)*(8-1)/2=28
极限情况n=500000,查找次数如下
(500000-1+1)*(500000-1)/2=1.25*10^11超时严重,如何解决,这难道是 提高+/省选- 的难度所在?
继续研究样例
原串11001011
取反00110100
对称00101100
关注其中的串001011
原串11(001011)
取反00(110100)
对称(001011)00
关注其中的串1100
原串(1100)1011
取反(0011)0100
对称0010(1100)
关注其中的串0101
原串110(0101)1
取反001(1010)0
对称0(0101)100
关注其中的串10
原串1(10)01011
取反0(01)10100
对称00101(10)0
关注其中的串10
原串1100(10)11
取反0011(01)00
对称00(10)1100
关注其中的串01
原串110(01)011
取反001(10)100
对称001(01)100
关注其中的串01
原串11001(01)1
取反00110(10)0
对称0(01)01100
突然发现满足反对称串的长度必须是偶数,且0与1的数量相等。感觉要用到前缀和了。这难道是 提高+/省选- 的难度所在?
研究原串与反传后对称串关系:
字串长度len
原串子串位置区间[x,y]
取反子串位置区间[len-(y-1),len-(x-1)]
推导如下:
原串11001011
取反00110100
对称00101100
关注其中的串001011
原串11(001011)
取反00(110100)
对称(001011)00
字串长度8
位置12345678
原串11(001011) 子串区间[3,8]
取反00(110100)
对称(001011)00 子串区间[1,6]即[8-(8-1),8-(3-1)]
即有如下关系
字串长度len
原串子串位置区间[x,y]
取反子串位置区间[len-(y-1),len-(x-1)]
查找取反对称子串过程如下:
位置12345678
原串11001011
取反00110100
对称00101100
从对称串出发,以间隔为2,进行字符跳跃。
(00)101100 (00)中0与1的数量不等,跳过。
(0010)1100 (0010)中0与1的数量不等,跳过。
(001011)00 (001011)中0与1的数量相等,找原串对应位置11(001011),发现相等,找到反转对称串。
(00101100) (00101100)中0与1的数量不等,跳过。
0(01)01100 (01)中0与1的数量相等,找原串对应位置11001(01)1,发现相等,找到反转对称串。
0(0101)100 (0101)中0与1的数量相等,找原串对应位置110(0101)1,发现相等,找到反转对称串。
0(010110)0 (010110)中0与1的数量相等,找原串对应位置1(100101)1,发现不相等,跳过。
00(10)1100 (10)中0与1的数量相等,找原串对应位置1100(10)11,发现相等,找到反转对称串。
00(1011)00 (1011)中0与1的数量不等,跳过。
00(101100) (101100)中0与1的数量相等,找原串对应位置(110010)11,发现不相等,跳过。
......
总查找次数8/2+7/2+6/2+5/2+4/2+3/2+2/2+1/2=4+3+3+2+2+1+0=15次
极限情况n=500000,查找次数编程计算如下:
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,i;
long long cnt=0;
scanf("%d",&n);
for(i=n;i>=1;i--)
cnt+=i/2;
printf("cnt=%lld\n",cnt);
return 0;
}
上述代码对应的输入输出数据如下:
500000
cnt=62500000000
即6.25*10^10,超时无疑。
目前,暂时想不出其它办法,先实现了再说。
编码过程中,习得!与~两个运算符号的区别
!0=1,!1=0
~0=-1,~1=-2
编着编着,发现前缀和对目前算法没有本质影响,那就不用前缀和了。
暴力哈希 顺序逆序字串映射关系(40分)如下:
#include <bits/stdc++.h>
#define B 3
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char s[maxn];
int a[maxn],b[maxn];
ULL ah[maxn],bh[maxn],n[maxn],cnt=0;
int main(){
int N,i,j,len,xa,ya,xb,yb;
scanf("%d%s",&N,s+1);
for(i=1;i<=N;i++){
a[i]=s[i]-'0';//顺序字串
b[N-i+1]=!a[i];//取反反转字串,b[N-i+1]=1-a[i];也可
}
n[0]=1;
for(i=1;i<=N;i++)n[i]=n[i-1]*B;
ah[0]=0,bh[0]=0;
for(i=1;i<=N;i++){
ah[i]=ah[i-1]*B+a[i]+1;
bh[i]=bh[i-1]*B+b[i]+1;
}
for(i=1;i<=N;i++)
for(len=2;i-1+len<=N;len+=2){
xa=i,ya=i-1+len;
xb=N-(ya-1),yb=N-(xa-1);
if(ah[ya]-ah[xa-1]*n[ya-(xa-1)]==bh[yb]-bh[xb-1]*n[yb-(xb-1)])cnt++;
}
printf("%llu\n",cnt);
return 0;
}
2.回文单调性 二分答案100分
ybt
通过
测试点 | 结果 | 内存 | 时间 |
测试点1 | 答案正确 | 636KB | 2MS |
测试点2 | 答案正确 | 640KB | 2MS |
测试点3 | 答案正确 | 640KB | 1MS |
测试点4 | 答案正确 | 640KB | 2MS |
测试点5 | 答案正确 | 640KB | 1MS |
测试点6 | 答案正确 | 644KB | 1MS |
测试点7 | 答案正确 | 636KB | 1MS |
测试点8 | 答案正确 | 632KB | 2MS |
测试点9 | 答案正确 | 640KB | 1MS |
测试点10 | 答案正确 | 640KB | 1MS |
测试点11 | 答案正确 | 640KB | 1MS |
测试点12 | 答案正确 | 644KB | 2MS |
测试点13 | 答案正确 | 648KB | 1MS |
测试点14 | 答案正确 | 652KB | 1MS |
测试点15 | 答案正确 | 660KB | 1MS |
测试点16 | 答案正确 | 660KB | 2MS |
测试点17 | 答案正确 | 636KB | 1MS |
测试点18 | 答案正确 | 664KB | 2MS |
测试点19 | 答案正确 | 668KB | 1MS |
测试点20 | 答案正确 | 664KB | 2MS |
测试点21 | 答案正确 | 684KB | 2MS |
测试点22 | 答案正确 | 16732KB | 51MS |
测试点23 | 答案正确 | 864KB | 2MS |
测试点24 | 答案正确 | 836KB | 2MS |
LOJ
LUOGU
如何改进?
以下思路比较奇特,不看他人代码,第一次是想不出来的。还是以样例为例,进行分析。
回文单调性:
原串11001011
取反00110100
对称00101100
关注其中的串001011
原串11(001011)
取反00(110100)
对称(001011)00
以串001011为基准,采用中心对称001 011,将最左最右各抛弃一个字符,得到0101
请注意0101也是满足条件的取反对称串
原串0101
取反1010
对称0101
以串0101为基准,采用中心对称01 01,将最左最右各抛弃一个字符,得到10
请注意10也是满足条件的取反对称串
原串10
取反01
对称10
很明显,找到了最大的取反对称串001011,以其为基准,还可以找到
0101,10这些取反对称串,共计3个,
也即001011的长度是6,以其为基准共可找到6/2=3个取反对称串,
这样就为计算缩短了时间,可以一片一片的找取反对称串的数量。
回文单调性:
回文串是满足单调性的(如果以一个点为中心扩展k格是回文串,那么扩展k-1格也是回文串),所以我们可以枚举中心,然后二分最大的k,然后用hash判断回文。
#include <bits/stdc++.h>
#define B 3
#define ULL unsigned long long
#define maxn 500010
using namespace std;
char s[maxn];
int a[maxn],b[maxn],N;
ULL ah[maxn],bh[maxn],n[maxn],cnt=0;
int judge(int pos,int d){//pos字串中空位置,d距离中空的字符数量
int xa,ya,xb,yb;
xa=pos-d+1,ya=pos+d;
xb=N-(ya-1),yb=N-(xa-1);
if(ah[ya]-ah[xa-1]*n[ya-(xa-1)]==bh[yb]-bh[xb-1]*n[yb-(xb-1)])return 1;//1表示找到取反对称串
else return 0;
}
int bisection(int pos){
int l,r,mid;
l=1,r=min(pos,N-pos);
while(l<=r){//此处写成l<r也折腾了会
mid=(l+r)/2;
if(judge(pos,mid))l=mid+1;//好久没写二分了,此处写成l=mid;耽搁了些时间
else r=mid-1;
}
return l-1;
}
int main(){
int i,j,len,xa,ya,xb,yb;
scanf("%d%s",&N,s+1);
for(i=1;i<=N;i++){
a[i]=s[i]-'0';//顺序字串
b[N-i+1]=!a[i];//取反反转字串,b[N-i+1]=1-a[i];也可
}
n[0]=1;
for(i=1;i<=N;i++)n[i]=n[i-1]*B;
ah[0]=0,bh[0]=0;
for(i=1;i<=N;i++){
ah[i]=ah[i-1]*B+a[i]+1;
bh[i]=bh[i-1]*B+b[i]+1;
}
for(i=1;i<N;i++)cnt+=bisection(i);
printf("%llu\n",cnt);
return 0;
}
提高+/省选- 需要学习的是,利用回文单调性,采用二分答案来统计数量。