1 条题解

  • 0
    @ 2026-5-7 0:11:19

    Blog

    简单思维题,但我没场切,抱抱我真堂。

    注意到 xx 可以选任意数,所以对一个区间加 xx 的操作可以等效于将这个区间里的数与区间外的数区分开来,因为 xx 可以直接拉到一个很大的数,下一次操作可以拉到更大的数,以此类推。

    因为把这个区间与其他区间分隔开了,所以相当于把这个序列切成了三段。而对于后续的区间加也是同理,会在两个端点所在的块内把序列分割。这是本题最关键的性质。由此可以得到,如果进行了 xx 次分割,那么至少要进行 x2\left \lceil \dfrac{x}{2} \right \rceil 次区间加操作,因为每次区间加会导致两次分割。

    接下来考虑利用这个性质,直接对最后形成的序列进行分析,在把这个序列分割成多个段后,如果某个段内仍然存在相同的数字,显然是无法满足要求的。注意第一段和最后一段是特殊的,因为我们分割序列的时候区间加的都是中间段,所以第一段和最后一段是不需要进行区间加的,但是需要满足第一段和最后一段合起来每个数字均出现一次。

    于是问题被转化为:对一个序列进行分割,使得每一段内均无重复数字出现,且第一段和最后一段需要特殊考虑。求最小分割次数。

    首先可以观察到一个单调性:如果一段的左端点已确定,那么当右端点最右的时候一定是最优的。因此,在左端点确定的时候,我们能够确定的段是唯一的,因为这个段就是左端点和最大右端点所组成的段。为了求出最大右端点,可以根据单调性用一个双指针unordered_map 进行维护。

    剩下的 DP 是显然的,对于中间段,可以直接找到最大右端点进行转移;而对于首尾段,DP 的过程中显然不可以多记录一维,而前后要保证无重复元素,可以把他们接成同一个段。因此考虑“断环为链”的 trick,把数组复制一倍,如果此时的右端点跳出了 nn 就直接停止,根据左端点判断是否计入答案即可。

    这一部分可以用倍增或者直接递推实现。如果直接递推即可做到时间复杂度 O(n)O(n)

    #include <bits/stdc++.h>
    #define fi first
    #define se second
    #define eb(x) emplace_back(x)
    #define pb(x) push_back(x)
    #define lc(x) (tr[x].ls)
    #define rc(x) (tr[x].rs)
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef long double ldb;
    using pi=pair<int,int>;
    const int N = 400005, inf = 0x3f3f3f3f;
    int n, a[N], to[N], dp[N], ans = inf;
    unordered_map<int, int> tot;
    int main()
    {
        //freopen("sample.in","r",stdin);
        //freopen("sample.out","w",stdout);
        ios::sync_with_stdio(0);
        cin.tie(0);
        cout.tie(0);
        cin >> n;
        for(int i = 1; i <= n; i++)
        {
            cin >> a[i];
            a[i + n] = a[i];
            to[i + n] = i + n - 1;
        }
        int p = 1;
        for(int i = 1; i <= n; i++)
        {
            while(tot[a[p]] == 0)
            {
                tot[a[p]]++;
                p++;
            }
            to[i] = p;
            tot[a[i]]--;
        }
        if(to[1] > n)
        {
            cout << 0;
            return 0;
        }
        for(int i = n; i >= 1; i--)
        {
            dp[i] = dp[to[i]] + 1;
            to[i] = to[to[i]];
        }
        for(int i = 1; i <= n; i++)
            if(to[i] >= i + n - 1)
                ans = min(ans, dp[i]);
        cout << (ans + 1) / 2;
        return 0;
    }
    
    • 1

    「UOI 2023 Stage 4 Day1」数组与区间加法

    信息

    ID
    10966
    时间
    2000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者