计蒜之道 初赛 第三场 题解 Manacher o(n)求最长公共回文串 线段树
腾讯手机地图的定位功能用到了用户手机的多种信号,这当中有的信号的作用范围近。有的信号作用的范围则远一些。有的信号相对于用户在不同的方位强度是不同的,有的则是在不论什么一个方向上信号强度都一致的。
已知用户面向北方拿着自己的手机。在不同方位的各种信号覆盖区域能够被抽象成以用户为圆心的一系列扇形。已知每一个扇形的半径 r,和每一个扇形的两条边相对于正东方向的夹角度数。每一个信号覆盖区域抽象出的扇形都能够通过从第一条边逆时针旋转到第二条边画出。
<img src="http://res.jisuanke.com/img/nanti/429.png" <="" a="" style="box-sizing: border-box; border: 0px; vertical-align: middle; display: block; max-width: 100%; height: auto; margin: auto;">请计算一下。用户手机全部信号覆盖到的区域的总面积。
输入格式
第一行输入一个整数 T (1 ≤ T ≤ 20),表示数据组数。
接下来一共 T 组数据。每组数据第一行输入一个整数 n (1 ≤ n ≤ 104),表示各种信号能够抽象出的不同扇形的总个数。接下来输入 n 行,每行 3 个整数表示扇形半径 r (1 ≤ r ≤ 105)、扇形第一条边与正东方向的夹角 α、扇形第二条边与正东方向的夹角 β (-90 ≤ α ≤ β < 270)。
输出格式
输出 T 行,每行一个非负浮点数。表示用户手机全部信号覆盖到的区域的范围的总面积。
每组输出与标准答案绝对误差在 10-3 内均觉得是正确的。
例子1
输入:
1 4 2 -30 32 3 20 81 3 121 160 2 141 201
输出:
11.030
#define N 205 #define M 100005 #define maxn 205 #define MOD 1000000000000000007 int n,T,rr,aa,bb; ll maxx[400]; int main() { while(S(T)!=EOF) { while(T--){ S(n); memset(maxx,0,sizeof(maxx)); FI(n){ S(rr);S2(aa,bb);aa+=90;bb+=90; for(int j = aa;j<bb;j++){ maxx[j] = max(maxx[j],(ll)rr); } } double sum = 0; ll sa = maxx[0]; int ss = 0; for(int i =0;i<=360;i++){ if(maxx[i] == sa)continue; sum += (double)sa / 360.0 * (double)sa * acos(-1) * (i - ss); sa = maxx[i],ss = i; } printf("%.3f\n",sum); } } return 0; }
商品推荐走马灯(简单)
有一个新的研究显示,人在看见一系列的图片时。假设它们的排列有一定的轴对称性,则会更为认为赏心悦目。依据这个特性。作为阿里巴巴旗下重要的电子商务交易平台的淘宝,希望了解商品推荐的图片走马灯如今的赏心悦目情况,以便推断是否之后须要做出调整。比如,当价值分别为 1,2,1 的商品图片排列在一起的时候,人们能够看到它的全部非空区间 [1]、[2]、[1]、[1。2]、[2,1]、[1,2,1] 中有四个是轴对称的,所以这组图片的展示价值是全部轴对称的非空区间的全部价值总和 1 + 2 + 1 + (1 + 2 + 1) = 8。
<img src="http://res.jisuanke.com/img/nanti/433.png" <="" a="" style="box-sizing: border-box; border: 0px; vertical-align: middle; display: block; max-width: 100%; height: auto; margin: auto;">对于如今淘宝商品推荐的图片走马灯序列里的图片。我们从左到右将它们自 1 開始依次递增编号。如今我们希望了解,对于每次由两个编号 li, ri 组成的第i次询问,若将 li 到 ri 的这些图片选出进行展示,他们的展示价值是多少。
输入格式
第一行输入两个整数 n (1 ≤ n ≤ 105), m (1 ≤ m ≤ 105),分别代表图片总数和询问次数。
第二行一共 n 个整数 ci (-100 ≤ ci ≤ 100),表示从编号 1 到编号 n 的图片价值。
接下来 m 行。每行两个整数 li, ri (1 ≤ li ≤ ri ≤ n)。表示一组询问 [li, ri]。
输入数据保证询问区间合法,图片价值 ci 满足 -100 ≤ ci ≤ 100。
对于简单版本号,1 ≤ n, m ≤ 300;
对于中等版本号,1 ≤ n ≤ 20000,1 ≤ m ≤ 3000;
对于困难版本号。1 ≤ n, m ≤ 100000。
输出格式
一共输出 m 行。每行输出一组询问相应的展示价值。
例子1
输入:
5 2 1 1 0 1 0 2 4 1 2
输出:
4 4
#define N 505 #define M 100005 #define maxn 205 #define MOD 1000000000000000007 int n,m,l,r; ll sum[N][N],pri[N],all; bool dp[N][N]; int main() { while(S2(n,m)!=EOF) { FI(n) scanf("%lld",&pri[i]); for(int len = 0;len<n;len++){ for(int i =0;i<n && i + len < n;i++){ int j = i + len; if(i == j){ dp[i][j] = true; sum[i][j] = pri[i]; } else if(i + 1 == j){ if(pri[i] == pri[j]){ dp[i][j] = true; sum[i][j] = pri[i] + pri[i+1]; } else { dp[i][j] = false; sum[i][j] = 0; } } else { if(pri[i] == pri[j] && dp[i+1][j-1]){ dp[i][j] = true; sum[i][j] = pri[i] + pri[j] + sum[i+1][j-1]; } else { dp[i][j] = false; sum[i][j] = 0; } } } } while(m--){ S2(l,r);all = 0;l--,r--; for(int i = l;i<=r;i++){ for(int j = i;j<=r;j++){ if(dp[i][j]) { all+= sum[i][j]; } } } printf("%lld\n",all); } } return 0; }
第二中解法,使用Manacher o(n)求最长公共回文串。首先增加#字符形成全部的回文串都是奇串,也就是形成#a#b#这种字符串,p[i]表示以i为中心的最大回文串的长度 Manacher 能够用O(n)的复杂度来求出最长公共回文串。然后应用预处理的方法,sum[i]表示前i项的和,dp[i]前i项sum的和。
详细manacher算法能够看这个博客。
则能够推出公式,
当i为奇数时,t为i位置的最大回文串,则这段串总和为
t为偶数时s(t) = (dp[i+t] - 2 * dp[i-1] + dp[i-t-2])/2;
t为奇数时 s(t-1) + sum[i+t] - sum[i-t-1];也就是把奇数转化成偶数的情况,且加上最外环仅仅算了一次。
当i为偶数时,t为i位置的最大回文串,则这段串总和为
t为奇数时s(t) = (dp[i+t] - 2 * dp[i-1] + dp[i-t-2])/2;
t为偶数时 s(t-1) + sum[i+t] - sum[i-t-1];也就是把偶数长度转化成奇数的情况,且加上最外环仅仅算了一次。
注意,这里的t要求是min(min( r - i,p[i]),i - l);由于这里,要求最长串不能超过l r 的范围。总的复杂度为o(n * m);
int n,m,l,r,p[N + N]; ll sum[N + N],dp[N + N],pri[N],all,tstr[N+N]; void Manacher(ll str[],int len){ int l2 =0,mi; tstr[l2++] = maxn; for(int i =0;i<len;i++){ tstr[l2++] = str[i]; tstr[l2++] = maxn; } p[0] = 0;mi = 0; for(int i = 1;i<l2;i++){ int mi2 = mi + mi - i; if(mi + p[mi] >= i) p[i] = min(mi2 - (mi - p[mi]),p[mi2]); else p[i] = 0; if(p[i] == 0 || mi2 - p[mi2] == mi - p[mi]){ int maxx = p[i]+1; while(i- maxx >= 0 && i + maxx < l2 && tstr[i-maxx] == tstr[i+maxx]){ maxx++; } p[i] = maxx - 1; } if(p[i] + i > p[mi] + mi) mi = i; } sum[0] = (tstr[0] == maxn ? 0:tstr[0]); for(int i = 1;i < l2;i++){ sum[i] = sum[i - 1] + (tstr[i] == maxn ?0:tstr[i]); } dp[0] = sum[0]; for(int i = 1;i < l2;i++){ dp[i] = dp[i - 1] + sum[i]; } } int main() { while(S2(n,m)!=EOF) { FI(n) scanf("%lld",&pri[i]); Manacher(pri,n); while(m--){ S2(l,r);all = 0;l = l * 2 -1;r = r * 2 -1; for(int i = l;i<=r;i++){ int t = min(min( r - i,p[i]),i - l); if(t == 0){ all += sum[i] - sum[i-1]; continue; } if((((i&1) && !(t&1)) || (!(i&1) && (t&1)))){ all += sum[i+t] - sum[i-t-1];t--; } if(t >= 0){ ll st = (dp[i+t] - 2 * dp[i-1] + (i - t - 2 >= 0? dp[i-t-2]:0))/2; all += st; } } printf("%lld\n",all); } } return 0; }
第三种方法用线段树。在第二个方法中,已经转化成了区间和,用一个线段树更新区间值就能够了。
复杂度o(m * log(n));