首页 > 试题广场 >

小团的默契游戏

[编程题]小团的默契游戏
  • 热度指数:768 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解

        小团从某不知名论坛上突然得到了一个测试默契度的游戏,想和小美玩一次来检验两人的默契程度。游戏规则十分简单,首先有给出一个长度为n的序列,最大值不超过m

        小团和小美各自选择一个[1,m]之间的整数,设小美选择的是l,小团选择的是r,我们认为两个人是默契的需要满足以下条件:

1.    l小于等于r

2.    对于序列中的元素x,如果0<x<l,r<x<m+1,x按其顺序保留下来,要求保留下来的子序列单调不下降。

小团为了表现出与小美最大的默契,因此事先做了功课,他想知道能够使得两人默契的二元组<l,r>一共有多少种。

我们称一个序列A为单调不下降的,当且仅当对于任意的i>j,满足A_i>=A_j


输入描述:

输入第一行包含两个正整数m和n,表示序列元素的最大值和序列的长度。(1<=n,m<=100000)

输入第二行包含n个正整数,表示该序列。



输出描述:
输出仅包含一个整数,表示能使得两人默契的二元组数量。
示例1

输入

5 5
4 1 4 1 2

输出

10
m, n = list(map(int, input().split()))
sequence = list(map(int, input().split()))

def check(sequence, l, r):
    stack = []
    for i in sequence:
        if i < l&nbs***bsp;i > r:
            if not stack&nbs***bsp;i >= stack[-1]:
                stack.append(i)
            else:
                return False
    return True

count = 0
for l in range(1,m+1):
    if not check(sequence, l, m):
        break
    for r in range(l,m+1):
        if check(sequence, l, r):
            count += m-r+1
            break
print(count)
通过!感谢评论区老哥的小trick,如果l=i左边不满足,l再取i+1,i+2不可能满足。。
发表于 2021-10-03 15:00:15 回复(0)
#include <bits/stdc++.h>
using namespace std;

int solve(vector<int> &nums, int m) {
    int cnt = 0, n = nums.size();
    for (int r = m; r > 0; --r) {
        // 枚举 l 的最大可能性
        int lo = 0, hi = r, mid;  // [0, r],0是为了方便判断 (r,m+1) 所选序列是非法的
        while (lo < hi) {
            mid = (hi - lo + 1) / 2 + lo;
            // 在 nums 中选出 (0, l) ∪ (r, m+1) 的元素并检测是否单调非降
            int pre = -1, flag = 1;  // nums[i] > 0
            for (int x: nums) {
                if (x < mid || x > r) {
                    if (x < pre) {
                        flag = 0;
                        break;
                    }
                    pre = x;
                }
            }
            if (flag) {  // 尝试把 l 放大的同时记录答案
                lo = mid;
            } else {  // 一定不是,那么把 l 减小
                hi = mid - 1;
            }
        }
        // 如果 lo = 0,证明不管怎么降低 l 都无法构成单调非降,因为 (r,m+1) 所选的子序列有问题
        if (0 == lo) break;
        cnt += lo;
    }
    return cnt;
}

int main() {
    int m, n; cin >> m >> n;
    vector<int> nums(n);
    for (int i = 0; i < n; ++i) {
        scanf("%d", &nums[i]);
    }
    cout << solve(nums, m) << endl;
    return 0;
}

发表于 2021-08-15 19:28:47 回复(0)
从大到小遍历 r 的可能性,固定 r 的同时利用二分法搜索符合题意的 l。需要注意的是:
(1) 固定r后在进行二分法之前,先检验一下如果只保留大于 r 的元素能否满足单调不减,如果连这个条件都不能满足就没有必要再二分搜索 l 了;事实上,继续往小再搜索 r 也没有意义了(假设把 r 减小为 r - 1,还是要保留序列中大于 r 的元素,那刚才被选出来的不满足单调不减的元素又被选出来了,还是不满足单调不减)。
(2) 在利用二分法搜索 l 时,如果某个 l 满足条件,那小于等于这个 l 的所有正整数也满足条件,符合题意的数对也增加 l 个。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] params = br.readLine().trim().split(" ");
        int m = Integer.parseInt(params[0]);
        int n = Integer.parseInt(params[1]);
        String[] strArr = br.readLine().trim().split(" ");
        int[] arr = new int[n];
        for(int i = 0; i < n; i++) arr[i] = Integer.parseInt(strArr[i]);
        int count = 0;   // 符合默契的数对数
        int r = m, l;
        while(r > 0){
            ArrayList<Integer> temp = new ArrayList<>();
            for(int i = 0; i < n; i++){
                if(arr[i] > r)
                    temp.add(arr[i]);
            }
            if(!isIncrease(temp)) break;
            int lowerBound = 1, upperBound = r;
            // 固定r然后二分法确定l
            int curCnt = 0;   // 在当前r下能获得的符合题意的数对数
            while(lowerBound <= upperBound){
                l = (upperBound + lowerBound) / 2;
                // 保留数组中小于l或大于r的数
                temp = new ArrayList<>();
                for(int i = 0; i < n; i++){
                    if(arr[i] < l || arr[i] > r)
                        temp.add(arr[i]);
                }
                // 检查一下temp是否满足单调不减
                if(isIncrease(temp)){
                    // 如果满足,l就可以取小于等于当前l的数,一共有(l,r)对l个
                    curCnt = l;
                    lowerBound = l + 1;     // 小的肯定满足,尝试更大的l
                }else{
                    upperBound = l - 1;
                }
            }
            r --;
            count += curCnt;
        }
        System.out.println(count);
    }
    
    // 判断序列的单调不减性
    private static boolean isIncrease(ArrayList<Integer> seq) {
        for(int i = 0; i < seq.size() - 1; i++)
            if(seq.get(i) > seq.get(i + 1)) return false;
        return true;
    }
}


编辑于 2021-03-01 12:08:02 回复(9)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] line = br.readLine().split(" ");
        Integer m = Integer.parseInt(line[0]);
        Integer n = Integer.parseInt(line[1]);
        line = br.readLine().split(" ");
        int[] nums = new int[n];
        for(int i = 0; i < n; i++) {
            nums[i] = Integer.parseInt(line[i]);
        }

        int res = 0;
        int l = 1, r = m;
        while (true){
            if (judge(l, r, nums, m)) {
                r--;
            } else break;
        }
        r = r + 1;
        for (int i = r; i <= m; i++) {
            while (l <= i) {
                if (judge(l, i, nums, m)) {
                    l++;
                    res += m - i + 1;
                } else break;
            }
        }
        System.out.println(res);



    }

    private static boolean judge(int l, int r, int[] nums, int m) {//定义函数简化流程
        int tmp = 0;
        for (int num : nums) {
            if ((num > 0 && num < l) || (num < m + 1 && num > r)) {
                if (num >= tmp) tmp = num;
                else return false;
            }
        }
        return true;
    }
}

发表于 2022-03-04 17:53:26 回复(0)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

/*      一种nlgn的做法:
    对于序列seq中每个元素,记它为<pos,val>,其中pos为它在序列中的编号,val为它的值大小。
    初始输入后,seq中元素按照pos升序排序,现在使用稳定排序,将它们按照val升序排序,记处理
    后的序列为arr。为了方便,令arr[0] = <0,0>,arr[n+1] = <n+1,m+1>。arr[1..n]真
    正用于保存元素。

    令:
        满足0<x<l的元素集合为A。
        满足r<x<m+1的元素集合为B。
    此时,对于任意的 l<=r:
        集合A是arr的某个前缀,集合B是arr的某个后缀,且它们显然没有交集。

    现在如果仅考虑集合A中的元素,则它们是合法的 <==> 它们的pos是升序排列的。
        因此可以预先处理arr,找出使得arr[1..P)按照pos升序的最大的P。
        这样就确定了 l<=arr[P].val。

    类似地,如果仅考虑集合B中的元素,则它们是合法的 <==> 它们的pos是升序排列的。
        因此可以预先处理arr,找出使得arr(Q..n]按照pos升序的最小的Q。
        这样就确定了 r>=arr[Q].val。

    此外,如果A和B联合起来要合法,则还需要符合:集合A中最大的pos < 集合B中最小的pos。

    现在对A和B进行搜索,寻找合法的A、B。假设A中pos最大元素为arr[p],B中pos最小元素为arr[q],
    则预先可以确定有这些限制:p<q , p<P , q>Q , arr[p].pos<arr[q].pos。反之只要符合这
    些限制,那么就可以得到合法的A、B,即A=arr[1..p],B=arr[q..n]。
    (为了方便,允许p==0表示A为空集,q==n+1表示B为空集)

    接下来如何搜索合法的A和B(也就是如何搜索合法的p和q)。对于每一个[0,..P)的p,只需要在
    (max(Q,p)...n+1]中,搜索满足arr[p].pos<arr[q].pos的q(范围包含0或N+1是因为允许A、B为空集)。
    对于每一个p,arr[p].pos是一个常数,由于arr(max(Q,p),n+1]是按照pos递增的,因此可以二分搜索满足
    条件的最小的q,找到它后,任何比它大的q都是合法的。

    对于每一对合法的A=arr[1..p],B=arr[q..n],一定有p<q。可以确定对应的l和r:
        1、arr[p].val   < l <= arr[p+1].val
        2、arr[q-1].val <= r < arr[q].val
        3、l <= r
    (回忆:我们令arr[0].val==0、arr[n+1].val==m+1。因此当p为N或者q为1时是可以正常运算的
    此外,还允许p为0或者q为N+1,此时A或B为空集,此时仍然可以按照上述公式计算)
    这里面<l,r>的对数可以如此计算:
        |集合1| * |集合2| - |集合1 ∩ 集合2 中l>r的二元组|
        = |集合1| * |集合2| - Combination(2,|集合1 ∩ 集合2|)

    按照搜索中的顺序,对于一个固定的p,对于不同的q,上述的集合2是互不相交的,且至多存在一个
    集合2(对应搜索到符合条件的最小q的那个)会与集合1有交集。因此,对于固定p的一轮搜索,可以
    将上式聚合起来计算。
*/

class node{
public:
    int val;
    int pos;
    node(int p,int v):pos(p),val(v){}

    class val_comp{
    public:
        bool operator()(const node &a,const node &b) const{
            return a.val<b.val;
        }
    };
};

//vector<node> nodes;
vector<node> arr;
int not_found = -1;
int find_first_greater_pos_index(int pos,int from,int to){  // arr[from,to) 内找到第一个node.pos大于pos的值的下标
    if(from>=to)
        return not_found;
    if(from+1==to){
        if(arr[from].pos>pos)
            return from;
        else
            return not_found;
    }
    int mid = (from+to)/2;
    int tmp;
    if(arr[mid].pos<=pos)
        tmp = find_first_greater_pos_index(pos, mid+1, to);
    else{
        tmp = find_first_greater_pos_index(pos, from, mid);
        if(tmp==not_found)
            tmp = mid;
    }
    return tmp;
}

int main() {
    int n,m;
    cin>>n>>m;
    arr.emplace_back(0,0);
    for(int i=0;i<n;i++){
        int tmp;
        cin>>tmp;
        arr.emplace_back(i+1,tmp);
    }

    std::stable_sort(arr.begin(),arr.end(),node::val_comp());
    arr.emplace_back(n+1,m+1);  // arr[0,1...,n,n+1]
    int mx_index_arr = n + 1;

    int P,Q;
    for(P=1; P <= mx_index_arr; P++)
        if(arr[P].pos<arr[P-1].pos)
            break;

    for(Q= mx_index_arr - 1; Q > 0; Q--)
        if(arr[Q].pos>arr[Q+1].pos)
            break;

    int count = 0;
    for(int p = 0;p<P;p++){
        int q = find_first_greater_pos_index(arr[p].pos, max(Q,p)+1, mx_index_arr + 1);
        if(q==not_found)
            continue;
        int set1 = arr[p+1].val - arr[p].val;
        int set2 = arr[mx_index_arr].val - arr[q - 1].val;
        int inter_set = arr[p+1].val - arr[q-1].val - 1;
        count += set1*set2;
        if(inter_set>0)
            count -= (inter_set-1)*inter_set/2;
    }

    cout<<count;
}
// 64 位输出请用 printf("%lld")

发表于 2023-05-01 19:57:05 回复(0)
import java.io.*;
import java.lang.*;
import java.util.*;

// 1<=x<l || r<x<=m的数都要保留下来,构成一个非递减序列
// 意思是上述两个条件任一范围内保留下来的数不能构成非递减序列,则这个数对肯定不能满足条件
public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] params = br.readLine().trim().split(" ");
        int m = Integer.parseInt(params[0]);
        int n = Integer.parseInt(params[1]);
        int[] nums = new int[n];
        params = br.readLine().trim().split(" ");
        for(int i=0;i<n;++i){
            nums[i] = Integer.parseInt(params[i]);
        }
        // 1.从m开始逆序枚举r的位置,对每个r,首先判断>r的数是否能构成非递减序列,如果不能,枚举结束;
        // 2.如果r满足,则寻找满足条件的最大l,在[1,r]内二分查找。满足条件的对数增加 L 个
        int cnt = 0;
        for(int r=m;r>=1;r--){
            // 步骤1
            if(!notDecreaseWithR(nums,r)){
                break;
            }
            // 步骤2
            cnt += findMaxL(nums,r);
        }
        System.out.println(cnt);
    }

    // 判断>r的能否构成非递减序列
    public static boolean notDecreaseWithR(int[] nums,int r){
        // 留下来的前一个数
        int pre = 0;
        for(int i=0;i<nums.length;++i){
            // 如果当前nums[i]>r,但是<pre,则不能构成非递减序列
            if(nums[i] > r){
                if(nums[i] < pre){
                    return false;
                }
                pre = nums[i];
            }
        }
        return true;
    }

    // 满足1<=x<l || r<x<=m留下来的序列能够构成非递减
    public static boolean notDecreaseWithL(int[] nums,int l,int r){
        int pre = 0;
        for(int i=0;i<nums.length;++i){
            if(nums[i] > r || nums[i] < l){
                if(nums[i] < pre){
                    return false;
                }
                pre = nums[i];
            }
        }
        return true;
    }

    // 在[1,r]找到满足条件的最大 l,二分查找
    public static int findMaxL(int[] nums,int r){
        int low =1;
        int high =r;
        int maxL = 0;
        while(low<=high){
            int l = low + (high-low)/2;
            if(notDecreaseWithL(nums,l,r)){
                maxL = l;
                low = l+1;
            }else{
                high = l-1;
            }
        }
        return maxL;
    }
}
发表于 2022-03-09 10:27:32 回复(0)
import java.io.*;
import java.util.*;
public class Main{
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = " ";
        while((str = br.readLine()) != null){
            String[] str_array = str.split(" ");
            int m = Integer.parseInt(str_array[0]);
            int n = Integer.parseInt(str_array[1]);
            int[] array = new int[n];
            str_array = br.readLine().split(" ");
            for(int i = 0; i < n; ++i) array[i] = Integer.parseInt(str_array[i]);
            int result = 0;
            for(int i = 1; i <= m; ++i){
                int index = function(array,m,i,i,m);
                //System.out.println("以"+i+"开头有"+(n-index+1)+"个");
                result += index == -1? 0 : (n-index+1);
            }
            System.out.println(result);
            
        }
    }
    public static int function(int[] array, int m, int left,int l, int r){//找到以left为起始位置的最小合法区间的右边界值
        int mid;
        while(l < r){
            mid = (l + r) >> 1;
            if(isVaild(array,m,left,mid)) r = mid;
            else l = mid + 1;
        }
        
        return isVaild(array,m,left,l)? l : -1;
    }
    public static boolean isVaild(int[] array, int m,int left, int right){
        int pre = Integer.MIN_VALUE;
        
        for(int value : array){
            if((value > 0 && value < left) || (value > right && value < m+1)){
                if(value < pre) return false;
                pre = value;
            }
        }
        
        
        return true;
    }
    
}

发表于 2022-07-18 11:34:44 回复(0)
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int num[100005],rit[100005],lef[100005];
int main(){
    ios::sync_with_stdio(false);
    int n,m;
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        cin>>num[i];
    }
    for(int i=0;i<=n+1;i++){
        rit[i]=0;
        lef[i]=m+1;
    }
    for(int i=1;i<=n;i++) rit[num[i]]=i; //最后一次出现
    for(int i=n;i>=1;i--) lef[num[i]]=i; //第一次出现
    int cur = n+1,op;
    for(int i=n;i>=1;i--){  //最后一次出现递减序列,找到第一个非法作为op
        if(lef[i]<cur){
            cur=lef[i];
        }else if(lef[i]==n+1){
            lef[i]=cur;
        }else{
            op=i+1;
            break;
        }
    }
    cur=0;
    int ans = 0;
    for(int i=0;i<=n;i++){ //从头开始,拿当前最后一次出现的位置匹配递减序列中第一次出现
        if(rit[i]>=cur){   //当递减序列中第一次出现在你之后的都可以构成二元组
            cur = rit[i];  //二分找
        }else if(rit[i]==0){
            rit[i]=cur;
        } else break;
        ans+=n+2-(lower_bound(lef+op,lef+n+2,cur)-lef);

    }
    cout<<ans<<endl;
    return 0;
}


发表于 2021-08-17 14:11:00 回复(0)
超时,通过百分之60
m,n=map(int,input().split())
arr=list(map(int,input().split()))
#注意:(l,r)默契则(l,r+i)默契且(l-i,r)也默契
last=0
res=0
def isMo(l,r):
    a=list(arr)
    for i in range(l,r+1):
        while i in a:
            a.remove(i)
    if sorted(a)==a:
        return True
    else:
        return False
for l in range(m,0,-1):
    if l==m:
        r=l
        if isMo(l,r):
            res+=l
            last=r-1
        else:
            last=r
    else:
        if isMo(l,l):
            res+=l*(m-l+1)
            last=l-1
        else:
            #用二分法搜索构成默契的最小的r
            r=last
            if l==r:
                continue
            elif not isMo(l,r):
                continue
            else:
                ll=l 
                while ll<r:
                    mid=(ll+r)//2
                    if isMo(l,mid):
                        r=mid
                    else:
                        ll=mid+1
                res+=l*(last-ll+1)
                last=ll-1
print(res)


发表于 2021-04-27 16:46:35 回复(1)
import java.util.Scanner;

public class Main {
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int m = sc.nextInt();
        int n = sc.nextInt();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++)
            arr[i] = sc.nextInt();
        int ans = 0;
        int r = m;
        while (r > 0) {
            int cnt = 0;
            if (!check(arr, 0, r)) {
                break;
            }
            int l = 1;
            int temp = r;
            int mid = 0;
            while (l <= r) {
                mid = l + (r - l) / 2;
                if (check(arr, mid, temp)) {
                    cnt = mid;
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
            //System.out.println("r = "+temp+" cnt = "+cnt);
            r = temp - 1;
            ans += cnt;
        }
        System.out.println(ans);
    }

    public static boolean check(int[] arr, int l, int r) {
        int pre = 0;
        for (int x : arr) {
            if (x >= l && x <= r)
                continue;
            else if (x < pre)
                return false;
            else {
                pre = x;
            }
        }
        return true;
    }

}

发表于 2021-03-14 18:22:32 回复(0)