音游家的谱面(Hard version)
一道稍微有点难度的单调队列+性质优化题。有两种情况分类讨论用不同的优化方式,强烈建议写一下
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
using namespace std;
const int maxn = 5e3+10;
const ll inf = 1e18;
template <typename _tp>
inline void read(_tp& x) {
char ch = getchar(), sgn = 0;
while (ch ^ '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') ch = getchar(), sgn = 1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
if (sgn) x = -x;
}
int n, m, pos[maxn];
int f[maxn][maxn], pre[maxn][maxn];
pii mn[maxn];
pii que[maxn];
int res[maxn];
int L[maxn], R[maxn];
pii checkmin(pii a, pii b){
if(a.first < b.first) return a;
return b;
}
void dfs(int cur, int x){
res[cur] = f[cur][x];
if(cur == 1) return;
dfs(cur-1, pre[cur][x]);
}
signed main(){
read(n), read(m);
rep(i, 1, m) read(pos[i]);
rep(i, 1, n){
f[1][i] = min(max(pos[1]-1, n-i), max(i-1, n-pos[1]));
//debug(f[1][i]);
}
mn[0] = make_pair(inf, -1);
mn[n+1] = make_pair(inf, -1);
rep(i, 2, m){
int tmp = abs(pos[i] - pos[i-1]);
int head = 1, tail = 0;
int len = 1;
rep(j, 1, n) f[i][j] = inf;
rep(j, 1, n){
int l = max(j - tmp, 1ll), r = min(j + tmp, n);
while(len <= n && len <= r){
while(tail - head + 1 > 0 && que[tail].first >= f[i-1][len] + tmp) tail--;
que[++tail] = make_pair(f[i-1][len] + tmp, len);
++len;
}
while(tail - head + 1 > 0 && que[head].second < l) head++;
if(tail - head + 1 > 0){
if(f[i][j] > que[head].first){
f[i][j] = que[head].first;
pre[i][j] = que[head].second;
}
}
}
rep(j, 1, n) mn[j] = make_pair(inf, -1);
rep(j, 1, n){
int tp = abs(pos[i] - j);
int l = max(pos[i-1] - tp, 1ll), r = min(pos[i-1] + tp, n);
mn[l] = min(make_pair(f[i-1][j] + tp, j), mn[l]);
mn[r] = min(make_pair(f[i-1][j] + tp, j), mn[r]);
}
rep(j, 1, pos[i-1]) mn[j] = min(mn[j], mn[j-1]);
dep(j, n, pos[i-1]) mn[j] = min(mn[j], mn[j+1]);
rep(j, 1, n){
if(f[i][j] > mn[j].first){
f[i][j] = mn[j].first;
pre[i][j] = mn[j].second;
}
//debug(f[i][j]);
}
//ct
}
int ans = inf, index;
rep(i, 1, n) {
//debug(f[m][i]);
if(ans > f[m][i]){
ans = f[m][i];
index = i;
}
}
dfs(m, index);
rep(i, 1, m) cout << res[i] << " ";
return 0;
}
对于每个点
j
j
j 用双指针预处理出满足条件的情况下能到最左边的下标
L
[
j
]
L[j]
L[j]
对于每个区间单独处理,处理分第
i
i
i 个区间,用分
i
−
1
i-1
i−1 个区间的状态进行转移。
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
L
[
j
]
−
1
]
+
j
−
L
[
j
]
+
1
)
f[i][j] =max(f[i][j-1], f[i-1][L[j]-1]+j-L[j]+1)
f[i][j]=max(f[i][j−1],f[i−1][L[j]−1]+j−L[j]+1)
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示分前
i
i
i 个区间字符串前
j
j
j 个最长长度
很显然,其实从
k
∈
[
L
[
j
]
,
j
−
1
]
k\in[L[j], \ j-1]
k∈[L[j], j−1] 都可以转移到
j
j
j 这个状态
假设,
k
′
∈
[
L
[
j
]
,
j
−
1
]
k^{\prime}\in[L[j], \ j-1]
k′∈[L[j], j−1], 此时
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
k
′
−
1
]
+
j
−
k
′
+
1
f[i][j] = f[i-1][k^{\prime}-1]+j-k^{\prime}+1
f[i][j]=f[i−1][k′−1]+j−k′+1
假设
L
[
L
[
j
−
1
]
]
=
=
1
L[L[j-1]] == 1
L[L[j−1]]==1 那么此时
m
a
x
(
f
[
i
]
[
j
]
)
=
=
j
max(f[i][j])==j
max(f[i][j])==j
此时,
L
[
k
′
]
<
L
[
j
]
f
[
i
−
1
]
[
L
[
k
′
]
−
1
]
=
L
[
k
′
]
−
1
L[k^{\prime}]< L[j] \ \ \ f[i-1][L[k^{\prime}]-1] = L[k^{\prime}]-1
L[k′]<L[j] f[i−1][L[k′]−1]=L[k′]−1
所以
f
[
i
−
1
]
[
k
′
−
1
]
+
j
−
k
′
+
1
≤
f
[
i
−
1
]
[
L
[
j
]
−
1
]
+
j
−
L
[
j
]
+
1
f[i-1][k^{\prime}-1]+j-k^{\prime}+1 \leq f[i-1][L[j]-1]+j-L[j]+1
f[i−1][k′−1]+j−k′+1≤f[i−1][L[j]−1]+j−L[j]+1
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
using namespace std;
const int maxn = 1e7+10;
const ll inf = 1e18;
template <typename _tp>
inline void read(_tp& x) {
char ch = getchar(), sgn = 0;
while (ch ^ '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') ch = getchar(), sgn = 1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
if (sgn) x = -x;
}
char s[maxn];
int f[2][maxn];
int cnt[30];
int L[maxn];
signed main(){
int n, m;
read(n), read(m);
scanf("%s", s+1);
int t = 0;
int l = 1;
rep(i, 1, n){
int c = s[i] - 'a';
cnt[c]++;
while(cnt[c] > m){
cnt[s[l] - 'a']--;
++l;
}
L[i] = l;
}
rep(i, 1, 3){
t ^= 1;
rep(j, 1, n){
f[t][j] = max(f[t][j-1], f[t^1][L[j]-1] + j - L[j] + 1);
}
}
cout << f[t][n];
return 0;
}
AtCoder Beginner Contest 207 E - Mod i
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前
i
i
i 个数分
j
j
j 组的情况数
很显然能得出这个式子,
f
[
i
]
[
j
]
+
=
f
[
k
]
[
j
−
1
]
×
(
(
∑
l
=
k
+
1
i
a
[
l
]
)
m
o
d
j
=
=
0
)
f[i][j] \mathrel{+}= f[k][j-1]\times ((\displaystyle\sum^{i}_{l=k+1}{a[l}])\ mod\ j==0)
f[i][j]+=f[k][j−1]×((l=k+1∑ia[l]) mod j==0)
取一个前缀和就变成了
f
[
i
]
[
j
]
+
=
f
[
k
]
[
j
−
1
]
×
(
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
m
o
d
j
=
=
0
)
f[i][j] \mathrel{+}= f[k][j-1]\times ((sum[i]-sum[k])\ mod\ j==0)
f[i][j]+=f[k][j−1]×((sum[i]−sum[k]) mod j==0) 很明显这个式子的复杂度是
O
(
n
3
)
O(n^3)
O(n3)
我们可以发现对于
(
s
u
m
[
i
]
−
s
u
m
[
k
]
)
m
o
d
j
=
=
0
(sum[i]-sum[k])\ mod\ j==0
(sum[i]−sum[k]) mod j==0 可以转换为
s
u
m
[
i
]
m
o
d
j
=
=
s
u
m
[
k
]
m
o
d
j
sum[i] \ mod\ j==sum[k] \ mod\ j
sum[i] mod j==sum[k] mod j 也就是说,对于
k
∈
[
1
,
i
−
1
]
k\in[1,\ i-1]
k∈[1, i−1],只要满足
s
u
m
[
i
]
m
o
d
j
=
=
s
u
m
[
k
]
m
o
d
j
sum[i] \ mod\ j==sum[k] \ mod\ j
sum[i] mod j==sum[k] mod j 就是满足条件的
问题就转换为对于当前的
a
[
i
]
a[i]
a[i] 要分
j
j
j 组只要找到满足条件的
k
k
k 使
s
u
m
[
i
]
m
o
d
j
=
=
s
u
m
[
k
]
m
o
d
j
sum[i] \ mod\ j==sum[k] \ mod\ j
sum[i] mod j==sum[k] mod j,这样我们只要维护一个
f
[
j
]
[
s
u
m
[
i
]
m
o
d
(
j
+
1
)
]
f[j][sum[i]\ mod\ (j+1)]
f[j][sum[i] mod (j+1)] 根据上面的定义,转移就变成了
f
[
j
]
[
s
u
m
[
i
]
m
o
d
(
j
+
1
)
]
=
f
[
j
]
[
s
u
m
[
i
]
m
o
d
j
]
f[j][sum[i]\ mod\ (j+1)] = f[j][sum[i]\ mod\ j]
f[j][sum[i] mod (j+1)]=f[j][sum[i] mod j]
有一个小细节,
d
p
dp
dp 数组维护的不是答案,所以答案统计的的时候只要求最后一次转移的总和就行了
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
#define mod 1000000007
using namespace std;
const int maxn = 3010;
const ll inf = 1e18;
template <typename _tp>
inline void read(_tp& x) {
char ch = getchar(), sgn = 0;
while (ch ^ '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') ch = getchar(), sgn = 1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
if (sgn) x = -x;
}
int f[maxn][maxn], a[maxn], sum[maxn];
signed main(){
//freopen("1.in", "r", stdin);
//freopen("1.out", "w", stdout);
int n;
read(n);
rep(i, 1, n) read(a[i]);
rep(i, 1, n) sum[i] = (sum[i-1] + a[i]);
int ans = 0;
f[0][0] = 1;
rep(i, 1, n){
dep(j, n, 1){
f[j][sum[i]%(j+1)] = (f[j][sum[i]%(j+1)] + f[j-1][sum[i]%j]) % mod;
if(i == n) ans += f[j-1][sum[i]%j], ans %= mod;
}
}
cout << ans << endl;
return 0;
}
Max Sum Counting
一道简单背包计数
O
(
500
0
3
)
O(5000^3)
O(50003) 的做法很好出,很显然,你确定完
m
a
x
(
a
[
i
]
)
max(a[i])
max(a[i]) 之后,你就不能取
a
[
j
]
>
a
[
i
]
a[j] > a[i]
a[j]>a[i] 对应的
b
[
j
]
b[j]
b[j] 了,那么对于
a
[
i
]
a[i]
a[i] 从大到小排个序,背包计数就行了
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
#define mod 998244353
using namespace std;
const int maxn = 1e6+10;
const ll inf = 1e18;
template <typename _tp>
inline void read(_tp& x) {
char ch = getchar(), sgn = 0;
while (ch ^ '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') ch = getchar(), sgn = 1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
if (sgn) x = -x;
}
pii a[maxn];
bool cmp(pii x, pii y){
return x.first > y.first;
}
int f[5001];
signed main(){
//freopen("1.in", "r", stdin);
//freopen("1.out", "w", stdout);
int n;
read(n);
rep(i, 1, n) read(a[i].first);
rep(i, 1, n) read(a[i].second);
sort(a + 1, a + 1 + n, cmp);
int ans = 0;
rep(i, 1, n){
if(a[i].first < a[i].second) continue;
int tmp = a[i].first - a[i].second;
ans = (ans + 1) % mod;
memset(f, 0, sizeof(f));
f[0] = 1;
rep(j, i+1, n){
dep(k, tmp, a[j].second){
f[k] += f[k - a[j].second];
f[k] %= mod;
}
}
rep(j, 1, tmp) ans = (ans + f[j]) % mod;
}
cout << ans;
return 0;
}
很显然会被T飞,那么考虑优化,对于每个 a [ i ] a[i] a[i] 来说,我要对 i + 1 − n i+1 - n i+1−n 重新跑一遍背包计数,这很费时间,如果从右往左推的话,把每个 b [ i ] b[i] b[i] 加入背包计数里面,只要跑一遍背包即可,复杂度 O ( 500 0 2 ) O(5000^2) O(50002)
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define pii pair<int, int>
#define rep(i, x, y) for(int i = x; i <= y; ++i)
#define dep(i, x, y) for(int i = x; i >= y; --i)
#define debug(x) cout << #x": " << x << endl;
#define ct cout << endl;
#define mod 998244353
using namespace std;
const int maxn = 1e6+10;
const ll inf = 1e18;
template <typename _tp>
inline void read(_tp& x) {
char ch = getchar(), sgn = 0;
while (ch ^ '-' && !isdigit(ch)) ch = getchar();
if (ch == '-') ch = getchar(), sgn = 1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
if (sgn) x = -x;
}
pii a[maxn];
bool cmp(pii x, pii y){
return x.first > y.first;
}
int f[5010];
signed main(){
//freopen("1.in", "r", stdin);
//freopen("1.out", "w", stdout);
int n;
read(n);
rep(i, 1, n) read(a[i].first);
rep(i, 1, n) read(a[i].second);
sort(a + 1, a + 1 + n, cmp);
int ans = 0;
f[0] = 1;
dep(i, n, 1){
if(a[i].first >= a[i].second) ans = (ans + 1) % mod;
int tmp = a[i].first - a[i].second;
if(tmp >= 1) rep(j, 1, tmp) ans = (ans + f[j]) % mod;
dep(j, 5000, a[i].second) f[j] = (f[j] + f[j - a[i].second]) % mod;
}
// rep(i, 1, n){
// if(a[i].first < a[i].second) continue;
// int tmp = a[i].first - a[i].second;
// ans = (ans + 1) % mod;
// memset(f, 0, sizeof(f));
// f[0] = 1;
// rep(j, i+1, n){
// dep(k, tmp, a[j].second){
// f[k] += f[k - a[j].second];
// f[k] %= mod;
// }
// }
// rep(j, 1, tmp) ans = (ans + f[j]) % mod;
// }
cout << ans;
return 0;
}