Loading

ZR 25 noip D3T2 题解 | 构造、数学

传送门

标签:构造、数学

题意

给你一个长为 \(2 \times n\) 的数列 \(a\),满足 \(\forall i \in [1, 2 \times n], a_i \in [0, n]\)

求一个区间,可以将区间中的数划分到两个集合,满足两个集合中数的和相同。

思路

考虑分析题目研究的组合对象,是一个区间。
考虑这个操作的数学刻画,就是给区间 \([l,r]\) 中的数赋一个权值 \(-1\)\(+1\),然后求一个和为 \(0\) 的区间。

此时,先别管赋权值,将区间和转换为前缀和,就是找到两个相等的前缀和。

思考,存在两个相等的对象,可以让我们联想到抽屉原理。
我们有 \(2 \times n + 1\) 个前缀和,也就是说我们要前缀和的值域在 \(2 \times n\) 种中才可以。

我们考虑将前缀和压在值域 \((-n,n]\) 中,显然这样就满足条件了。

这时,再回去看赋权值的事情,我们每次这样更新 \(sum\)

\[sum \leftarrow \begin{cases} sum + a_i, & sum \le 0 \\ sum - a_i, & sum \gt 0 \end{cases} \]

然后就做完了。
确实是很有意思的一道题。
赛时是没用想到赋权值,想到之后的转换其实是很自然的。
然后联想到抽屉原理又是一个思维难点。
总之,不要一直想着什么代码和数据结构的事情,要专心研究数学对象,怎么维护是后面的事情,思维不能跳来跳去。

代码

link

const int N = 2e6 + 5;
int n;
int a[N];
int sum[N];
int id[N * 2];
void solve_test_case(){
    n = read();
    rep(i, 1, 2 * n) a[i] = read();
    memset(id, -1, sizeof(id));
    id[n] = 0;
    int idl = 0, idr = 0;
    rep(i, 1, 2 * n){
        if(sum[i - 1] > 0) sum[i] = sum[i - 1] - a[i];
        else sum[i] = sum[i - 1] + a[i];
        if(~id[sum[i] + n]){
            idl = id[sum[i] + n] + 1;
            idr = i;
            break;
        }
        id[sum[i] + n] = i;
    }
    write(idl, ' '), write(idr);
    int tmp = sum[idl - 1];
    rep(i, idl, idr){
        if(tmp > 0){
            tmp -= a[i];
            putchar('0');
        }else{
            tmp += a[i];
            putchar('1');
        }
    }
}
posted @ 2025-09-14 20:23  lajishift  阅读(12)  评论(0)    收藏  举报