三道水题先放到前面..
A题解析:求一个序列的两个子序列组合后的中位数,首先保证第一个序列在第二个序列的前面,此时这两个序列有3种情况,包含 分离 相交,如果包含可将两个的右端点交换,这样就变成了相交了,就只有两种情况了。然后在模拟一下,分情况讨论就可以了。
B题解析:高中数学题,计算阴影部分面积,如图连接3条辅助线,通过余弦定理和二倍角公式计算角度,然后在用海伦公式计算三角形的面积,即可出来了~~
K题解析:计算从1到n的英文的字符个数.. WA的原因有3位数的时候要判断是不是整除100,如果不能整除就要再加3(and)。
其他的题目都是看题解写出来的,对我的用处很大!
D题题意:求死亡序列。。 约瑟夫环的dp。 用dp[i]表示i是第几轮挂掉的,则dp[i]=(i-1)%k?dp[i-(i-1)/i-1]+1:0; 既然知道他是第几轮死的 ,我们在开一个数组保存每轮挂掉的人数,这个很容易实现。我们还想知道这个人是这一轮第几个死掉的,那再开一个数组! a[i]=(i-1)%k?a[i-(i-1)/i-1]:(i-1)/k+1; 大功告成了! (碰到约瑟夫环问题得思考用dp!).
E题题意:有很多条路,开启时需要一定的代价,每天我们都要走其中的一些路,每条路都可以开启关闭1次,初始状态下路都是关闭的。问每天花的代价。
其实我一开始也想到了线段树的 可是我不会写啊。。
仔细思考下,就知道我们得维护每一条道路的开始用的天数和最后用的天数,即最小值最大值。然后遍历天数判断即可。
贴一段线段树维护最小值最大值的代码。
struct Tree
{
int l,r;
int mi,ma;
int tag1,tag2; //tag1 的用处是记录直接作用与该区间的的最小值,tag2则是记录直接作用与该区间的最大值,用来更新它的孩子。
}tr[maxn<<2];
inline void build(int p,int l,int r)
{
tr[p].l=l;
tr[p].r=r;
tr[p].mi=tr[p].tag1=inf;
tr[p].ma=tr[p].tag2=0;
if(l!=r)
{
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
}
inline void up(int p) //根据自己的两个孩子的最大值最小值来更新自己的最大值最小值。
{
if(tr[p].l==tr[p].r)return;
tr[p].ma=max(tr[2*p].ma,tr[2*p+1].ma);
tr[p].mi=min(tr[2*p].mi,tr[2*p+1].mi);
}
inline void down(int p) //根据自己来更新自己的孩子。
{
if(tr[p].l==tr[p].r)return;
tr[2*p].ma=max(tr[2*p].ma,tr[p].tag2);
tr[2*p].mi=min(tr[2*p].mi,tr[p].tag1);
tr[2*p].tag1=min(tr[p].tag1,tr[2*p].tag1);
tr[2*p].tag2=max(tr[p].tag2,tr[2*p].tag2);
tr[2*p+1].ma=max(tr[2*p+1].ma,tr[p].tag2);
tr[2*p+1].mi=min(tr[2*p+1].mi,tr[p].tag1);
tr[2*p+1].tag1=min(tr[p].tag1,tr[2*p+1].tag1);
tr[2*p+1].tag2=max(tr[p].tag2,tr[2*p+1].tag2);
tr[p].tag1=inf;
tr[p].tag2=0;
}
inline void change1(int p,int l,int r,int x)// 更新l到r的最大值 记住它的逻辑
{
if(tr[p].l>=l&&tr[p].r<=r)
{
tr[p].tag2=max(tr[p].tag2,x);
tr[p].ma=max(tr[p].ma,x);
}
else
{
int mid=(tr[p].l+tr[p].r)/2;
if(l<=mid)
change1(p*2,l,r,x);
if(r>mid)
change1(p*2+1,l,r,x);
up(p);
}
}
inline void change2(int p,int l,int r,int x)//更新l到r的最小值
{
if(tr[p].l>=l&&tr[p].r<=r)
{
tr[p].tag1=min(tr[p].tag1,x);
tr[p].mi=min(tr[p].mi,x);
}
else
{
int mid=(tr[p].l+tr[p].r)/2;
if(l<=mid)
change2(p*2,l,r,x);
if(r>mid)
change2(p*2+1,l,r,x);
up(p);
}
}
inline int ask1(int p,int l,int r)
{
if(tr[p].l>=l&&tr[p].r<=r)
{
return tr[p].ma;
}
else
{
down(p);
int mid=(tr[p].l+tr[p].r)/2;
int ans=0;
if(l<=mid)
ans=max(ans,ask1(p*2,l,r));
if(r>mid)
ans=max(ans,ask1(p*2+1,l,r));
up(p);
return ans;
}
}
inline int ask2(int p,int l,int r)
{
if(tr[p].l>=l&&tr[p].r<=r)
{
return tr[p].mi;
}
else
{
down(p);
int mid=(tr[p].l+tr[p].r)/2;
int ans=inf;
if(l<=mid)
ans=min(ans,ask2(p*2,l,r));
if(r>mid)
ans=min(ans,ask2(p*2+1,l,r));
up(p);
return ans;
}
}
F题题意:给你平行于x轴或y轴的线段,求线段的交点数。看了别人的博客 学习到了树状数组的用法。首先n最大1e5,如果暴力求解肯定超时!所以我们可将横向的线段与竖向线段分开处理 对于横向线段只保留端点 再按x从小到大排序
竖向线段同样从小到大排序,然后枚举竖向线段,将小于该竖向线段横坐标的点进行处理,如果是左端点则对应的y坐标值的处的树状数组+1,若为右端点则-1.
还有一个点很重要,如果直接用y坐标建立树状数组,会浪费很多空间,因为他给的坐标范围很大,我们可以将坐标的值排序,可以用下标代替它真正的坐标。
好题啊。
G题题意:给你两个长度为n的字符串,最长公共子序列的长度为m,可以用k种不同的字符构造字符串,问你有多少种方法构造符合题意的字符串。
注意它的n的范围很大...
得用某种方法,时间复杂度为o(n)或更小..
首先得把答案推出来吧。
用dp[i][j]来表示已经构造了i个字符,后j个是相同的方法。
所以dp[i][j]=dp[i-1][j-1]*k;
而dp[i][0]=(dp[i-1][0]+dp[i-1][1]+...+dp[i-1][m])*k*(k-1);
而sum=dp[n][0]+dp[n][1]+dp[n][2]+...+dp[n][m];
sum的含义为长度为n的字符其前面的最长公共子序列<=m,后面的最长公共子序列<=m 而我们需要的是长度为n的字符其前面的最长公共子序列<=m,后面的最长公共子序列<=m,并且两个等于号至少要有一个,所以我们可计算sum1
其含义为长度为n的字符前面最长公共子序列<=m-1,后面的最长公共子序列<=m-1.两者一减就是答案了。
关键是怎么快速求这个。。
用矩阵快速幂。。时间复杂的o(log(n))..很强。
贴一段矩阵快速幂的板子
struct Mat
{
ll mat[maxn][maxn];
int row,col;
};
Mat mod_mul(Mat a,Mat b,int p) //矩阵的乘法
{
Mat ans;
ans.row=a.row;
ans.col=b.col;
memset(ans.mat,0,sizeof(ans.mat));
for(int i=0;i<ans.row;i++)
for(int k=0;k<a.col;k++)
if(a.mat[i][k])
for(int j=0;j<ans.col;j++)
{
ans.mat[i][j]+=a.mat[i][k]*b.mat[k][j]%p;
ans.mat[i][j]%=p;
}
return ans;
}
Mat mod_pow(Mat a,int k,int p)//快速幂算法
{
Mat ans;
ans.row=a.row;
ans.col=a.col;
for(int i=0;i<a.row;i++)
for(int j=0;j<a.col;j++)
ans.mat[i][j]=(i==j);
while(k)
{
if(k&1)ans=mod_mul(ans,a,p);
a=mod_mul(a,a,p);
k>>=1;
}
return ans;
}
其他题还没写出来。。就不补了。。