zoukankan      html  css  js  c++  java
  • 2020牛客暑假多校训练二

    暂时更新前七道题(按难度排序)

    D.Duration

    题意:

    求给出时间的秒差

    题解:

    统一转换为秒,然后取相减的绝对值

    时间复杂度:O(1)

    C.Cover the Tree

    题意:

    给一棵无根树,求最少的链覆盖所有的边,并输出任意符合条件的结果集合

    题解:

    对于一颗树而言,我们取入度为1的作为叶子,以其中任意一个大于1度的作为根结点。我们会发现,叶子结点到其父节点的边是不会被重叠覆盖,除非使用两次该叶节点作为链的端点。所以要使得最小链覆盖所有,即将叶子结点进行不重复的两两匹配,从而将树完全覆盖。所以链个数的下界即为 $⌈frac{s}{2}⌉ $

    而对于 (nleq2) 的情况,结果显然。而 (n > 3) 则是 取dfs序 进行排序,将前 (n/2) 化为一组,后 (n/2) 化为另一组,然后匹配

    证明:

    设某边儿子结点所覆盖的叶子结点范围为 ([L,R])

    如果在([L,R]) 范围内的某叶子结点,与该范围以外的某叶子结点相匹配,则此边一定会被经过

    · 如果 (Rleq frac{n}{2}) 那么此边必定会被 (R - R+frac{n}{2}) 覆盖

    · 如果 (L> frac{n}{2}) 那么此边必定会被 (L-frac{n}{2} — L) 覆盖

    · 如果(R> frac{n}{2} 且 L leq frac{n}{2}) 则必定存在 (xin [L,n/2] 或者 [n/2+1,R] 与 L+frac{n}{2} 和 R -frac{n}{2} 匹配)

    从而使得此边被覆盖

    否则,根的度数不为1,所以 l ≠ 1或者 r ≠ s 必有一个是满足的;l ≠ 1可以得出,这条边在l 1- l s/2+1; r ≠ s,这条边被 l s/2- ls 覆盖;

    s点为奇数时,只需根据叶子结点再接一个节点

    时间复杂度:O (n)

    F.Fake Maxpooling

    题意:

    给出 (n imes m) 的矩阵,其中 (A_{i,j}=lcm(i,j)) ,给出 (k) 求出,所有大小为 (k) 的子矩阵中的最大值的和.

    注意不是 "和的最大值"

    范围(n,m,k (1≤n,m≤5000,1≤k≤min {n,m }))

    题解:

    对于求在大矩阵中求 给出子矩阵的最值,有以下常见的方法

    DP , 单调队列,优先队列,倍增ST表

    已知 lcm(a,b) = a*b/gcd(a,b) ,又n,m<5000 ,gcd() 的 时间复杂度大约为 log(n) , 所以对于每个位置的值 :5000 x 5000 x log(5000) ~ 1e8

    而这里时间限制大概在3s左右,可能数据没有卡好, (O(nmlogn)) 基本上没有 (TLE)

    题解中给出了 (O(nm))(A_{i,j}的gcd) 的方法,利用倍数,类似埃式筛。对于筛出任何互质的 (i,j) ,以及其倍数

    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(!gcd[i][j]){
                for(int k=1; k*i<=n&&k*j<=m;k++){
                    gcd[i*k][j*k] = k, A[k*i][k*j] = i*j*k;//lcm
                }
            }
        }
    }
    

    方法一:即是标程的 【单调队列】(O(a*b))

    类似滑动窗口,处理出在行列 范围【i,i+k】的最大值,然后横向维护,纵向维护一次即可

    int a[5005][5005];//a为处理出来的 lcm(i,j)
    int que[5005];//维护下标
    int b[5005][5005];
    void func(){
    	long long sum = 0;
    	for(int i=1;i<=n;i++){
    		int l=1,r=1;
    		for(int j=1;j<=m;j++){
    			while(r>l && j-que[l] >= k) ++l;
    			while(r>l && a[i][j]>a[i][que[r-1]]) --r;
    			que[r++] = j;
    			if(j>=k) b[i][j-k+1] = a[i][que[l]];
    		}
    	}
    	//横向于纵向维护方式一样 
    	for(int j=1;j<=m-k+1;j++){
    		int l=1,r=1;
    		for(int i=1;i<=n;i++){
    			while(r>l && i-que[l] >= k) ++l;
    			while(r>l && b[i][j]>b[que[r-1]][j]) --r;
    			que[r++] = i;
    			if(i>=k) sum += b[que[l]][j];
    		}
    	}
    	cout<<sum<<endl;
    }
    

    方法二: 【递推/倍增】(O(a*b*log(k)))

    要求出范围为 k*k 的矩阵最值,我们可以采用递推的方法

    递推公式 (maxv(i,j,k) 标识 以(i,j) 为左上角,大小为k的矩形最值)

    (maxv(i,j,k)=max{maxv(i,j,k-1), maxv(i+1,j+1,k-1), maxv(i+1,j,k-1), maxv(i,j+1,k-1)})

    这样时间复杂度会达到 (O(a*b*k)) , 但这样明显会超时 最坏便是5000^3

    由于 (O(a*b)) 为复杂度下限,所以主要考虑如何优化 k

    我们可以考虑倍增的思想 (其实也是基于分治的思想):

    (maxv(i,j,k)=max{maxv(i,j,k), maxv(i+2^{(k-1)},j+2^{(k-1)},k-1),)

    (maxv(i,j+2^{(k-1)},k-1), maxv(i+2^{(k-1)},j,k-1) })

    其他就是主要注意一下关于处理范围的取舍. 由于数据不是那么严格所以 (O(a*b*log(k))) 能过

    void func(){
    	long long sum = 0;
    	int lok;
    	lok=floor(log2(k));
    	for(int l=0;l<=lok-1;l++){
    		for(int i=1;i+(1<<l)<=n;i++){
    			for(int j=1;j+(1<<l)<=m;j++){
    				a[i][j] = max(a[i][j], max(a[i+(1<<l)][j+(1<<l)], max(a[i+(1<<l)][j], a[i][j+(1<<l)])));//最后处理出来的大小为 2^lok
    			}
    		}
    	}
    	
    	for(int i=1;i<=n-k+1;i++){
    		for(int j=1;j<=m-k+1;j++){
    			sum += max(a[i][j],max(a[i+k-(1<<lok)][j+k-(1<<lok)],max(a[i+k-(1<<lok)][j],a[i][j+k-(1<<lok)])));
    		}
    	}
    	cout<<sum<<endl;
    }
    

    B.Boundary

    题意:

    给出在二维平面上的 (n) 个点,其中 ((0,0)) 必定在该圆的边界上,找到一个圆,使得在该圆边界上的点数最多.

    题解:

    考虑暴力情况: 枚举两个点,再枚举其他点是否再该圆上 (找出圆心,两中弦垂线交点) (O(n^3))

    思路一:

    利用同弧所对圆周角相等(充分条件),可以求出圆周角,判断是否相同。

    由于是充分条件,所以在圆周角相同时还需要增加 $overrightarrow{OP} imes overrightarrow{OA} >0 $

    通过增加方向判断可以得到同侧的圆周角相等时对应同弧。最后取众数即可

    二维向量叉乘公式 (a(x_1,y_1),b(x_2,y_2)) , (overrightarrow{a} imes overrightarrow{b}=(x_1*y_2-x_2*y_1)) 交叉相乘再相减

    思路二:

    可以利用三点求圆心公式,求得圆心坐标,以出现的最多次数作为结果

    有圆上三点为(x1,y1),(x2,y2),(x3,y3)

    img
    设圆的公式如下:
    (Ax^{2}+Ay^{2}+Bx+Cy+D=0)
    系数由如下行列式求得:

    (A=egin{vmatrix} x_{1}& y_{1} & 1\ x2& y2& 1\ x3& y3& 1 end{vmatrix})
    (=x_{1}(y2-y3)-y_{1}(x2-x3)+x2x3-x3x2)

    (B=-egin{vmatrix} x_{1}^{2}+y_{1}^{2}& y_{1} & 1\ x_{2}^{2}+y_{2}^{2}& y_{2}& 1\ x_{3}^{2}+y_{3}^{2}& y_{3}& 1 end{vmatrix})
    (= (x_{1}^{2}+y_{1}^{2})(y_{3}-y_{2})+ (x_{2}^{2}+y_{2}^{2})(y_{1}-y_{3})+(x_{3}^{2}+y_{3}^{2})(y_{2}-y_{1}))

    (C=egin{vmatrix} x_{1}^{2}+y_{1}^{2}& x_{1} & 1\ x_{2}^{2}+y_{2}^{2}& x_{2}& 1\ x_{3}^{2}+y_{3}^{2}& x_{3}& 1 end{vmatrix})
    (=(x_{1}^{2}+y_{1}^{2})(x_{2}-x_{3})+ (x_{2}^{2}+y_{2}^{2})(x_{3}-x_{1})+(x_{3}^{2}+y_{3}^{2})(x_{1}-x_{2}))

    (D=-egin{vmatrix} x_{1}^{2}+y_{1}^{2}& x_{1} & y_{1}\ x_{2}^{2}+y_{2}^{2}& x_{2}& y_{2}\ x_{3}^{2}+y_{3}^{2}& x_{3}& y_{3} end{vmatrix})
    (=(x_{1}^{2}+y_{1}^{2})(x_{3}y_{2}-x_{2}y_{3})+ (x_{2}^{2}+y_{2}^{2})(x_{1}y_{3}-x_{3}y_{1})+(x_{3}^{2}+y_{3}^{2})(x_{2}y_{1}-x_{1}y_{2}))

    将圆方程化为标准方程:
    ((x-(-frac{B}{2A}))^2+(y-(-frac{C}{2A})^2)=(sqrt{frac{B^{2}+C^{2}-4AD}{4A^{2}}})^{2})

    将上述系数代入即可解得圆心(x,y)和半径R
    (x=-frac{B}{2A} = frac{(x_{1}^{2}+y_{1}^{2})(y_{3}-y_{2})+ (x_{2}^{2}+y_{2}^{2})(y_{1}-y_{3})+(x_{3}^{2}+y_{3}^{2})(y_{2}-y_{1})}{2 imes(x_{1}(y2-y3)-y_{1}(x2-x3)+x2x3-x3x2)})

    (y=-frac{C}{2A} = -frac{(x_{1}^{2}+y_{1}^{2})(x_{2}-x_{3})+ (x_{2}^{2}+y_{2}^{2})(x_{3}-x_{1})+(x_{3}^{2}+y_{3}^{2})(x_{1}-x_{2})}{2 imes(x_{1}(y2-y3)-y_{1}(x2-x3)+x2x3-x3x2)})

    (r=sqrt{frac{B^{2}+C^{2}-4AD}{4A^{2}}})

    Code:

    const int maxn = 2e6+5;
    struct Node{
        double x,y;
    }node[maxn];
    int ans ;
    
    map<pair<double,double>,int>mp;
    //由于其中一个点为(0,0)
    
    void solve(Node a,Node b){ 
        double x = ((a.x*a.x+a.y*a.y)*(b.y)-(b.x*b.x+b.y*b.y)*(a.y))/(2.0*(a.x*b.y-a.y*b.x));
        double y = ((a.x*a.x+a.y*a.y)*(b.x)-(b.x*b.x+b.y*b.y)*(a.x))/(2.0*(a.y*b.x-a.x*b.y));
        mp[{x,y}]++;
        ans=max(ans,mp[{x,y}]);
    }
    
    int main(){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>node[i].x>>node[i].y;
        }
        
        for(int i=1;i<=n;i++){
            mp.clear();
            for(int j=i+1;j<=n;j++){
                if(node[i].x*node[j].y==node[i].y*node[j].x) continue; //不能关于原点对称
                solve(node[i],node[j]);
            }
        }
        cout<<ans+1<<endl;
        return 0;
    }
    

    J.Just Shuffle

    题意:

    给出长为 (n) 的排列 以及一个整数 (k) , 你要找到一个置换排列 (P) 是的通过对({1,2,...,n}) 进行(k) 次置换操作,得到 (A) 排列。如果存在 (P) 则输出任意,否则输出 (-1) . (输出的置换规律P即为 对({1,2,...,n})进行第一次置换的结果)

    范围:(n,k (1≤n≤10^5,10^8≤k≤10^9))

    题解:

    这里涉及到 置换群 以及 逆元

    参考下面博客的内容:

    https://blog.csdn.net/qq_36102055/article/details/107456175

    置换概念

    对于一个n个元素集合S={a1,a2...an},排列P={p1,p2...pn},将排列P每个元素当做S的下标替换掉原本S每个元素的位置,就得到一个置换(f={a_{p1},a_{p2}...a_{pn}})

    通过多次置换,可以发现规律:

    排列 A=(2,3,1,5,4)

    1.置换是可以分成块的:也就是(2,3,1)和(4,5)这两个块,你可以发现无论怎么置换都不会发生这两个集合的元素之间互换。
    2.存在循环:也就是对于每个块,在置换一定次数之后就会变回原样,比如(2,3,1)这个块在置换三次后就会变回原来排列中的位置
    3.每个环的(下面不叫块了叫环)循环的大小就是块的元素的数目

    可以证明所有的置换都可以拆成若干个环w1,w2...wn, 而每个环的大小为r1,r2...rn,也就是说对于环wi在对原排列置换ri次后保持不变,那么也就可以得到原排列置换lcm(r1,r2...rn)次后保持不变。

    关系

    (C = lcm(r_1,r_2,..,r_n))

    (A^C = A)

    #include<bits/stdc++.h>
    #define INF 0x3f3f3f3f
    #define ll long long
    #define pb push_back
    #define eps 1e-8
    #define endl '
    '
    using namespace std;
    const ll maxn = 1e6 + 5;
    int n,k,a[maxn],ans[maxn];
    bool vis[maxn];
    vector<int> v;
    void setinv(){
        int r = v.size(),inv;//r为环的大小,inv为k在r下的逆元
        for(int i = 0; i < r; i++)
            if((ll) k * i % r == 1)inv = i;//求逆元
        for(int i = 0; i < r; i++)
            ans[v[i]] = v[(i + inv) % r];//转inv次
    }
    int main(){
        scanf("%d %d",&n,&k);
        for(int i = 1; i <= n; i++)
            scanf("%d",a + i);
        for(int i = 1; i <= n; i++){
            if(!vis[i]){
                v.clear();
                int x = a[i];
                while(!vis[x]){//找环操作
                    v.push_back(x);
                    vis[x] = 1;
                    x = a[x];
                }
                //找到环后就处理该环所有位置的值
                setinv();
            }
        }
        for(int i = 1; i <= n; i++)
            printf("%d ",ans[i]);
        return 0;
    }
    
    

    A.All with Pairs

    题意:

    给定n个字符串,求所有字符串最长的前缀与后缀相等长度的平方和。
    如样例,匹配长度为1,2,3的分别有4,4,1个,所以答案为(4×1^2+4×2^2+1×3^2=29)

    范围: (n,(1≤n≤10^5)) (sum|s_i|leq10^6)

    题解:

    首先思考暴力的方法 n 为个数,m为长度

    (O(n imes m))利用map记录后缀出现次数​ + (O(n^2)) 枚举两个点 x (O(m)) 遍历前缀长度计算总和

    这个复杂度快 (O(n^3)) 了,所以需要思考如何简化复杂度

    考虑使用 进制hash 求出所有的后缀值,并保存于map中(相对map 好点)

    (O(n imes m)) 求出后缀出现次数 , 再 (O(n imes m))遍历n个字符串的前缀值,出现与后缀相同则相加。但是原题中要求求出最长匹配,所以要减去前面多加的长度。可以使用 next[] 数组

    例如 (aba)(aba) 匹配的时候:

    能够匹配到 (a,aba) 由于要求最长的作为 贡献值,所以一开始 加上 (a) 的贡献之后就需要被剪掉。

    我们知道 next[] 数组记录上一次前缀出现相同长度的位置,所以减去该位置长度即可

    //给定n个字符串,求所有字符串最长的前缀与后缀相等长度的平方和。
    //如样例,匹配长度为1,2,3的分别有4,4,1个,所以答案为
    #include<bits/stdc++.h>
    using namespace std;
    typedef unsigned long long ull;
    const int maxn = 1e6+5;
    const ull mod = 998244353;
    string str[maxn];
    int nex[maxn];
    map<ull,ull>Hsh;
    const ull base = 131;
    void getNext(string s){
        int i=0,j=-1;
        nex[i] = j;
        while(i<(int)s.length()){
            if(j==-1 || (s[i] == s[j])){
                i++,j++;
                nex[i] = j;
            }else j = nex[j];//向前匹配位置移动
        }
    }
    void getHash(string s){
        ull ans = 0;
        ull bas = 1;
        for(int i=s.length()-1;i>=0;i--){
            ans = (s[i]*bas + ans);
            bas *= base;
            Hsh[ans]++;
        }
    }
    int main(){
        int n;
        cin>>n;
        for(int i=1;i<=n;i++) cin>>str[i];
        ull ans = 0;
        for(int i=1;i<=n;i++) getHash(str[i]);
        for(int i=1;i<=n;i++){
            ull res = 0;
            getNext(str[i]);
            for(int j=0;j<(int)str[i].length();j++){
                res = (res*base+str[i][j]);
               // cout<<res<<" "<<Hsh[res]<<endl;
                ans = (ans + Hsh[res] * (j+1) % mod *(j+1) %mod)%mod ;//个数
                ans = (ans - Hsh[res] * nex[j+1] % mod *nex[j+1] % mod + mod)%mod;
                ans = (ans%mod+mod)%mod;
            }
        }
        cout<<ans<<endl;
        return 0;
    }
        
    

    G.Greater and Greater

    题意:

    给一个大小为 (n) 的序列 (A) , 和一个大小为 (m) 的序列 (B) ,求出长为 (m)(A) 中的子区间,使得(S_igeq B_i) (iin{1,2,..,m})

    范围:

    (n,m (1≤n≤150000,1≤m≤min{n,40000}).)

    (1leq A_i,B_ileq10^9)

    思路:

    如果最粗暴的方式去匹配 复杂度为 (0(nm)) , 大概6e9 左右,那肯定超时了。

    所以如何优化,或者通过什么去记录状态,便于判断?

    我们可以用bitset维护状态,用一个 bitsetf[m] , 维护(A) 的每个位置与比 (b_j) 的关系。

    那么要使得 子区间 S 的每一位大于 B 的每一位,只需要在对位上,所有的都符合大于 (S_j>B_j) 的关系即可。

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 2e5+100;
    bitset<N>ans,f;
    struct node{
        int pos, x;
        bool operator < (const node &A) const {
            return x > A.x;
        }
    }a[N],b[N];
    int n,m;
    int main(){
        scanf("%d%d",&n,&m);
        for (int i = 1; i <= n; ++i)
            scanf("%d",&a[i].x), a[i].pos = i;
        for (int i = 1; i <= m; ++i)
            scanf("%d",&b[i].x), b[i].pos = i;
        sort(a+1,a+n+1);
        sort(b+1,b+m+1);
        ans.set(); f.reset();
    
        for (int i = 1, j = 0; i <= m; ++i){
            while((j+1) <= n && a[j+1].x >= b[i].x){
                f[a[j+1].pos] = 1;
                j++;
            }
            ans &= (f >> b[i].pos);
        }
        printf("%d
    ",(int)ans.count());
        return 0;
    }
    

    H.Happy Triangle

    题意:

    给出一个 multiset (MS) 和 q 次操作,(MS) 一开始是空的,有以下三种类型操作

    1.插入 (x) 到 MS 中

    2.删除 (x) 在 MS 中

    3.给出一个 (x) , 是否能在 MS 中找到两个元素 a,b 使得与 (x) 一起构成 nondegenerate triangle

    (三点不共线的三角形)

    思路:

  • 相关阅读:
    iOS 9音频应用播放音频之第一个ios9音频实例2
    隐写术: 基地组织如何用视频隐藏绝密文档?
    stm32 Fatfs 读写SD卡
    STM32应用笔记转载
    STM32-NVIC中断管理实现[直接操作寄存器]
    FatFs源码剖析
    STM32固件库详解(转)
    Arduino线程库ProtoThreads
    ARM9(TQ2440)裸机代码分享
    将USBASP改造成STK502编程器(转)
  • 原文地址:https://www.cnblogs.com/Tianwell/p/13406379.html
Copyright © 2011-2022 走看看