B - 外挂使用拒绝
思路:
设 dp[k][i] d p [ k ] [ i ] 为:操作 k k 天后第个账号还有的钱的数量, 那么有以下等式:
⎧⎩⎨⎪⎪⎪⎪⎪⎪dp[k][1]=dp[k−1][1]dp[k][2]=dp[k−1][2]+dp[k][1]......dp[k][n]=dp[k][n−1]+dp[k−1][n]
{
d
p
[
k
]
[
1
]
=
d
p
[
k
−
1
]
[
1
]
d
p
[
k
]
[
2
]
=
d
p
[
k
−
1
]
[
2
]
+
d
p
[
k
]
[
1
]
.
.
.
.
.
.
d
p
[
k
]
[
n
]
=
d
p
[
k
]
[
n
−
1
]
+
d
p
[
k
−
1
]
[
n
]
⇒
⇒
⎧⎩⎨⎪⎪⎪⎪⎪⎪dp[k−1][1]=dp[k][1]dp[k−1][2]=dp[k][2]−dp[k][1]......dp[k−1][n]=dp[k][n]−dp[k][n−1]
{
d
p
[
k
−
1
]
[
1
]
=
d
p
[
k
]
[
1
]
d
p
[
k
−
1
]
[
2
]
=
d
p
[
k
]
[
2
]
−
d
p
[
k
]
[
1
]
.
.
.
.
.
.
d
p
[
k
−
1
]
[
n
]
=
d
p
[
k
]
[
n
]
−
d
p
[
k
]
[
n
−
1
]
即:
⎡⎣⎢⎢⎢⎢⎢⎢dp[k−1][1]dp[k−1][2]dp[k−1][3]....dp[k−1][n]⎤⎦⎥⎥⎥⎥⎥⎥
[
d
p
[
k
−
1
]
[
1
]
d
p
[
k
−
1
]
[
2
]
d
p
[
k
−
1
]
[
3
]
.
.
.
.
d
p
[
k
−
1
]
[
n
]
]
=
⎡⎣⎢⎢⎢⎢⎢⎢1−10...001−1...0001...0...............000...−1000...1⎤⎦⎥⎥⎥⎥⎥⎥
[
1
0
0
.
.
.
0
0
−
1
1
0
.
.
.
0
0
0
−
1
1
.
.
.
0
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
0
0
0
.
.
.
−
1
1
]
⎡⎣⎢⎢⎢⎢⎢⎢dp[k][1]dp[k][2]dp[k][3]....dp[k][n]⎤⎦⎥⎥⎥⎥⎥⎥
[
d
p
[
k
]
[
1
]
d
p
[
k
]
[
2
]
d
p
[
k
]
[
3
]
.
.
.
.
d
p
[
k
]
[
n
]
]
那么未修改之前的就是:
=
⎡⎣⎢⎢⎢⎢⎢⎢1−10...001−1...0001...0...............000...−1000...1⎤⎦⎥⎥⎥⎥⎥⎥k
[
1
0
0
.
.
.
0
0
−
1
1
0
.
.
.
0
0
0
−
1
1
.
.
.
0
0
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
0
0
0
.
.
.
−
1
1
]
k
⎡⎣⎢⎢⎢⎢⎢⎢dp[k][1]dp[k][2]dp[k][3]....dp[k][n]⎤⎦⎥⎥⎥⎥⎥⎥
[
d
p
[
k
]
[
1
]
d
p
[
k
]
[
2
]
d
p
[
k
]
[
3
]
.
.
.
.
d
p
[
k
]
[
n
]
]
这个东西本来是矩阵快速幂解决,但是
n
n
到了,这样的话时间就是
O(n3logk)
O
(
n
3
log
k
)
,显然不行,那就打个表出来, 发现了
...
.
.
.
这个矩阵
k
k
次幂之后 =
(−1)i−j∗Ci−jk
(
−
1
)
i
−
j
∗
C
k
i
−
j
,这样从
(i,i)
(
i
,
i
)
这个位置往前推,但是组合数很大, 注意
Cxk
C
k
x
可以由
Cx−1k
C
k
x
−
1
递推而来。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e3 + 10;
const ll mod = 1e9 + 7;
using namespace std;
ll n, m, T, kase = 1, k;
ll ans[maxn], a[maxn], inv[maxn];
ll qmod(ll x, ll n, ll mod) {
ll ans = 1;
for( ; n; n >>= 1) {
if(n & 1) ans = ans * x % mod;
x = x * x % mod;
}
return ans;
}
int main() {
for(ll i = 1; i < maxn; i++) inv[i] = qmod(i, mod - 2, mod);
scanf("%d", &T);
while(T--){
scanf("%lld %lld", &n, &k);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(int i = 1; i <= n; i++) {
ll now = 1; ans[i] = a[i];
for(int j = i - 1; j >= 1; j--) {
int flag = (i - j) & 1;
int res = (i - j);
now = now * inv[res] % mod;
now = now * (k - res + 1) % mod;
if(flag) ans[i] -= now * a[j];
else ans[i] += now * a[j];
ans[i] %= mod;
}
}
for(int i = 1; i <= n; i++) {
if(ans[i] < 0) ans[i] +=mod;
printf("%lld%c", ans[i], i < n ? ' ' : '\n');
}
}
return 0;
}
D - 雷电爆裂之力
思路:
最多要经过四条街道, 设
dp[i][j]:
d
p
[
i
]
[
j
]
:
到达第
i
i
条街道中的这个位置时经过的最短距离,那么到达
j
j
这个位置,一种可以从北边向南面走来,一种可以从南面向北面走来,那么有以下两个状态转移方程:
从南面向北走到
j
j
:,其中要保证
k
k
这个位置连通第和第
i−1
i
−
1
条街道
2、
2
、
从北面向南走到
j
j
:,其中要保证
k
k
这个位置连通第和第
i−1
i
−
1
条街道
两次分别维护
dp[i−1][k][+k
d
p
[
i
−
1
]
[
k
]
[
+
k
和
dp[i−1][k]−k
d
p
[
i
−
1
]
[
k
]
−
k
前缀的最小值即可。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
const ll INF = 1e18;
using namespace std;
int n, m, T, kase = 1, k;
char s[maxn];
ll dp[5][maxn];
ll que[maxn * 4], b[maxn * 4], l, r;
ll a[5][maxn], tot[10];
ll c[5][maxn], d[5];
int main() {
scanf("%d", &T);
while(T--) {
for(int i = 0; i < 3; i++) cin >> tot[i];
int num = 0; tot[3] = 0;
for(int i = 0; i < 3; i++) {
d[i] = tot[i];
for(int j = 0; j < tot[i]; j++) {
cin >> a[i][j];
c[i][j] = a[i][j];
b[num++] = a[i][j];
}
sort(c[i], c[i] + d[i]);
c[i][d[i]] = INF;
}
for(int i = 3; i >= 1; i--) {
for(int j = 0; j < tot[i - 1]; j++) {
a[i][tot[i]] = a[i - 1][j];
tot[i]++;
}
}
for(int i = 0; i < 4; i++) {
sort(a[i], a[i] + tot[i]);
tot[i] = unique(a[i], a[i] + tot[i]) - a[i];
}
sort(b, b + num);
num = unique(b, b + num) - b;
ll ans = INF;
for(int i = 0; i < 5; i++) {
for(int j = 0; j < num; j++) dp[i][j] = INF;
}
for(int i = 0; i < tot[0]; i++) {
ll y = a[0][i];
ll idx = lower_bound(b, b + num, y) - b;
dp[0][idx] = 0;
}
for(int i = 1; i < 4; i++) {
///从南向北
ll m1 = INF, x = 0;
for(int j = 0; j < tot[i]; j++) {
int idx = lower_bound(b, b + num, a[i][j]) - b;
while(x < num && x <= idx) {
int rs = b[x], id =lower_bound(c[i - 1], c[i - 1] + d[i - 1], rs) - c[i - 1];
if(c[i - 1][id] != rs) { x++; continue; }
m1 = min(m1, dp[i - 1][x] - b[x]); x++;
}
dp[i][idx] = b[idx] + m1 + 1;
}
///从北向南
m1 = INF; x = num - 1;
for(int j = tot[i] - 1; j >= 0; j--) {
int idx = lower_bound(b, b + num, a[i][j]) - b;
while(x >= 0 && x >= idx) {
int rs = b[x], id = lower_bound(c[i - 1], c[i - 1] + d[i - 1], rs) - c[i - 1];
if(c[i - 1][id] != rs) { x--; continue; }
m1 = min(m1, dp[i - 1][x] + b[x]); x--;
}
dp[i][idx] = min(m1 + 1 - b[idx], dp[i][idx]);
}
}
for(int i = 0; i < num; i++) ans = min(ans, dp[3][i]);
cout << ans << endl;
}
return 0;
}
E - 可以来拯救吗
思路:
因为题目保证了 Ckn⩽100000 C n k ⩽ 100000 ,很显然可以去暴力搜索,要么 n n 很小, 要么很小, 因为 Ckn=Cn−kn C n k = C n n − k ,不能直接对 k k 去搜索, 因为可能很大,但是取了最小值之后就不会很大了,然后直接搜索就行了。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
const ll INF = 1e18;
using namespace std;
struct P {
int id, tot;
ll sum;
P() {}
P(int id, int t, ll s) :
id(id), tot(t), sum(s) {}
} res[maxn * 10];
int n, m, T, kase = 1, k;
char s[maxn];
ll a[maxn];
ll solve(int k, int flag, ll su) {
ll ans = 0, num = 0;
res[num++] = P(0, 0, 0);
while(num) {
P now = res[num - 1]; num--;
int id = now.id, t = now.tot;
ll s = now.sum;
if(t == k) {
if(flag == 1) ans = ans ^ (s * s);
else ans = ans ^ ((su - s) * (su - s));
continue;
}
if(t + n - id < k) continue;
res[num++] = P(id + 1, t, s);
res[num++] = P(id + 1, t + 1, s + a[id]);
}
return ans;
}
void dfs(int id, int k, int tot, ll sum, ll &ans) {
if(n - id + tot < k) return ;
if(tot == k) { ans = ans ^ (sum * sum); return ; }
dfs(id + 1, k, tot + 1, sum + a[id], ans);
dfs(id + 1, k, tot, sum, ans);
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d %d", &n, &k);
ll sum = 0;
for(int i = 0; i < n; i++) { scanf("%lld", &a[i]); sum += a[i]; }
int nxt = n - k;
ll ans;
if(nxt > k) ans = solve(k, 1, sum);
else ans = solve(nxt, -1, sum);
cout << ans << endl;
}
return 0;
}
F - 汤圆防漏理论
思路:
记初始状态第 i i 汤圆的粘度为,有序集合集合里面存入这样的信息:(汤圆的粘度和, 下标),关键字按粘度和排序,那么贪心的思想,为了不让汤圆粘漏,肯定先从粘度和小的粘,即取集合第一个元素,那么答案为每次取的该汤圆的粘度和的最大值。当该元素删除后,与它相连的元素的粘度也随之改变,那么需要先删除集合里现有的那些相连的元素,再插入更新后的情况,时间复杂度 O((n+m)logn). O ( ( n + m ) log n ) .
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
using namespace std;
int n, m, T, kase = 1;
ll sum[maxn], vis[maxn];
typedef pair<ll, ll> pa;
vector<pa> G[maxn];
set<pa> st;
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d %d", &n, &m);
for(int i = 0; i < maxn; i++) {
G[i].clear();
sum[i] = vis[i] = 0;
}
st.clear();
while(m--) {
ll x, y, z;
scanf("%lld %lld %lld", &x, &y, &z);
G[x].push_back(pa(y, z));
G[y].push_back(pa(x, z));
sum[x] += z; sum[y] += z;
}
for(ll i = 1; i <= n; i++) st.insert(pa(sum[i], i));
ll ans = 0;
while(!st.empty()) {
set<pa>::iterator it = st.begin();
pa now = *it; st.erase(it);
ans = max(ans, now.first);
ll x = now.second; vis[x] = 1;
for(int i = 0; i < G[x].size(); i++) {
pa nxt = G[x][i];
if(vis[nxt.first]) continue;
ll u = nxt.first;
it = st.lower_bound(pa(sum[u], u));
st.erase(it); sum[u] -= nxt.second;
st.insert(pa(sum[u], u));
}
}
cout << ans << endl;
}
return 0;
}
H - 吾好梦中做题
思路:
如果区间
[l,r]
[
l
,
r
]
是一段合法的括号序列, 那么一定会有对
∀i∈[l,r]
∀
i
∈
[
l
,
r
]
,左括号的数量
totl[i]⩾
t
o
t
l
[
i
]
⩾
右括号的数量
totr[i]
t
o
t
r
[
i
]
(自己意会一下), 那么我们记录
sl[i]:
s
l
[
i
]
:
区间
[1,i]
[
1
,
i
]
的左括号的数量,
sr[i]:
s
r
[
i
]
:
区间
[1,i]
[
1
,
i
]
的右括号的数量。
那么我们对于查询
x
x
这个位置的最长的合法序列的时候,我们就是要找到一个,使得对
∀i∈[x,y]
∀
i
∈
[
x
,
y
]
有:
sl[i]−sl[x−1]⩾sr[i]−sr[x−1]
s
l
[
i
]
−
s
l
[
x
−
1
]
⩾
s
r
[
i
]
−
s
r
[
x
−
1
]
且
sl[y]−sl[x−1]=sr[y]−sr[x−1]
s
l
[
y
]
−
s
l
[
x
−
1
]
=
s
r
[
y
]
−
s
r
[
x
−
1
]
,这样还不好处理,可以移项,得
sl[i]−sr[i]⩾sl[x−1]−sr[x−1]
s
l
[
i
]
−
s
r
[
i
]
⩾
s
l
[
x
−
1
]
−
s
r
[
x
−
1
]
且
sl[y]−sr[y]=sl[x−1]−sr[x−1]
s
l
[
y
]
−
s
r
[
y
]
=
s
l
[
x
−
1
]
−
s
r
[
x
−
1
]
,这样就变成了维护每个位置
i
i
的的值了,这个容易维护,因为
sl,sr
s
l
,
s
r
记录的是前缀值,如果第
i
i
个位置左括号变右括号,那么区间的值都加上
−2
−
2
,反之右括号变左括号就加上
2
2
,现在考虑怎么找。
根据分析知道,区间
[x,y]
[
x
,
y
]
中
sl−sr
s
l
−
s
r
的最小值就是
sl[x−1]−sr[x−1](记为sum)
s
l
[
x
−
1
]
−
s
r
[
x
−
1
]
(
记
为
s
u
m
)
且
sl[y]−sr[y]=sum
s
l
[
y
]
−
s
r
[
y
]
=
s
u
m
,线段树维护区间最小值,考虑怎么找
y
y
这个位置, 线段树中的区间
[l,r]
[
l
,
r
]
(节点标号
o
o
)完全包含在时候,区间从左往右考虑,假设该区间最小值是
min
m
i
n
,有三种情况:
- min>sum m i n > s u m :如果还没有遇到不合法的情况, 那么还可以往右边扩展,直接返回就行了。
- min=sum m i n = s u m :如果还没有遇到不合法的情况, 那么 y y 的位置至少在这里, 记录下线段树的节点,也是直接返回。
- min<sum m i n < s u m :出现了这种情况, 知道区间 [l,r] [ l , r ] 一定存在不合法的情况,但是这里要考虑左右两个孩子 o1,o2 o 1 , o 2 节点的最小值 r1,r2 r 1 , r 2 ,如果 r1=sum r 1 = s u m ,那么 fac_node=o1 f a c _ n o d e = o 1 ,最远可能在 o2 o 2 ,递归处理 o2 o 2 ;如果 r1<sum r 1 < s u m ,那么肯定最远只能在 o1 o 1 ,递归处理 o1 o 1 ;否则直接递归处理 o2 o 2 ( C[o]<sum C [ o ] < s u m 考虑完前面的情况后只能 r1>sum r 1 > s u m ,可能可以继续往右扩展)。
注意情况三不能递归处理两部分, 只能处理一部分,区间最多 logn log n 个节点, 完全包含的区间最多往下走 logn log n 步, 所以时间复杂度 O(nlog2n) O ( n log 2 n )
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 6e5 + 10;
const int INF = 1e9 + 10;
using namespace std;
int n, m, T, kase = 1;
char s[maxn];
int sl[maxn], sr[maxn];
int C[4 * maxn], laz[4 * maxn];
void build(int o, int l, int r) {
laz[o] = 0;
if(l == r) { C[o] = sl[l] - sr[l]; return ; }
int mid = (l + r) >> 1;
build(o << 1, l, mid);
build(o << 1 | 1, mid + 1, r);
C[o] = min(C[o << 1], C[o << 1 | 1]);
}
void push_down(int o) {
if(!laz[o]) return ;
int now = laz[o];
laz[o << 1] += now;
laz[o << 1 | 1] += now;
laz[o] = 0;
C[o << 1] += now; C[o << 1 | 1] += now;
}
void update(int o, int l, int r, int ql, int qr, int data) {
if(l > qr || r < ql) return ;
if(l >= ql && r <= qr) { laz[o] += data; C[o] += data; return ; }
push_down(o);
int mid = (l + r) >> 1;
update(o << 1, l, mid, ql, qr, data);
update(o << 1 | 1, mid + 1, r, ql, qr, data);
C[o] = min(C[o << 1], C[o << 1 | 1]);
}
int query_ans(int o, int l, int r, int ind) {
if(l == r) return C[o];
int mid = (l + r) >> 1;
push_down(o);
if(ind <= mid) return query_ans(o << 1, l, mid, ind);
else return query_ans(o << 1 | 1, mid + 1, r, ind);
}
int fac_node, l_node, r_node;
void query_max(int o, int l, int r, int ql, int qr, int sum, int &flag, int &ans) {
if(flag) return ;
int mid = (l + r) >> 1;
push_down(o);
if(l > qr || r < ql) return ;
if(l >= ql && r <= qr) {
if(C[o] > sum) return ; ///可以往右扩展
if(C[o] == sum) {
if(!flag) { fac_node = o; l_node = l; r_node = r; }
return ;
} ///最大可能所在的节点, 并且没有遇到不行的情况
if(flag) return ;
if(l == r) { if(C[o] < sum) flag = 1; return ; }
int r1 = C[o << 1], r2 = C[o << 1 | 1];
if(r1 == sum) { fac_node = o << 1; l_node = l; r_node = mid; query_max(o << 1 | 1, mid + 1, r, ql, qr, sum, flag, ans); }
else if(r1 < sum) query_max(o << 1, l, mid, ql, qr, sum, flag, ans);
else query_max(o << 1 | 1, mid + 1, r, ql, qr, sum, flag, ans);
if(C[o] < sum) flag = 1;
return ;
}
query_max(o << 1, l, mid, ql, qr, sum, flag, ans);
if(flag) return ;
query_max(o << 1 | 1, mid + 1, r, ql, qr, sum, flag, ans);
}
int query_idx(int o, int l, int r, int sum) {
if(l == r) return l;
push_down(o);
int mid = (l + r) >> 1, o1 = o << 1, o2 = o1 | 1;
if(C[o2] == sum) return query_idx(o2, mid + 1, r, sum);
else return query_idx(o1, l, mid, sum);
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d %d", &n, &m);
scanf("%s", s + 1);
for(int i = 1; i <= n; i++) {
if(s[i] == '(') { sl[i] = sl[i - 1] + 1; sr[i] = sr[i - 1]; }
else { sr[i] = sr[i - 1] + 1; sl[i] = sl[i - 1]; }
}
sl[n + 1] = INF; sr[n + 1] = 0;
build(1, 0, n + 1);
while(m--) {
int x, y; scanf("%d %d", &x, &y);
if(x == 1) {
if(s[y] == '(') { s[y] = ')'; update(1, 0, n + 1, y, n, -2); }
else { s[y] = '('; update(1, 0, n + 1, y, n, 2); }
} else {
if(s[y] == ')') { puts("0"); continue; }
int now = query_ans(1, 0, n + 1, y - 1);
int flag = 0, ans = n + 1;
fac_node = -1;
query_max(1, 0, n + 1, y, n + 1, now, flag, ans);
if(~fac_node) ans = query_idx(fac_node, l_node, r_node, now);
int tot = query_ans(1, 0, n + 1, ans);
if(tot == now) printf("%d\n", ans - y + 1);
else puts("0");
}
}
}
return 0;
}
K - 好学期来临吧
思路:
设
dp[i][0/1][j][k]:
d
p
[
i
]
[
0
/
1
]
[
j
]
[
k
]
:
只考虑前
i
i
个原先计划好的工作, 且第个工作不选/选, 已经插入了
j
j
个新添加的工作(只是用来隔位用的),剩余个位置对于插入该位置的工作该工作一定可以做到。
首先可以知道,这
j
j
个插入的工作, 一定是快乐值最小的,先对新添加的工作快乐值从小到大排个序,那么这个工作一定是排好序的前面
j
j
个工作,有初始状态,因为第一个工作无论选还是不选,首先不用新添加的工作来隔位,但是如果选了,最前面的位置添加一个新工作就不能必定可以选择了,反之不选则
m
m
项中的工作插入一项在最前面的话肯可以选择。那么考虑状态转移的时候,有种情况:
- 第 i−1 i − 1 个工作选,第 i i 个工作选:要多插入一个新添加的工作,一定可以做的工作数量不变
- 第个工作选,第 i i 个工作不选:不需要插入新添加的工作,一定可以做的工作数量也不变
- 第个工作不选,第 i i 个工作选:不需要插入新添加的工作,一定可以做的工作数量也不变
- 第个工作不选,第 i i 个工作不选:不需要插入新添加的工作,一定可以做的工作数量
现在到达 n n 这个状态,那么对于用了 i i 个工作,有个位置可以必选,那么剩余 m−i−j m − i − j 个工作只能隔位选择了,注意如果第 n n 个工作不选,可供必选的位置
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e3 + 10;
const int INF = 2e9 + 19;
using namespace std;
int a[maxn], b[maxn], sum[maxn];
int n, m, T, kase = 1;
int dp[maxn][2][105][105];
int solve(int x, int y) { ///[x + 1, m]的项目有y个一定可以选到
int res = m - x, ans = 0;
if(y >= res) return sum[x + 1];
ans += sum[m - y + 1]; ///最大的y个要选进去
int l = x + 1, r = m - y; ///[l, r]区间的项目只能间隔取
int tot = (r - l + 1) / 2;
int mid = r - tot + 1;
return ans + sum[mid] - sum[r + 1];
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d %d", &n, &m);
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= m; j++) {
for(int k = 0; k <= m; k++) {
dp[i][0][j][k] = -INF;
dp[i][1][j][k] = -INF;
}
}
}
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 1; i <= m; i++) scanf("%d", &b[i]);
sort(b + 1, b + m + 1); sum[m + 1] = 0;
for(int i = m; i >= 1; i--) sum[i] = sum[i + 1] + b[i];
dp[1][1][0][0] = a[1]; dp[1][0][0][1] = 0;
for(int i = 2; i <= n; i++) {
for(int j = 0; j <= m; j++) {
for(int k = 0; k <= m; k++) {
///选择第i个项目
dp[i][1][j][k] = dp[i - 1][0][j][k] + a[i]; ///第i-1个项目不选
if(j) dp[i][1][j][k] = max(dp[i - 1][1][j - 1][k] + a[i], dp[i][1][j][k]); ///第i-1个项目也选
///不选择第i个项目
dp[i][0][j][k] = max(dp[i - 1][1][j][k], dp[i - 1][0][j][k]); ///第i-1个项目选择
if(k) dp[i][0][j][k] = max(dp[i - 1][0][j][k - 1], dp[i][0][j][k]); ///两个都不选,多出一个可供选择的新增的项目
}
}
}
int ans = 0;
for(int flag = 0; flag < 2; flag++) {
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= m; j++) {
int now = dp[n][flag][i][j];
///已经用了i个项目, 还剩j个空位可以插入
ans = max(ans, now + solve(i, j + 1 - flag));
}
}
}
printf("%d\n", ans);
}
return 0;
}