2016多校训练总结

三道水题先放到前面..

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;
}
其他题还没写出来。。就不补了。。

  




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值