1 条题解
-
0
简单思维题,但我没场切,抱抱我真堂。
注意到 可以选任意数,所以对一个区间加 的操作可以等效于将这个区间里的数与区间外的数区分开来,因为 可以直接拉到一个很大的数,下一次操作可以拉到更大的数,以此类推。
因为把这个区间与其他区间分隔开了,所以相当于把这个序列切成了三段。而对于后续的区间加也是同理,会在两个端点所在的块内把序列分割。这是本题最关键的性质。由此可以得到,如果进行了 次分割,那么至少要进行 次区间加操作,因为每次区间加会导致两次分割。
接下来考虑利用这个性质,直接对最后形成的序列进行分析,在把这个序列分割成多个段后,如果某个段内仍然存在相同的数字,显然是无法满足要求的。注意第一段和最后一段是特殊的,因为我们分割序列的时候区间加的都是中间段,所以第一段和最后一段是不需要进行区间加的,但是需要满足第一段和最后一段合起来每个数字均出现一次。
于是问题被转化为:对一个序列进行分割,使得每一段内均无重复数字出现,且第一段和最后一段需要特殊考虑。求最小分割次数。
首先可以观察到一个单调性:如果一段的左端点已确定,那么当右端点最右的时候一定是最优的。因此,在左端点确定的时候,我们能够确定的段是唯一的,因为这个段就是左端点和最大右端点所组成的段。为了求出最大右端点,可以根据单调性用一个双指针和
unordered_map进行维护。剩下的 DP 是显然的,对于中间段,可以直接找到最大右端点进行转移;而对于首尾段,DP 的过程中显然不可以多记录一维,而前后要保证无重复元素,可以把他们接成同一个段。因此考虑“断环为链”的 trick,把数组复制一倍,如果此时的右端点跳出了 就直接停止,根据左端点判断是否计入答案即可。
这一部分可以用倍增或者直接递推实现。如果直接递推即可做到时间复杂度 。
#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
信息
- ID
- 10966
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者