第五届新疆省赛 L最优子区间
题目描述
给长度为 n 的序列 a[],一个区间的得分为这个区间内有多少种元素恰出现一次。输出得分最大的区间,得分为多少。
分析
- 如果暴力枚举所有区间那么是N^2,需要考虑如何优化,可以根据区间得分的特点去思考。
- 设dp[l][r-1]为区间l~r-1的得分,那么dp[l][r]的得分有3种情况
d
p
[
l
]
[
r
]
=
d
p
[
l
]
[
r
−
1
]
+
1
,
第
r
个
数
没
有
在
l
∼
r
−
1
出
现
过
dp[l][r] = dp[l][r-1] + 1,第 r 个数没有在l \sim r - 1出现过
dp[l][r]=dp[l][r−1]+1,第r个数没有在l∼r−1出现过
d
p
[
l
]
[
r
]
=
d
p
[
l
]
[
r
−
1
]
−
1
,
第
r
个
数
恰
好
在
l
∼
r
−
1
出
现
过
一
次
dp[l][r] = dp[l][r - 1] - 1, 第 r 个数恰好在l \sim r - 1出现过一次
dp[l][r]=dp[l][r−1]−1,第r个数恰好在l∼r−1出现过一次
d
p
[
l
]
[
r
]
=
d
p
[
l
]
[
r
−
1
]
,
第
r
个
数
恰
好
在
l
∼
r
−
1
至
少
出
现
了
2
次
dp[l][r] = dp[l][r - 1] , 第 r 个数恰好在l \sim r - 1至少出现了2次
dp[l][r]=dp[l][r−1],第r个数恰好在l∼r−1至少出现了2次
根据这个特点,不难发现只要记录第r个数上一次出现位置就可以知道新的dp[l][r] 的值,
d
p
[
l
]
[
r
]
=
d
p
[
l
]
[
r
−
1
]
+
1
,
第
r
个
数
上
一
次
出
现
位
置
+
1
≤
l
≤
r
dp[l][r] = dp[l][r-1] + 1, 第r个数上一次出现位置 + 1\leq l \leq r
dp[l][r]=dp[l][r−1]+1,第r个数上一次出现位置+1≤l≤r
d
p
[
l
]
[
r
]
=
d
p
[
l
]
[
r
−
1
]
−
1
,
第
r
个
数
上
上
次
出
现
位
置
+
1
≤
l
≤
第
r
个
数
上
一
次
出
现
位
置
dp[l][r] = dp[l][r - 1] - 1, 第r个数上上次出现位置 + 1\leq l \leq 第r个数上一次出现位置
dp[l][r]=dp[l][r−1]−1,第r个数上上次出现位置+1≤l≤第r个数上一次出现位置
d
p
[
l
]
[
r
]
=
d
p
[
l
]
[
r
−
1
]
,
l
≤
第
r
个
数
上
上
次
出
现
位
置
dp[l][r] = dp[l][r-1] , l \leq第r个数上上次出现位置
dp[l][r]=dp[l][r−1],l≤第r个数上上次出现位置
举个例子
区间 1 2 3 1 2 3 1 2 3
下标 1 2 3 4 5 6 7 8 9
那么dp[l][9]如下
d
p
[
l
]
[
9
]
=
d
p
[
l
]
[
8
]
+
1
,
7
≤
l
≤
9
dp[l][9] = dp[l][8] + 1, 7 \leq l \leq 9
dp[l][9]=dp[l][8]+1,7≤l≤9
d
p
[
l
]
[
9
]
=
d
p
[
l
]
[
r
]
−
1
,
3
+
1
<
=
l
<
=
6
dp[l][9] = dp[l][r] - 1, 3 + 1<= l <= 6
dp[l][9]=dp[l][r]−1,3+1<=l<=6
d
p
[
l
]
[
9
]
=
d
p
[
l
]
[
r
]
,
l
<
=
3
dp[l][9] = dp[l][r] , l <= 3
dp[l][9]=dp[l][r],l<=3
- 这个状态转移可以通过线段树来做,定义t[i].l ~ t[i].r 为区间左端点在 t[i].l ~ t[i].r ,右端点在i的所有区间的得分值,用last[i]记录上一个第i个数出现的位置,plast[i]记录上上个第i个数出现的位置,i从左到右遍历,每次转移后,求下区间最大得分即可。
code
#include <bits/stdc++.h>
using namespace std;
#define IOS std::ios_base::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);// 快读
typedef long long ll;
const int MAXN = 1e5 + 10;
struct tnode
{
int l, r;
ll lzy,ma;
};
ll c[MAXN], w[MAXN];
struct Segment_Tree
{
tnode t[MAXN << 2] = {};
int leaf[MAXN] = {};
inline void update(int i) //用子区间更新当前的区间
{
t[i].ma = max(t[i << 1].ma, t[i << 1 | 1].ma);
}
void inline init_lzy(int i) //初始化懒标记
{
t[i].ma = t[i].lzy = 0;
}
void inline build(int i, int l,int r ) // 建树
{
t[i].l = l;
t[i].r = r;
init_lzy(i); //important
if (l == r)
{
leaf[t[i].l] = i;
return;
}
int m = (l + r) >> 1;
build(i << 1, l, m);
build(i << 1 | 1, m + 1, r);
//update(i);
}
void push_down(int i){
if(t[i].lzy){
ll lz = t[i].lzy;
t[i << 1].ma += lz;
t[i << 1].lzy += lz;
t[i << 1 | 1].ma += lz;
t[i << 1 | 1].lzy += lz;
t[i].lzy = 0;
}
}
inline void add(int i, int l, int r, ll b)
{
if (t[i].l >= l && t[i].r <= r)
{
t[i].ma += b;
t[i].lzy += b;
return ;
}
push_down(i);
if (t[i << 1].r >= l)
add(i << 1, l, r, b);
if (t[i << 1 | 1].l <= r)
add(i << 1 | 1, l, r, b);
update(i);
}
inline ll search_sqares(int i, int l, int r)
{
if (l <= t[i].l && t[i].r <= r)
{
return t[i].ma;
}
push_down(i);
ll res = 0;
if (t[i << 1].r >= l)
res = search_sqares(i << 1, l, r);
if (t[i << 1 | 1].l <= r)
res = max( res,search_sqares(i << 1 | 1, l, r) );
return res;
}
void check_leaf(int sz)
{ //查询叶子是否正确
for (int i = 1; i <= sz; ++i)
if (t[i].l == t[i].r && t[i].l)
cout << t[i].l << " " << t[i].r << " " << t[i].ma << '\n';
}
void check_section(int sz, int l, int r)
{ //查询区间是否正确
for (int i = 1; i < sz; ++i)
{
if (l <= t[i].l && t[i].r <= r)
{
cout << t[i].l << ' ' << t[i].r << '\n';
}
}
}
};
Segment_Tree ST;
int last[MAXN], plast[MAXN];
int main(){
int n;
while(cin >> n){
for(int i = 1; i <= n; ++i)
cin >> c[i];
ST.build(1,1,n);
memset(last,0,sizeof last);
memset(plast,0,sizeof plast);
ll ans = 0;
for(int i = 1; i <= n; ++i){
ST.add(1,last[c[i]] + 1,i,1);
if(last[c[i]])
ST.add(1,plast[c[i]] + 1,last[c[i]],-1);
// cout << last[c[i]] + 1 << " " << i << '\n';
// cout << plast[c[i]] + 1 << " " << last[c[i]] << '\n';
plast[c[i]] = last[c[i]];
last[c[i]] = i;
ans = max(ans,ST.search_sqares(1,1,n));
}
cout << ans << '\n';
}
}
方块 III
一道基本一样的题目
进阶题
E. Partition Game
题目描述
You are given an array a of n integers. Define the cost of some array t as follows:
cost(t)=∑x∈set(t)last(x)−first(x),
where set(t) is the set of all values in t without repetitions, first(x), and last(x) are the indices of the first and last occurrence of x in t, respectively. In other words, we compute the distance between the first and last occurrences for each distinct element and sum them up.
You need to split the array a into k consecutive segments such that each element of a belongs to exactly one segment and the sum of the cost of individual segments is minimum.
思路
题意很像,就是更复杂了
- 先试着把普通的动态规划代码写出来,在用线段树维护状态转移
dp[k][l][r];// 划分到第k块,第k块,第k块长度为l ~ r
for (int i = 1; i <= k; ++i)
{
memset(last, 0x3f, sizeof last);
for (int j = i; j <= n - (k - i); ++j)
{
dp[i][j][j] = min(dp[i - 1][...][j - 1]);// i - 1 <= ... <= j - 1
}
for (int l = i; l <= n - (k - i); ++l)
{
for (int r = l; r <= n - (k - i); ++r)
{
dp[i][l][r] += max(r - last[x[r]], 0);
last[x[r]] = r;
}
}
}
-
然后可以用线段树进行维护状态转移,可以开个大小为2的线段树数组,滚动一下。
① 下面代码中 当前块继承前面块的答案,可以放在枚举区间左右端点的时候做
for (int j = i; j <= n - (k - i); ++j)
{
dp[i][j][j] = min(dp[i - 1][...][j - 1]);// i - 1 <= ... <= j - 1
}
②下面代码 枚举块的左右端点可以用线段树做。
for (int l = i; l <= n - (k - i); ++l)
{
for (int r = l; r <= n - (k - i); ++r)
{
dp[i][l][r] += max(r - last[x[r]], 0);
last[x[r]] = r;
}
}
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 35020,inf = 1e9;
struct tnode
{
ll l, r,lzy,mi;// mi 区间最小值
};
ll last[MAXN],a[MAXN];
struct Segment_Tree
{
tnode t[MAXN << 2] = {};
int leaf[MAXN] = {};// 存叶子结点在t数组的下标
inline void update(int i)//用子区间更新当前的区间
{
t[i].mi = min(t[i << 1].mi,t[i << 1 | 1].mi);
}
inline void renew(int i)//i为叶子结点下标,当修改叶子结点后,从叶子结点更新父区间直到根节点
{
while (i >>1 )
{
i >>= 1;
t[i].mi = min(t[i << 1].mi , t[i << 1 | 1].mi);
}
}
void inline init_lzy(int i)//初始化懒标记等
{
t[i].lzy = 0;
t[i].mi = 0;
}
void inline build(int i, ll l, ll r)// 建树
{
t[i].l = l;
t[i].r = r;
init_lzy(i); //important
if (l == r)
{
leaf[t[i].l] = i;
return;
}
ll m = (l + r) >> 1;
build(i << 1, l, m);
build(i << 1 | 1, m + 1, r);
update(i);
}
inline void push_down(int i)
{
if(t[i].lzy){
t[i << 1].lzy += t[i].lzy;
t[i << 1].mi += t[i].lzy;
t[i << 1 | 1].lzy += t[i].lzy;
t[i << 1 | 1].mi += t[i].lzy;
t[i].lzy = 0;
}
}
inline void add(int i, ll l, ll r, ll b)
{
if(b <= 0) return ;
if (t[i].l >= l && t[i].r <= r)
{
t[i].mi += b;
t[i].lzy += b;
return ;
}
push_down(i);
if (t[i << 1].r >= l)
add(i << 1, l, r, b);
if (t[i << 1 | 1].l <= r)
add(i << 1 | 1, l, r, b);
update(i);
}
inline ll search_sqares(int i, int l, int r)// 区间查找
{
if (l <= t[i].l && t[i].r <= r)
{
return t[i].mi;
}
push_down(i);
ll x = inf;
if (t[i << 1].r >= l)
x = search_sqares(i << 1, l, r);
if (t[i << 1 | 1].l <= r)
x = min(x,search_sqares(i << 1 | 1, l, r) );
/* 合并两个区间得到的答案后在返回 */
return x;
}
void check_leaf(int sz){//查询叶子是否正确
for(int i = 1; i <= sz; ++i)
if(t[i].l == t[i].r && t[i].l)
cout << t[i].l << " " << t[i].r << " " << t[i].mi << '\n';
}
void check_section(int sz,int l,int r){//查询区间是否正确
for(int i = 1; i < sz; ++i){
if(l <= t[i].l && t[i].r <= r){
cout << t[i].l << ' ' << t[i].r << '\n';
}
}
}
};
Segment_Tree ST[2];
int n,k;
int main()
{
cin >> n >> k;
ST[0].build(1,1,n); ST[1].build(1,1,n);
memset(last,0x3f,sizeof last);
for(int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= k; ++i)
{
// for(int j = i; j <= n - (k - i); ++j){
// dp[k][j + 1][j] = min(dp[k - 1][][]); search(1,i - 1,j);
// }// min( dp[k - 1][...][j] )
// for (int l = i; l <= n - (k - i); ++l)
// {
// for (int r = l; r <= n - (k - i); ++r)
// {
// dp[k][l][r] += max(r - last[x[r]], 0);
// last[x[r]] = r;
//
// }
// }
for(int r = i; r <= n - (k - i); ++r){
//if(i == 2)
//cout << r << " " << i << " " << last[a[r]] << " " << r - last[a[r]] << '\n';
ST[i&1].add( 1,i,last[a[r]], r - last[a[r]] );// 1 <= 第i个区间左端的 <= n - (k - i)
int id = ST[ (i + 1) & 1].leaf[min(r + 1,n)];// 第i + 1 个区间左端的r + 1 的位置,最后会越n的边界
// 第i + 1个区间如果在r + 1 开始则要继承从 1 ~ r 分完前 i 个区间后的最小值,特判,如果是第2个区间那么要继承的第1个区间的左端的必须在 1
ST[ (i + 1) & 1].t[id].mi = i != 1 ? ST[i&1].search_sqares(1,i,r) : ST[i&1].search_sqares(1,i,i);
ST[ (i + 1)& 1].renew(id); //获取后从叶子结点向上更新
last[a[r]] = r;
}
memset(last,0x3f,sizeof last);
// 必须重新建树,相当于初始化,虽然吧结点的mi改了,但是里面的lzy 标签还在,会影响下次add
if(i!=k)
ST[i&1].build(1, 1, n+1);
}
if(k == 1)// 如果只有一个区间,需要特判选择左端点从1开始
cout << ST[k&1].search_sqares(1,1,1);
else cout << ST[k&1].search_sqares(1,k,n);
return 0;
}
//
// 1 ~ last[x] += (r - last[x]);
// last[x] = r;