二分+前缀和优化累加

题意

有 n 块木板排成一排的围栏。

莫诺卡普决定按照以下规则粉刷围栏:

  • 栅栏的每块木板都要涂上一种颜色;
  • 栅栏上的每块木板都要涂上恰好一种颜色;在m种颜色中只能选两种颜色
  • 涂上相同颜色的栅栏木板必须形成一个连续的序列,也就是说,所有涂上相同颜色的木板对之间都没有涂上不同颜色的木板。

Monocarp 公司有 m 种不同的油漆,其中 i 种颜色的油漆足以涂刷不超过 ai 块木板。Monocarp 不会再购买其他油漆。

你的任务是确定满足 Monocarp 所描述的所有愿望的不同栅油漆方法的数量。如果有一块木板在这两种涂漆方式下被涂上了不同的颜色,那么这两种涂漆方式就被认为是不同的。

输入

第一行包含一个整数 t( 1≤t≤1e4 ) - 测试用例数。

每个测试用例的第一行包含两个整数 n 和 m ( 2≤n,m≤2⋅105) --栅栏中木板的数量和 Monocarp 拥有的不同颜色油漆的数量。

第二行包含 m 个整数 a1,a2,…,am ( 1≤ai≤n ),其中 ai 是可以涂上颜色为 i 的油漆的最大木板数。

所有测试用例的 n之和不超过 2⋅1e5。所有测试用例中 m 的总和不超过 2⋅1e5 。

输出

对于每个测试用例,输出满足 Monocarp 所描述的所有愿望的不同栅栏喷漆方法的数量。

Example

Input

Copy

3
5 2
2 4
5 2
3 4
12 3
5 9 8

Output

Copy

4
6
22

在第一个测试案例中,栅栏有 44 种不同的涂色方法(下面列出了从左到右可涂色木板的色号序列):

  1. [1,2,2,2,2]
  2. [1,1,2,2,2]
  3. [2,2,2,1,1]
  4. [2,2,2,2,1]

在第二个测试案例中,有 66 种不同的方法来粉刷栅栏(下面列出了从左到右粉刷木板的色号序列):

  1. [1,2,2,2,2]
  2. [1,1,2,2,2]
  3. [1,1,1,2,2]
  4. [2,2,1,1,1]
  5. [2,2,2,1,1]
  6. [2,2,2,2,1]

分析

涂满长度为n的围栏, 只能用两种颜色

注意到题目要求必须使用到两种颜色, 所以对于a[i]==n, 必须先 -1 . (假如这颜色能涂n个木板, 实际最多也只能涂n-1个)

首先可以先对每种颜色可以涂的围栏数量,也就是a[]数组进行排序

从小到大遍历a数组, 对于每个a[i], 寻找能配对的颜色

什么才能配对呢?

当然是 选涂的木板的数量+a[i]>=n 的才可以

对每个符合的配对颜色 答案ans可以加上 ( a[i] - ( n - a[j] ) + 1 ) * 2

(如果不明白这步可以随便选一个长度n,和一对符合的颜色对来试着计算一下发现规律)

现在就可以很朴素地用双指针解决问题......了吗?

 rep(i,1,m-1){
     for(int j=m;j>i;j--){
         if(a[i]+a[j]<n)break;
         ans+=(a[i]+a[j]-n+1)*2;
     }
 }

这会导致超时

注意到那么对里面那层循环分析, 假设循环了k次, 那么ans就加上了k个a[i] , a[m-k+1] 到 a[m] 的和, k个n, k个1, 这些的和乘2

那么就可以考虑用二分找到k, 再用前缀和O(1) 地计算a[m-k+1] 到 a[m] 的和了

前缀和可以优化循环的公式累加, 这或许是个常用的技巧

ac代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define all(x) (x).begin(),(x).end()
#define endl '\n'
#define int long long

const int N=2e5+5;

int a[N];
int pre[N];
int n,m;

void solve(){
    int n,m;cin>>n>>m;
    rep(i,1,m){
        cin>>a[i];
        if(a[i]==n)a[i]-=1;// 处理a[i]==n
    }

    sort(a+1,a+1+m);

    rep(i,1,m)pre[i]=pre[i-1]+a[i];

    int ans=0;

    rep(i,1,m-1){
        // for(int j=m;j>i;j--){ //朴素的双指针解法: 超时
        //     if(a[i]+a[j]<n)break;
        //     ans+=(a[i]+a[j]-n+1)*2;
        // }
        
        // 二分查找从左往右第一个符合与a[i] 能配对的颜色,后面当然都可以配对啦
        int l=i,r=m+1;
        while(l+1!=r){
            int mid=l+r>>1;
            if(a[mid]+a[i]<n)l=mid;
            else r=mid;
        }
        // r
        if(r==m+1)continue;// 说明对于当前a[i],即使找能涂最多的木板的颜色也不能涂完所有的木板
        
        int cnt=m-r+1;
        ans+= (a[i]*cnt+pre[m]-pre[r-1]-n*cnt+cnt)*2; 
    }
    cout<<ans<<endl;
}

signed main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _;cin>>_;while(_--)
    solve();
    return 0;
}

题目来自 Educational Codeforces Round 176 (Rated for Div. 2) C. Two Colors

posted @ 2025-03-19 19:45  byxxx  阅读(21)  评论(0)    收藏  举报