1462 Poi2010 Antisymmetry(Bzoj2084 LOJ2452 LUOGU3501 提高+/省选-) 暴力哈希 顺序逆序字串映射关系40分 回文单调性 二分答案100分

总目录

在线测评地址(ybt)

在线测评地址(LOJ)

在线测评地址(LUOGU)

1.暴力哈希 顺序逆序字串映射关系(40分)

ybt

未通过

测试点结果内存时间
测试点1答案正确632KB2MS
测试点2答案正确632KB1MS
测试点3答案正确632KB1MS
测试点4答案正确632KB1MS
测试点5答案正确628KB1MS
测试点6答案正确628KB2MS
测试点7答案正确628KB1MS
测试点8答案正确636KB1MS
测试点9答案正确632KB1MS
测试点10答案正确632KB1MS
测试点11答案正确628KB1MS
测试点12答案正确624KB1MS
测试点13答案正确636KB1MS
测试点14答案正确640KB2MS
测试点15答案正确656KB2MS
测试点16答案正确652KB2MS
测试点17答案正确632KB1MS
测试点18答案正确660KB2MS
测试点19答案正确664KB2MS
测试点20答案正确664KB2MS
测试点21答案正确680KB2MS
测试点22运行超时16692KB995MS
测试点23答案正确844KB18MS
测试点24答案正确828KB14MS

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答案正确636KB2MS
测试点2答案正确640KB2MS
测试点3答案正确640KB1MS
测试点4答案正确640KB2MS
测试点5答案正确640KB1MS
测试点6答案正确644KB1MS
测试点7答案正确636KB1MS
测试点8答案正确632KB2MS
测试点9答案正确640KB1MS
测试点10答案正确640KB1MS
测试点11答案正确640KB1MS
测试点12答案正确644KB2MS
测试点13答案正确648KB1MS
测试点14答案正确652KB1MS
测试点15答案正确660KB1MS
测试点16答案正确660KB2MS
测试点17答案正确636KB1MS
测试点18答案正确664KB2MS
测试点19答案正确668KB1MS
测试点20答案正确664KB2MS
测试点21答案正确684KB2MS
测试点22答案正确16732KB51MS
测试点23答案正确864KB2MS
测试点24答案正确836KB2MS

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;
}

提高+/省选- 需要学习的是,利用回文单调性,采用二分答案来统计数量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值