总结一下在做题的时候,发现的很好的一些方法
给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
输入格式
第一行输入正整数 n。
接下来 n 行,每行输入一个整数,第 i+1 行的整数代表 ai。
输出格式
第一行输出最少操作次数。
第二行输出最终能得到多少种结果。
数据范围
0<n≤105,
0≤ai<2147483648
输入样例:
4
1
1
2
2
输出样例:
1
2
看到题中是在一个区间加1或者减1,可以想到考的时候差分
为甚没做出来?
当时没有想到一个性质
如果一个序列数都是一样的,那么它的差分数组,除了第一个有数,其余都为0
知道这个以后,目的就变了,现在的目的就是把一个序列的2~n这些元素全变为0最少操作次数
区间加一减一对应到差分数组就是,选2个数加一减1
因为最后的目的是变0,那么大胆的猜一下贪心,就是选正的减,负的加
然后小心的证明一下,这样是不是最少操作
如果 ,有一次操作选了一个正的加了,那么它变为0的操作就要增加了,所以上面的贪心是正确的
那么现在就是看每次选一对正的减,负的加需要的操作次数
那么也就是统计一下,正数的绝对值和,负数的绝对值和,他们需要的操作次数就是他们中的最大值
当凑不到一对的时候,就可以和n+1这个元素和1这个元素配对,如果和n+1配对就代表是,从这个位置开始,之后的所有数加一或者减1,和1配对就代表改了一下最后这个相等序列的值
所以操作次数就是 max(abs_sum(正),abs_sum(负))
最后序列的可能是 abs(abs_sum(正)-abs_sum(负))+1
这个+1是最后不能配对的,没有和第一个配对的
#include<iostream>
#include<cmath>
using namespace std;
const int N=1e5+10;
int a[N],b[N],n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i]-a[i-1];
}
// for(int i=1;i<=n;i++) cout<<b[i]<<" ";
// cout<<endl;
long long z=0,f=0;
for(int i=2;i<=n;i++) if(b[i]>0) z+=b[i];
else if(b[i]<0) f-=b[i];
cout<<max(f,z)<<endl<<abs(z-f)+1<<endl;
return 0;
}
acwing 109 天才acm
给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:
从集合 S 中取出 M 对数(即 2×M 个数,不能重复使用集合中的数,如果 S 中的整数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 S 的“校验值”。
现在给定一个长度为 N 的数列 A 以及一个整数 T。
我们要把 A 分成若干段,使得每一段的“校验值”都不超过 T。
求最少需要分成几段。
输入格式
第一行输入整数 K,代表有 K 组测试数据。
对于每组测试数据,第一行包含三个整数 N,M,T 。
第二行包含 N 个整数,表示数列A1,A2…AN。
输出格式
对于每组测试数据,输出其答案,每个答案占一行。
数据范围
1≤K≤12,
1≤N,M≤500000,
0≤T≤1018,
0≤Ai≤220
输入样例:
2
5 1 49
8 2 1 7 9
5 1 64
8 2 1 7 9
输出样例:
2
1
一般涉及到最值的问题,就可以先想想二分和dp
可以看出,要想分更少的段,就是每一段包含的数尽可能多,这里运用倍增算法(刚学的)
这个题的话,首先他是具有二分性质的,某一个分界点,之前的校验值>t的话,就可以确定,在左边的一个地方,是要分个界的
这里如果运用二分,最坏二分要 O(nlogn) 还没有加上算校验值,所以不可行
倍增:
既然知道,某一个地方超了,就后面都不要看了,那么,一开始就从起点看,假设起点是st
如果 st+1可以,就看st+2,如果也可以就看st+4…
就这样一直看 st+2^k 如果一直看的话,最多也就是 logn 次
当某一次不行了以后,就缩小把 2^k变成
2^k-1
还是不行就继续变
当又可以的时候,就代表着+2^k次方不可以
2^k-1是可以的,所以就加
结束条件就是+1都不可以的时候,就说明是这一段的最大长度了
这时候1/2会变为0
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e5+10;
ll a[N],b[N],n,m,t;
int check(int l,int r)
{
ll res=0;
int k=0;
for(int i=l;i<=r;i++) b[++k]=a[i];
sort(b+1,b+k+1);
for(int i=1,j=k;i<j&&i<=m;i++,j--)
res+=pow(b[i]-b[j],2);
if(res>t) return 0;
else return 1;
}
int main()
{
std::ios::sync_with_stdio(false);
int k;
cin>>k;
while(k--)
{
cin>>n>>m>>t;
for(int i=1;i<=n;i++) cin>>a[i];
int res=0;
int st=1,end=1;
while(st<=n)
{
int len=1;
while(len)
{
if(end+len<=n&&check(st,end+len))
{
end+=len;
len<<=1;
}
else len>>=1;
}
st=end+1;
res++;
}
cout<<res<<endl;
}
return 0;
}
在与联盟的战斗中屡战屡败后,帝国撤退到了最后一个据点。
依靠其强大的防御系统,帝国击退了联盟的六波猛烈进攻。
经过几天的苦思冥想,联盟将军亚瑟终于注意到帝国防御系统唯一的弱点就是能源供应。
该系统由 N 个核电站供应能源,其中任何一个被摧毁都会使防御系统失效。
将军派出了 N 个特工进入据点之中,打算对能源站展开一次突袭。
不幸的是,由于受到了帝国空军的袭击,他们未能降落在预期位置。
作为一名经验丰富的将军,亚瑟很快意识到他需要重新安排突袭计划。
他现在最想知道的事情就是哪个特工距离其中任意一个发电站的距离最短。
你能帮他算出来这最短的距离是多少吗?
输入格式
输入中包含多组测试用例。
第一行输入整数 T,代表测试用例的数量。
对于每个测试用例,第一行输入整数 N。
接下来 N 行,每行输入两个整数 X 和 Y,代表每个核电站的位置的 X,Y 坐标。
在接下来 N 行,每行输入两个整数 X 和 Y,代表每名特工的位置的 X,Y 坐标。
输出格式
每个测试用例,输出一个最短距离值,结果保留三位小数。
每个输出结果占一行。
数据范围
1≤N≤100000,
0≤X,Y≤1000000000
输入样例:
2
4
0 0
0 1
1 0
1 1
2 2
2 3
3 2
3 3
4
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
输出样例:
1.414
0.000
这题考的是最近点对问题,只是在最近点对的解法上,给每个点分个阵营,同一个阵营的不算距离
最近点对问题的核心是分治
/*
最近点对问题
首先忽略题中点分类的问题
就考虑2*n个点,找其中2对点他们之间的距离最短
首先2个点的距离是很容易算出来的
所以采用分治
首先将所有的点,进行对横坐标进行排序
然后递归出左边的最小值和右边的最小值,然后再取个min
这个结果就是左边2点之间,和右边2点之间的最小值
然后还有个问题就是,处于2边的对点的最小距离
首先设res为已经找到的不处于不同部分的点的最短距离
所以我们要比较的点 其实他们的横坐标x要满足 mid-res<=x&&x<=mid+res
因为不满足这个条件的点,他到中心的距离就大于res了,所以他和对面的点的距离肯定也是大于res的
所以再把这个范围内的所有点取出来
如果直接找的话,还是n^2的,所以要进行优化
假设现在被找最短距离的点是x
那么与x距离小于res的,就是以x为圆心,半径为res的园在另一部分包括的点
然后把园再放宽,变成矩形,也就是说满足条件的点,它的纵左边满足 大于 x的纵坐标-res 小于 x的纵坐标+res
所以对这些点纵坐标进行排序,这里的纵坐标排序就直接可以用归并排序,如果再用sort的话,就会导致时间复杂度更高,从小的开始找,当大于了就不找了
经过证明一个点,他最多有6个满足
所以n^2边成了6n
再加上题目中的条件
上面方法进行查找的时候,是点没有分阵营的,所以现在分了,那么一个阵营中的点的距离就不用求了
就解决了这个问题
*/
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=2e5+20;
const double inf=1e10;
struct node
{
double x,y;
int flag;
}pot[N],temp[N];
bool cmp(node a,node b)
{
return a.x<b.x;
}
double dist(node a,node b)
{
if(a.flag==b.flag) return inf;
double dx=a.x-b.x;
double dy=a.y-b.y;
return sqrt(dx*dx+dy*dy);
}
double dfs(int l,int r)
{
if(l>=r) return inf;
int m=l+r>>1;
double val=pot[m].x;
double res=min(dfs(l,m),dfs(m+1,r));
{
int i=l,j=m+1,cnt=l;
while(i<=m&&j<=r)
if(pot[i].y<=pot[j].y) temp[cnt++]=pot[i++];
else temp[cnt++]=pot[j++];
while(i<=m) temp[cnt++]=pot[i++];
while(j<=r) temp[cnt++]=pot[j++];
for(int k=l;k<=r;k++) pot[k]=temp[k];
}
int k=0;
for(int i=l;i<=r;i++)
if(pot[i].x>=val-res&&pot[i].x<=val+res) temp[k++]=pot[i];
for(int i=0;i<k;i++)
for(int j=i-1;j>=0&&temp[i].y-temp[j].y<=res;j--)
{
res=min(res,dist(temp[i],temp[j]));
}
return res;
}
int main()
{
int t,n;
cin>>t;
while(t--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>pot[i].x>>pot[i].y;
pot[i].flag=0;
}
for(int i=n+1;i<=2*n;i++)
{
cin>>pot[i].x>>pot[i].y;
pot[i].flag=1;
}
sort(pot+1,pot+2*n+1,cmp);
printf("%.3lf\n",dfs(1,2*n));
}
return 0;
}
达达学习数学竞赛的时候受尽了同仁们的鄙视,终于有一天…受尽屈辱的达达黑化成为了黑暗英雄怪兽达达。
就如同中二漫画的情节一样,怪兽达达打算毁掉这个世界。
数学竞赛界的精英 lqr 打算阻止怪兽达达的阴谋,于是她集合了一支由数学竞赛选手组成的超级行动队。
由于队员们个个都智商超群,很快,行动队便来到了怪兽达达的黑暗城堡的下方。
但是,同样强大的怪兽达达在城堡周围布置了一条“不可越过”的坚固防线。
防线由很多防具组成,这些防具分成了 N 组。
我们可以认为防线是一维的,那么每一组防具都分布在防线的某一段上,并且同一组防具是等距离排列的。
也就是说,我们可以用三个整数 S, E 和 D 来描述一组防具,即这一组防具布置在防线的 S,S+D,S+2D,…,S+KD(K∈Z,S+KD≤E,S+(K+1)D>E)位置上。
黑化的怪兽达达设计的防线极其精良。
如果防线的某个位置有偶数个防具,那么这个位置就是毫无破绽的(包括这个位置一个防具也没有的情况,因为 0 也是偶数)。
只有有奇数个防具的位置有破绽,但是整条防线上也最多只有一个位置有奇数个防具。
作为行动队的队长,lqr 要找到防线的破绽以策划下一步的行动。
但是,由于防具的数量太多,她实在是不能看出哪里有破绽。
作为 lqr 可以信任的学弟学妹们,你们要帮助她解决这个问题。
输入格式
输入文件的第一行是一个整数 T,表示有 T 组互相独立的测试数据。
每组数据的第一行是一个整数 N。
之后 N 行,每行三个整数 Si,Ei,Di,代表第 i 组防具的三个参数,数据用空格隔开。
输出格式
对于每组测试数据,如果防线没有破绽,即所有的位置都有偶数个防具,输出一行 “There’s no weakness.”(不包含引号) 。
否则在一行内输出两个空格分隔的整数 P 和 C,表示在位置 P 有 C 个防具。当然 C 应该是一个奇数。
数据范围
防具总数不多于108,
Si≤Ei,
1≤T≤5,
N≤200000,
0≤Si,Ei,Di≤231−1
输入样例:
3
2
1 10 1
2 10 1
2
1 10 1
1 10 1
4
1 10 1
4 4 1
1 5 1
6 10 1
输出样例:
1 1
There’s no weakness.
4 3
这个题,也就是一条数轴上,每次会在某些位置上加1,然后要找到最后,那个位置上的数是奇数
为甚没做出来:
想到了二分,but
没有考虑运用奇偶和的性质,当看到奇数偶数的时候,就应该要想到奇偶性质
所以二分判定,没想出来
/*
首先,对问题进行化简
也就是一根数轴,然后会在中间的一些点上进行加1操作
最后题目保证最多就一个位置的数是奇偶,找到这个位置,和这个位置上的数,如果全数偶数就输出错误
题中牵扯到偶数和奇数
那么可以联想一下会不会用到
奇数+偶数==奇数
偶数+偶数==偶数
如果某一个位置是奇数的话,那么从它开始前n项和就变成奇数了
所以现在可以确定,如果一个位置上前n项和奇数,那么这个位置或者前面的某个位置上肯定有奇数
这样就可以用二分了
还有个问题就是数轴的长度
范围是0~2^31-1
非常大,数组开不了这么大,所以要计算前n项和使用前缀和数组的话,是行不通的
假设现在要求的位置是 x
现在要计算第i件防具在这个0~x中贡献的和
首先要满足 a[i].s<=x
然后从a[i].s每次加a[i].d,并且每次贡献都是1
也就是代表贡献为 加的次数+1
可以加的次数也就是 (min(x,a[i].e)-a[i].s)/a[i].d
这样遍历一遍所有的就可以了
时间复杂度(nlogn)
*/
#include<iostream>
using namespace std;
const int N=200010;
struct node
{
long long s,e,d;
}a[N];
int n;
long long check(long long x)
{
long long res=0;
for(int i=1;i<=n;i++)
{
if(a[i].s<=x)
res+=(min(x,a[i].e)-a[i].s)/a[i].d+1;
}
return res;
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].s>>a[i].e>>a[i].d;
long long l=0,r=(1ll<<31)-1;
while(l<r)
{
long long m=(l+r)>>1;
if(check(m)&1) r=m;
else l=m+1;
}
long long res=check(r)-check(r-1);
if(res&1) cout<<r<<" "<<res<<endl;
else cout<<"There's no weakness."<<endl;
}
return 0;
}
格格兰郡的 N 名士兵随机散落在全郡各地。
格格兰郡中的位置由一对 (x,y) 整数坐标表示。
士兵可以进行移动,每次移动,一名士兵可以向上,向下,向左或向右移动一个单位(因此,他的 x 或 y 坐标也将加 1 或减 1)。
现在希望通过移动士兵,使得所有士兵彼此相邻的处于同一条水平线内,即所有士兵的 y 坐标相同并且 x 坐标相邻。
请你计算满足要求的情况下,所有士兵的总移动次数最少是多少。
需注意,两个或多个士兵不能占据同一个位置。
输入格式
第一行输入整数 N,代表士兵的数量。
接下来的 N 行,每行输入两个整数 x 和 y,分别代表一个士兵所在位置的 x 坐标和 y 坐标,第 i 行即为第 i 个士兵的坐标 (x[i],y[i])。
输出格式
输出一个整数,代表所有士兵的总移动次数的最小值。
数据范围
1≤N≤10000,
−10000≤x[i],y[i]≤10000
输入样例:
5
1 2
2 2
1 3
3 -2
3 3
输出样例:
8
为甚没做出来?
想到了中位数,当是没想到把x轴的问题也转化成中位数。
/*
首先可以看出,x和y是互相独立的,所以可以分开来做
然后要使最后y值都一样的话,也就是所有的y到某一个数距离之和最短,那么就是中位数
然后看x,把x排好序后,要把它们的x变成相邻的,也就是说,排好序后不对应位置已经不变了,就变坐标
最后我们要让,设置一个add,最后
x1 == add+1
x2 == add+2
x3 == add+3
...
然后转换
x1 - 1 == add
x2 - 2 == add
x3 - 3 == add
..
所有给x的数进行处理一下,最后问题也就变成了,让xi变成add,的最小,也就是中位数
*/
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=10010;
int x[N],y[N],n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>x[i]>>y[i];
sort(x+1,x+1+n);
sort(y+1,y+1+n);
for(int i=1;i<=n;i++) x[i]-=i;
sort(x+1,x+n+1);
int res=0;
int t1=y[n/2+1];
int t2=x[n/2+1];
for(int i=1;i<=n;i++)
{
res+=abs(x[i]-t2);
res+=abs(y[i]-t1);
}
cout<<res<<endl;
}
这个题考的是,单调栈
为甚没做出来?
笑死,根本想不到用单调栈
/*
题中,可以看出确定一个高度的话,就可以确定他的左边和右边
那么就可以找一下一个数的左边和右边,然后取最大值
主要是找左边第一个比他大的,和右边第一个比他大的
*/
#include<iostream>
#include<stack>
using namespace std;
const int N=100010;
int a[N],n,l[N],r[N];
int main()
{
while(cin>>n,n)
{
for(int i=1;i<=n;i++) cin>>a[i];
stack<int> stk1,stk2;
for(int i=1;i<=n;i++)
{
while(stk1.size()&&a[stk1.top()]>=a[i]) stk1.pop();
if(stk1.size()) l[i]=stk1.top()+1;
else l[i]=1;
stk1.push(i);
}
for(int i=n;i>=1;i--)
{
while(stk2.size()&&a[stk2.top()]>=a[i]) stk2.pop();
if(stk2.size()) r[i]=stk2.top()-1;
else r[i]=n;
stk2.push(i);
}
// for(int i=1;i<=n;i++) cout<<l[i]<<" "<<r[i]<<endl;
long long res=0;
for(int i=1;i<=n;i++)
res=max(res,a[i]*1ll*(r[i]-l[i]+1));
cout<<res<<endl;
}
return 0;
}
输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 1。
输入格式
第一行输入两个整数 n,m。
第二行输入 n 个数,代表长度为 n 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。
数据范围
1≤n,m≤300000
输入样例:
6 4
1 -3 5 1 -2 3
输出样例:
7
首先看到一段的和,那就前缀和了
为甚没做出来?
想到前缀和,but没想到枚举终点,然后单调队列维护,区间最小值
/*
首先看到求一个区间的和,可以想到就是前缀和,也就是 sum[j]-sum[i-1]
首先规定了长度不能超过m
那么就可以枚举sum[j],然后找他和之间的和最大值,再最这个最大值取个最大值
要想sun[j]-sum[i-1]尽可能大,也就是要sum[i-1]尽可能的小
设枚举的终点为 x
那么区间不超过m的和,也就是 sum[x]-sum[y](x-m<=y<=x-1)
这样问题就变成了,找x-m~x-1这个区间中的最小值,就用单调队列
*/
#include<iostream>
#include<queue>
using namespace std;
const int N=300010;
int a[N],n,m;
int que[N],hh,tt=-1;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
a[i]+=a[i-1];
}
que[++tt]=0;
int res=-1e9;
for(int i=1;i<=n;i++)
{
if(que[hh]<i-m) hh++;
res=max(res,a[i]-a[que[hh]]);
while(hh<=tt&&a[que[tt]]>=a[i]) tt--;
que[++tt]=i;
}
cout<<res<<endl;
return 0;
}
acwing 137 雪花雪花雪花
有 N 片雪花,每片雪花由六个角组成,每个角都有长度。
第 i 片雪花六个角的长度从某个角开始顺时针依次记为 ai,1,ai,2,…,ai,6。
因为雪花的形状是封闭的环形,所以从任何一个角开始顺时针或逆时针往后记录长度,得到的六元组都代表形状相同的雪花。
例如 ai,1,ai,2,…,ai,6 和 ai,2,ai,3,…,ai,6,ai,1 就是形状相同的雪花。
ai,1,ai,2,…,ai,6 和 ai,6,ai,5,…,ai,1 也是形状相同的雪花。
我们称两片雪花形状相同,当且仅当它们各自从某一角开始顺时针或逆时针记录长度,能得到两个相同的六元组。
求这 N 片雪花中是否存在两片形状相同的雪花。
输入格式
第一行输入一个整数 N,代表雪花的数量。
接下来 N 行,每行描述一片雪花。
每行包含 6 个整数,分别代表雪花的六个角的长度(这六个数即为从雪花的随机一个角顺时针或逆时针记录长度得到)。
同行数值之间,用空格隔开。
输出格式
如果不存在两片形状相同的雪花,则输出:
No two snowflakes are alike.
如果存在两片形状相同的雪花,则输出:
Twin snowflakes found.
数据范围
1≤N≤100000,
0≤ai,j<10000000
输入样例:
2
1 2 3 4 5 6
4 3 2 1 6 5
输出样例:
Twin snowflakes found.
一开始接触的时候,被惊艳了,这方法秒啊
这题考的是字符串的最小表示法
/*
字符串最小表示法
求出从字符串中某一个字符开始,循环输出这个字符串,这个结果是所有方案中字典序最小的
假如一个字符串是 dbac
方案有 dbac bacd acdb cdba
其中 acdb 字典序最小,所以它就是这个字符串的最小表示
如果2个字符串的最小表示是一样的,就代表他们是同构的
这个题,考的就是最小表示
问题是有没有一对,他们是同构的,其实也就是最小表示是不是一样的
由于题中,有顺时针和逆时针的概念,所以对于一个序列,求一下,他的原序最小表示,再求一下他的反序最小表示
然后再取最小的一个
最后就只要判断这些最小表示中有没有相等的
求最小表示法的方法,是用2个指针维护2个起点,进行比较
*/
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
int q[N][10],indx[N];//存储每一个序列的最小表示
int n;
bool cmp_array(int a[],int b[])
{
for(int i=1;i<=6;i++)
if(a[i]>b[i]) return false;
else if(a[i]<b[i]) return true;
return false;
}
void get_min(int a[])
{
static int b[20];
for(int i=1;i<=12;i++) b[i]=a[i>6?i-6:i];
int i=1,j=2;
while(i<=6&&j<=6)
{
int k;
for(k=0;k<6&&b[i+k]==b[j+k];k++);//知道b[i]!=b[j]的位置
if(k==6) break;//代表都是相等的
if(b[i+k]>b[j+k]) i+=k+1;
else j+=k+1;
if(i==j) j++;
}
int u=min(i,j);
for(int k=1;k<=6;k++) a[k]=b[u+k-1];
}
bool cmp(int a,int b)
{
return cmp_array(q[a],q[b]);
}
int main()
{
cin>>n;
int z[10],fz[10];//z存储正序最小表示,fz存储反序最小表示
for(int i=1;i<=n;i++)
{
for(int j=1,k=6;j<=6;j++,k--)
{
cin>>z[j];
fz[k]=z[j];
}
get_min(z);
get_min(fz);//获取最小表示
// for(int i=1;i<=6;i++) cout<<z[i]<<" ";
// cout<<endl;
// for(int i=1;i<=6;i++) cout<<fz[i]<<" ";
// cout<<endl;
if(cmp_array(z,fz)) memcpy(q[i],z,sizeof z);
else memcpy(q[i],fz,sizeof fz);//存储小一点的那个的最小表示
indx[i]=i;
}
sort(indx+1,indx+n+1,cmp);
int flag=0;
for(int i=1;i<n;i++)
{
if(!cmp(indx[i],indx[i+1])&&!cmp(indx[i+1],indx[i]))
{
flag=1;
break;
}
}
if(flag) puts("Twin snowflakes found.");
else puts("No two snowflakes are alike.");
return 0;
}
acwing 139 回文子串的最大长度
求一个字符串的最长回文子串
当时一看,这不马拉车模板,不过看标签是哈希,当时想了想,既然是最长的长度,那么肯定是二分长度,那时候有一些情况没有处理好
马拉车
#include<iostream>
#include<string>
using namespace std;
const int N=2e6+10;
int p[N],mx,id;
int manacher(string s)
{
int res=0;
id=mx=0;
for(int i=1;i<s.size();i++)
{
if(i<mx) p[i]=min(p[2*id-i],mx-i);
else p[i]=1;
while(s[i-p[i]]==s[i+p[i]]) p[i]++;
if(i+p[i]>mx) id=i,mx=i+p[i];
res=max(res,p[i]);
}
return res;
}
int main()
{
int cnt=1;
string t;
while(cin>>t)
{
if(t=="END") break;
string s;
s+="$#";
for(int i=0;i<t.size();i++)
{
s+=t[i];
s+='#';
}
s+='$';
int res=manacher(s);
cout<<"Case "<<(cnt++)<<": "<<res-1<<endl;
}
return 0;
}
二分+哈希(发现二分哈希是个好东西)
/*
二分+哈希
回文串分为偶回文,和奇回文
首先看奇回文
那么也就代表可以枚举中点,然后看他左右的字符串是不是相等的
快速判断是不是相等的,就是用哈希
这里还有二分性质,如果取得一个长度,不是回文,就代表长了
判断相等的话,回文正反读是一样的,所以可以先将原字符串反过来,然后进行哈希一遍后面就好判断了
偶回文的话
首先我们将回文字符中每2个字符之间加上一个不会出现的字符,那么这样的话,偶回文就变成奇回文了
*/
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2000010,P=131;
typedef unsigned long long ll;
char a[N],b[N];
ll hl[N],hr[N],p[N];
ll get(ll h[],int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
int cnt=1;
while(scanf("%s",a+1),strcmp(a+1,"END"))
{
int n=strlen(a+1);
n*=2;
for(int i=n;i>=1;i-=2)
{
a[i]=a[i/2];
a[i-1]='#';
}
p[0]=1;
for(int i=1,j=n;i<=n;i++,j--)
{
hl[i]=hl[i-1]*P+a[i]-'a'+1;
hr[i]=hr[i-1]*P+a[j]-'a'+1;
p[i]=p[i-1]*P;
}
int res=0;
for(int i=1;i<=n;i++)
{
int l=0,r=min(i-1,n-i);
while(l<r)
{
int m=l+r+1>>1;
if(get(hl,i-m,i-1)==get(hr,n-(i+m)+1,n-(i+1)+1)) l=m;
else r=m-1;
}
if(a[i-r]=='#') res=max(res,r);
else res=max(res,r+1);
}
printf("Case %d: %d\n",cnt++,res);
}
return 0;
}
这题就是要找循环节,是kmp的应用
/*
next数组的含义
next[i],表示 1~next[i] 是 1~i 这段字符串最大的前缀和后缀相等的长度
也就是 1~next[i] 和 i-next[i]~i 这段字符是相等的
如果他们有相交的部分就代表有循环节
并且那个循环节就是 1~i-next[i]
所以循环节的长度就是 i-next[i]
所以只有当 i%(i-next[i]) == 0 的时候,才代表有循环节
循环节个数为 i/(i-next[i])
注意要特判 next[i] == 0 情况
*/
#include<iostream>
#include<cstring>
using namespace std;
const int N=1000010;
char s[N];
int n,ne[N];
int main()
{
int cnt=1;
while(scanf("%d",&n),n)
{
scanf("%s",s+1);
for(int i=0,j=2;j<=n;j++)
{
while(i&&s[i+1]!=s[j]) i=ne[i];
if(s[i+1]==s[j]) i++;
ne[j]=i;
}
printf("Test case #%d\n",cnt++);
for(int i=2;i<=n;i++)
{
if(ne[i]&&i%(i-ne[i])==0) printf("%d %d\n",i,i/(i-ne[i]));
}
puts("");
}
return 0;
}
acwing 144
这道题的前导题是最大异或对
为甚没做出来?
当时想到了,也就是选2个点,他们的异或路径和最大,but没想到,路径和的计算
/*
可以看出,考的就是trie树的最大异或对那题
那么就要把这个题化成那种题的形式
题中说的路径,其实也就是选2个点,使得2点之间的路径异或和最大
怎么算出2点之间的路径异或和呢
这里有一个技巧
f(x,y)为x到y的路况异或和
f(a,b)==f(a,boot) ^ f(b,boot)
那么就要给这个图弄一个根节点,求出所有点到根节点的异或和
然后就按最大异或对来做
*/
#include<iostream>
#include<cstring>
using namespace std;
const int N=1000010;
int p[32*N][2],indx;
int a[N],boot;
int h[N],ne[N*2],val[N*2],w[N*2],idx=1;
int book[N];
void dfs(int x,int sum)
{
book[x]=1;
a[x]=sum;
for(int i=h[x];i!=-1;i=ne[i])
{
int j=val[i];
if(!book[j])
dfs(j,sum^w[i]);
}
}
void in(int x)
{
int t=0;
for(int i=31;i>=0;i--)
{
if(!p[t][x>>i&1]) p[t][x>>i&1]=idx++;
t=p[t][x>>i&1];
}
}
int f(int x)
{
int t=0;
int res=0;
for(int i=31;i>=0;i--)
{
if(p[t][!(x>>i&1)])
{
t=p[t][!(x>>i&1)];
res+=(1<<i);
}
else t=p[t][x>>i&1];
}
return res;
}
void add(int a,int b,int c)
{
val[indx]=b,ne[indx]=h[a],w[indx]=c,h[a]=indx++;
}
int main()
{
memset(h,-1,sizeof h);
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
boot=0;
dfs(boot,0);
for(int i=1;i<=n;i++) in(a[i]);
int res=0;
for(int i=1;i<=n;i++) res=max(res,f(a[i]));
cout<<res<<endl;
return 0;
}
acwing 156
二维哈希
为甚没做出来?
当时想到哈希做法,当是没想到怎么计算矩阵的哈希值
/*
二维哈希
首先把每一行的以为哈希,哈出来
然后要计算一个二维区域的哈希值的话,就是一行一行的走,由前面算出来的哈希值*p[宽]+这一行的哈希值
当行数超过的时候,就要减去第一行的哈希值
这里ab固定,所以可以先把这个矩阵中存在的a*b的矩阵的哈希值都先预处理出来,然后就只要查表
*/
#include<iostream>
#include<unordered_set>
using namespace std;
typedef unsigned long long ull;
const int N=1010,P=131;
ull f[N][N],p[N*N];
int n,m,a,b;
char s[N];
ull get(ull h[],int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
cin>>n>>m>>a>>b;
p[0]=1;
for(int i=1;i<=n*m;i++) p[i]=p[i-1]*P;
for(int i=1;i<=n;i++)
{
cin>>(s+1);
for(int j=1;j<=m;j++) f[i][j]=f[i][j-1]*P+s[j]-'0';
}
unordered_set<ull> st;
for(int i=b;i<=m;i++)
{
ull val=0;
int l=i-b+1,r=i;
for(int j=1;j<=n;j++)
{
val=val*p[b]+get(f[j],l,r);
if(j>a) val-=get(f[j-a],l,r)*p[a*b];
if(j>=a) st.insert(val);
}
}
int q;
cin>>q;
while(q--)
{
ull val=0;
for(int i=1;i<=a;i++)
{
cin>>(s+1);
for(int j=1;j<=b;j++)
val=val*P+s[j]-'0';
}
if(st.count(val)) cout<<1<<endl;
else cout<<0<<endl;
}
return 0;
}
这题考的 是树的最小表示
也就是一颗树的dfs序列的字典序最小的排列
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
string get(string s,int &u)
{
vector<string> a;
string res="0";
u++;
while(s[u]=='0') a.push_back(get(s,u));
u++;
sort(a.begin(),a.end());
for(auto x:a) res+=x;
res+='1';
return res;
}
int main()
{
int t;
cin>>t;
while(t--)
{
string a,b;
cin>>a>>b;
string x='0'+a+'1';
string y='0'+b+'1';
int ua=0,ub=0;
if(get(x,ua)==get(y,ub)) puts("same");
else puts("different");
}
return 0;
}