LeetCode - 第37场双周赛

Rank

这次的 C、D 都有难度,比赛时没有好的思路.

A. 删除某些元素后的数组均值

简单模拟.

class Solution {
public:
    double trimMean(vector<int>& arr) {
        int n=arr.size();
        sort(arr.begin(),arr.end());
        double ans=0;
        int sz=n/20;
        for(int i=sz;i+sz<n;++i) ans+=arr[i];
        ans/=1.0*(n-sz-sz);
        return ans;
    }
};

B. 网络信号最好的坐标

暴力枚举二维空间中输入范围内的所有点,计算最大值.

class Solution {
public:
    vector<int> bestCoordinate(vector<vector<int>>& towers, int radius) {
        int n=towers.size();
        int minx=INT_MAX,maxx=0;
        int miny=INT_MAX,maxy=0;
        for(auto t:towers){
            int x=t[0],y=t[1];
            minx=min(x,minx);
            maxx=max(x,maxx);
            miny=min(y,miny);
            maxy=max(y,maxy);
        }
        int rx,ry,ans=0;
        for(int x=minx;x<=maxx;++x){
            for(int y=miny;y<=maxy;++y){
                int sum=0;
                for(auto t:towers){
                    int tx=t[0];
                    int ty=t[1];
                    int q=t[2];
                    int d2=(x-tx)*(x-tx)+(y-ty)*(y-ty);
                    if(d2>radius*radius) continue;
                    sum+=floor(1.0*q/(1.0+sqrt(1.0*d2)));
                }
                if(sum>ans){
                    ans=sum;
                    rx=x;
                    ry=y;
                }
            }
        }
        return {rx,ry};
    }
};

C. 大小为 K 的不重叠线段的数目

解法一:dp.

dp[i][j][0]dp[i][j][1] 分别表示前 i 个点选择 j 个互不相交的线段 不使用/使用 最后一个点的合法方案数. 如果使用最后一个点,表示最后一个区间的右端点是第 i 个点. 递推方程如下:

dp[i][j][0] 比较好分析,不使用 第 i 个点,那么只能在前 i-1 个点中选择 j 个区间,因此:
d p [ i ] [ j ] [ 0 ] = d p [ i − 1 ] [ j ] [ 0 ] + d p [ i − 1 ] [ j ] [ 1 ] dp[i][j][0]=dp[i-1][j][0]+dp[i-1][j][1] dp[i][j][0]=dp[i1][j][0]+dp[i1][j][1]
dp[i][j][1] 应当这样考虑,最后一个区间的长度如果为 1,则应当在前 i-1 个点中选出 j-1 个点,即:
d p [ i − 1 ] [ j − 1 ] [ 0 ] + d p [ i − 1 ] [ j − 1 ] [ 1 ] dp[i-1][j-1][0]+dp[i-1][j-1][1] dp[i1][j1][0]+dp[i1][j1][1]
如果最后一个区间的长度大于 1,则对于前 i-1 个点来说,仍然相当于从中取出 j 个区间,且最后一个区间的右端点是第 i-1 个点,即:
d p [ i − 1 ] [ j ] [ 1 ] dp[i-1][j][1] dp[i1][j][1]
因此,dp[i][j][1] 为上述二者之和:
d p [ i ] [ j ] [ 1 ] = d p [ i − 1 ] [ j − 1 ] [ 0 ] + d p [ i − 1 ] [ j − 1 ] [ 1 ] + d p [ i ] [ j − 1 ] [ 1 ] dp[i][j][1]=dp[i-1][j-1][0]+dp[i-1][j-1][1]+dp[i][j-1][1] dp[i][j][1]=dp[i1][j1][0]+dp[i1][j1][1]+dp[i][j1][1]
初始状态为 d p [ 0 ] [ 0 ] [ 0 ] = 1 dp[0][0][0]=1 dp[0][0][0]=1 .

class Solution {
public:
    constexpr static int maxn=1005;
    constexpr static int mod=1e9+7;

    int dp[maxn][maxn][2];

    int numberOfSets(int n, int k) {
        dp[0][0][0]=1;
        for(int i=1;i<=n;++i){
            for(int j=0;j<i;++j){
                dp[i][j][0]=dp[i-1][j][0]+dp[i-1][j][1];
                dp[i][j][0]%=mod;

                dp[i][j][1]=dp[i-1][j][1];
                if(j-1>=0){
                    dp[i][j][1]+=dp[i-1][j-1][0];
                    dp[i][j][1]%=mod;
                    dp[i][j][1]+=dp[i-1][j-1][1];
                    dp[i][j][1]%=mod;
                }
            }
        }
        return (dp[n][k][0]+dp[n][k][1])%mod;
    }
};

解法二:组合数学.

题目问满足 0 ≤ l 1 < r 1 ≤ l 2 < r 2 . . . ≤ l k < r k < n 0 \leq l_1 < r_1 \leq l_2<r_2 ... \leq l_k<r_k<n 0l1<r1l2<r2...lk<rk<n 这个式子的 ( l 1 , r 1 , l 2 , r 2 . . . l k , r k ) (l_1,r_1,l_2,r_2...l_k,r_k) (l1,r1,l2,r2...lk,rk) 的个数.

做数学变换,令 l i ′ = l i + i − 1 , r i ′ = r i + i − 1 l_i'=l_i+i-1,r_i'=r_i+i-1 li=li+i1,ri=ri+i1. 问题变成了求解满足
0 ≤ l 1 ′ < r 1 ′ < l 2 ′ < r 2 ′ . . . < l k ′ < r k ′ < n + k − 1 0 \leq l_1' < r_1' <l_2'<r_2' ... < l_k'<r_k'<n+k-1 0l1<r1<l2<r2...<lk<rk<n+k1
这个式子的 ( l 1 ′ , r 1 ′ , l 2 ′ , r 2 ′ . . . l k ′ , r k ′ ) (l_1',r_1',l_2',r_2'...l_k',r_k') (l1,r1,l2,r2...lk,rk) 的个数.

相当于从 n + k − 1 n+k-1 n+k1 个互不相同的元素中取出 2 k 2k 2k 个数,即 a n s = C n + k − 1 2 k ans=C^{2k}_{n+k-1} ans=Cn+k12k.

class Solution:
    def numberOfSets(self, n: int, k: int) -> int:
        return math.comb(n+k-1,k*2) % (10**9+7)

D. 奇妙序列

解法一:线段树.

用线段树维护区间和,同时支持区间加法和区间乘法,需要两个 lazy 标记 addmul 分别对应加操作和乘操作. 对于线段树上的某个结点 x,我们规定计算顺序是先乘后加即最终的值应该是 x*mul+add;所以如果遇到了先加后乘的情况,在下推乘法标记 mul 的时候会影响到子节点的 add 标记,子节点的实际值变成 (x+add)*mul=x*mul+add*mul,所以要把子节点的 add 标记乘上当前结点的 mul,才能保证向下的正确性.

namespace SegTree{
#define node tree[id]
#define lson tree[id<<1]
#define rson tree[id<<1|1]
    const int maxn=1e5+50;
    const int mod=1e9+7;
    typedef long long ll;

    struct Tree{
        int le,ri,len;
        ll sum,add,mul;
    }tree[maxn<<2];

    void build(int id,int le,int ri){
        node.le=le;
        node.ri=ri;
        node.len=ri-le+1;
        node.sum=0;
        node.add=0;
        node.mul=1;
        if(le==ri) return;
        int mid=le+ri>>1;
        build(id<<1,le,mid);
        build(id<<1|1,mid+1,ri);
    }

    void pushup(int id){
        node.sum=lson.sum+rson.sum;
        node.sum%=mod;
    }

    void add(int id,ll val){
        node.sum=(node.sum+val*node.len)%mod;
        node.add=(node.add+val)%mod;
    }

    void mul(int id,ll val){
        node.sum=node.sum*val%mod;
        node.add=node.add*val%mod;
        node.mul=node.mul*val%mod;
    }

    void pushdown(int id){
        mul(id<<1,node.mul);
        add(id<<1,node.add);
        mul(id<<1|1,node.mul);
        add(id<<1|1,node.add);
        node.add=0;
        node.mul=1;
    }

    void update_add(int id,int le,int ri,ll val){
        if(node.le==le && node.ri==ri){
            add(id,val);
            return;
        }
        pushdown(id);
        int mid=node.le+node.ri>>1;
        if(ri<=mid) update_add(id<<1,le,ri,val);
        else if(le>mid) update_add(id<<1|1,le,ri,val);
        else{
            update_add(id<<1,le,mid,val);
            update_add(id<<1|1,mid+1,ri,val);
        }
        pushup(id);
    }

    void update_mul(int id,int le,int ri,ll val){
        if(node.le==le && node.ri==ri){
            mul(id,val);
            return;
        }
        pushdown(id);
        int mid=node.le+node.ri>>1;
        if(ri<=mid) update_mul(id<<1,le,ri,val);
        else if(le>mid) update_mul(id<<1|1,le,ri,val);
        else{
            update_mul(id<<1,le,mid,val);
            update_mul(id<<1|1,mid+1,ri,val);
        }
        pushup(id);
    }

    int query(int id,int le,int ri){
        if(node.le==le && node.ri==ri){
            return node.sum;
        }
        pushdown(id);
        int mid=node.le+node.ri>>1;
        if(ri<=mid) return query(id<<1,le,ri);
        else if(le>mid) return query(id<<1|1,le,ri);
        else return (query(id<<1,le,mid)+query(id<<1|1,mid+1,ri))%mod;
    }
}

class Fancy {
public:
    int tot,n=1e5;

    Fancy() {
        tot=0;
        SegTree::build(1,1,n);
    }

    void append(int val) {
        ++tot;
        SegTree::update_add(1,tot,tot,val);
    }

    void addAll(int inc) {
        SegTree::update_add(1,1,tot,inc);
    }

    void multAll(int m) {
        SegTree::update_mul(1,1,tot,m);
    }

    int getIndex(int idx) {
        if(++idx>tot) return -1;
        return SegTree::query(1,idx,idx);
    }
};

解法二:数论.

所有的 addAllmultAll 操作都浓缩为 (a,b) 表示将 x 变换成 ax+b. 最初 a=1,b=0 .

  • 执行 addAll(inc) 操作,就将 b+=inc.
  • 执行 multAll(m) 操作,就将 ab 同时乘 m.

我们维护这样的 2 个序列 ab 以及初始值序列 v,在遇到 getIndex(idx) 时, ( a _ i d x , b _ i d x ) (a\_{idx},b\_{idx}) (a_idx,b_idx) 表示在 v _ i d x v\_{idx} v_idx 被加入之前,将所有操作浓缩后的结果,此时序列的最后一项 ( a , b ) (a,b) (a,b) 表示直到目前为止所有操作浓缩后的结果.

也就是说,对 v _ i d x v\_{idx} v_idx 进行的操作就相当于把 ( a _ i d x , b _ i d x ) (a\_{idx},b\_{idx}) (a_idx,b_idx) 变成 ( a , b ) (a,b) (a,b) 的操作,记为 ( a 0 , b 0 ) (a_0,b_0) (a0,b0).
a i d x × a 0 ≡ a   ( m o d   m ) b i d x × a 0 + b 0 ≡ b   ( m o d   m ) a_{idx}×a_0 \equiv a \ (mod \ m) \\ b_{idx}×a_0+b_0 \equiv b \ (mod \ m) aidx×a0a (mod m)bidx×a0+b0b (mod m)
通过求解线性同余方程组,即可得到 ( a 0 , b 0 ) (a_0,b_0) (a0,b0) 的值.

class Fancy {
private:
    constexpr static int mod=1e9+7;
    vector<int> v,a,b;

public:
    Fancy() {
        v.clear();
        a.clear();
        b.clear();
        a.emplace_back(1);
        b.emplace_back(0);
    }

    int pw(int x,int n){
        int ans=1;
        while(n){
            if(n&1) ans=1LL*ans*x%mod;
            x=1LL*x*x%mod;
            n>>=1;
        }
        return ans;
    }

    void append(int val) {
        v.emplace_back(val);
        a.emplace_back(a.back());
        b.emplace_back(b.back());
    }

    void addAll(int inc) {
        b.back()=b.back()+inc;
        b.back()%=mod;
    }

    void multAll(int m) {
        b.back()=1LL*b.back()*m%mod;
        a.back()=1LL*a.back()*m%mod;
    }

    int getIndex(int idx) {
        if(idx>=(int)v.size()) return -1;
        int a0=1LL*pw(a[idx],mod-2)*a.back()%mod;
        int b0=(b.back()-1LL*b[idx]*a0%mod+mod)%mod;
        return (1LL*a0*v[idx]+b0)%mod;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值