第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛 题解
【题目链接】
模拟。从左往右填充每一个,如果某一个格子不足,需要从右边离他最近的有盈余的格子里拿一些来填充;如果某一个格子有盈余,那么多余部分往右扔过去。
/*******************************
Judge Result : AC
*******************************/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int INF = 0x7FFFFFFF;
int T, n;
long long a[maxn], b[maxn];
int main() {
#ifdef ZHOUZHENTAO
freopen("test.in", "r", stdin);
#endif
scanf("%d", &T);
while(T --) {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%lld", &a[i]);
}
for(int i = 1; i <= n; i ++) {
scanf("%lld", &b[i]);
a[i] = a[i] - b[i];
}
long long ans = 0;
int p = 1;
for(int i = 1; i <= n; i ++) {
if(a[i] == 0) continue;
if(a[i] > 0) ans += a[i], a[i + 1] += a[i], a[i] = 0;
else {
while(1) {
while(a[p] <= 0) p ++;
if(a[i] + a[p] >= 0) {
ans = ans + (p - i) * (-a[i]);
a[p] += a[i];
a[i] = 0;
break;
} else {
a[i] = a[i] + a[p];
ans = ans + (p - i) * a[p];
a[p] = 0;
}
}
}
}
printf("%lld\n", ans);
}
return 0;
}
B - 合约数
由于是处理子树问题,所以可以将树转成 dfs 序,然后就变成了区间问题。然后就是询问区间上有几个合约数,莫队操作一下就可以了。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
#define LL long long
const int maxn = 2e5 + 10;
vector<int> g[maxn];
int pri[maxn];
int T, n, root;
int a[maxn], sz;
int L[maxn], R[maxn];
int h[maxn], nx[maxn], to[maxn], cnt;
int val[maxn], pos[maxn];
int b[maxn], f[maxn];
struct point {
int id, l, r;
}s[maxn];
bool cmp(const point& a, const point& b) {
if(pos[a.l] == pos[b.l]) return a.r < b.r;
return pos[a.l] < pos[b.l];
}
int prime(int x) {
if(x == 1) return 0;
for(int i = 2; i * i <= x; i ++) {
if(x % i == 0) return 0;
}
return 1;
}
void init() {
for(int i = 1; i <= 10000; i ++) {
pri[i] = prime(i);
}
for(int i = 4; i <= 10000; i ++) {
if(pri[i]) continue;
for(int j = 4; j <= i; j ++) {
if(i % j) continue;
if(pri[j]) continue;
g[i].push_back(j);
}
}
}
void dfs(int x, int fa) {
sz ++;
a[sz] = x;
L[x] = sz;
for(int i = h[x]; i != -1; i = nx[i]) {
if(to[i] == fa) continue;
dfs(to[i], x);
}
R[x] = sz;
}
void add(int x, int y) {
to[cnt] = y;
nx[cnt] = h[x];
h[x] = cnt ++;
}
void Delete(int x) {
b[val[a[x]]] --;
}
void Insert(int x) {
b[val[a[x]]] ++;
}
int main() {
init();
scanf("%d", &T);
while(T --) {
scanf("%d %d", &n, &root);
int sqr = (int)sqrt(1.0 * n);
for(int i = 1; i <= n; i ++) {
pos[i] = i / sqr;
}
for(int i = 1; i <= 10000; i ++) {
b[i] = 0;
}
cnt = 0;
for(int i = 1; i <= n;i ++) {
h[i] = -1;
}
for(int i = 0; i < n - 1; i ++) {
int x, y;
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
sz = 0;
dfs(root, -1);
/*
for(int i = 1; i <= n; i ++) {
printf("%d ", a[i]);
}
printf("\n");
for(int i = 1; i <= n; i ++) {
printf("!!! %d %d %d\n", i, L[i], R[i]);
}
printf("ok\n");
*/
for(int i = 1; i <= n; i ++) {
scanf("%d", &val[i]);
}
for(int i = 1; i <= n; i ++) {
s[i].id = i;
s[i].l = L[i];
s[i].r = R[i];
}
sort(s + 1, s + 1 + n, cmp);
/*
for(int i = 1; i <= n; i ++) {
printf("q : %d %d %d\n", s[i].id, s[i].l, s[i].r);
}
*/
for(int i = s[1].l; i <= s[1].r; i ++) {
Insert(i);
}
f[s[1].id] = 0;
for(int j = 0; j < g[val[s[1].id]].size(); j ++) {
f[s[1].id] = f[s[1].id] + b[g[val[s[1].id]][j]];
}
int left = s[1].l, right = s[1].r;
for(int i = 2; i <= n; i ++) {
while(left > s[i].l) left --, Insert(left);
while(right < s[i].r) right ++, Insert(right);
while(left < s[i].l) Delete(left), left ++;
while(right > s[i].r) Delete(right), right --;
f[s[i].id] = 0;
for(int j = 0; j < g[val[s[i].id]].size(); j ++) {
f[s[i].id] = f[s[i].id] + b[g[val[s[i].id]][j]];
}
}
/*
for(int i = 1; i <= n; i ++) {
printf("!!! %d : %d\n", i, f[i]);
}
*/
long long mod = 1e9 + 7;
long long ans = 0;
for(int i = 1; i <= n; i ++) {
long long tmp = 1LL * i * f[i] % mod;
ans = (ans + tmp) % mod;
}
printf("%lld\n", ans);
}
return 0;
}
/*
100
13 10
10 2
3 1
4 11
4 12
4 13
10 4
2 5
10 3
3 8
3 9
2 6
2 7
30 60 50 24 5 10 12 25 10 120 2 3 4
*/
C - 序列变换
枚举全排列,计算每一种排列需要的操作次数。从一个排列转换成另一个排列,可以模拟搞。从一个数字转换成另一个数字,只能选择一种方式,因此很简单。
/*******************************
Judge Result : AC
*******************************/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int INF = 0x7FFFFFFF;
int T, n;
int a[maxn], b[maxn];
int cost[20][20];
int p[maxn];
int u[maxn];
int f[maxn];
int work(int x, int y) {
if(x == y) return 0;
if(x < y) swap(x, y);
int sum = 0;
while(1) {
if(x % 2 == 0) {
if(x / 2 >= y) sum ++, x = x / 2;
else return sum + x - y;
} else {
if(x == y) return sum;
else sum ++, x --;
}
}
return sum;
}
int get() {
int sum = 0;
for(int i = 1; i <= n; i ++) {
u[i] = p[i];
}
for(int i = 1; i <= n; i ++) {
if(u[i] == i) continue;
sum ++;
for(int j = i + 1; j <= n; j ++) {
if(u[j] == i) {
swap(u[i], u[j]);
break;
}
}
}
return sum;
}
int main() {
#ifdef ZHOUZHENTAO
freopen("test.in", "r", stdin);
#endif
scanf("%d", &T);
while(T --) {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= n; i ++) {
scanf("%d", &b[i]);
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n; j ++) {
cost[i][j] = work(a[i], b[j]);
//cout << a[i] << " -> " << b[j] << " : " << cost[i][j] << endl;
}
}
for(int i = 1; i <= n; i ++) {
p[i] = i;
}
int ans = 2e9;
do {
int tmp = get();
for(int i = 1; i <= n; i ++) {
tmp = tmp + cost[p[i]][i];
}
ans = min(ans, tmp);
} while(next_permutation(p + 1, p + 1 + n));
printf("%d\n", ans);
}
return 0;
}
D - 数字游戏
大致思路为如果无论 $n_1$ 为多少,都有一个 $n_2$ 能够找到,使得结果是 mod 的倍数,则先手输;否则后手输。由于 mod 范围不大,所以可以枚举一下 $n_2$ 的位数,然后暴力枚举处理,只要枚举 mod 范围内即可,因为取模之后的数不会超过 mod。
E - 小Y吃苹果
答案是 2 的 $n$ 次方。
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
printf("%d\n", 1 << n);
return 0;
}
F - 1 + 2 = 3?
找找规律可以发现满足条件的数字是二进制上没有相邻的 1。因此可以二分答案,然后数位 dp 计算方案数。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
#define LL long long
LL dp[110][2];
LL a[110];
LL n;
LL dfs(int len,int sta,bool limit)
{
if(len<0)
return 1;
if(dp[len][sta]!=-1&&!limit)
return dp[len][sta];
int up=limit?a[len]:1;
LL ans=0;
for(int i=0; i<=up; i++)
{
if(sta&&i==1)
continue;
ans+=dfs(len-1,i==1,limit&&i==up);
}
return limit?ans:dp[len][sta]=ans;
}
LL solve(LL x)
{
memset(dp,-1,sizeof dp);
int cnt=0;
while(x>0)
{
a[cnt++]=x%2;
x/=2;
}
return dfs(cnt-1,0,1);
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%lld", &n);
LL l = 1, r = 1e18;
LL mid, ans;
while(l <= r)
{
mid = (l + r) / 2;
LL t = solve(mid) - 1;
if(t < n)
{
l = mid + 1;
}
else
{
ans = mid;
r = mid - 1;
}
}
printf("%lld\n", ans);
}
return 0;
}
G - 小Y做比赛
暂时不会做
H - 小Y与多米诺骨牌
先处理出选择 $i$ 位置往左倒,最左会使得 $L_i$ 也到下;往右倒,最右会使得 $R_i$ 也倒下。
然后进行 dp,$dp_i$ 表示 $[1, i]$ 都倒下需要的最少操作次数。有两种途径,一种是 $[1, j]$ 先倒下,然后选择 $i$ 往左倒,使得 $[j+1,i]$ 都倒下;另一种是 $[1, j]$ 先倒下,然后选择 $j$ 向右倒,使得 $[j+1,i]$ 都倒下。两种都可以用线段树来维护来得到最优解。
/*******************************
Judge Result :
*******************************/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
const int INF = 1e6;
int x[maxn], y[maxn];
int T, n;
int L[maxn], R[maxn];
int s[2][4 * maxn];
int dp[maxn];
vector<int> g[maxn];
void build(int flag, int val, int l, int r, int rt) {
s[flag][rt] = val;
if(l == r) return ;
int mid = (l + r) / 2;
build(flag, val, l, mid, 2 * rt);
build(flag, val, mid + 1, r, 2 * rt + 1);
}
void update(int flag, int op, int pos, int val, int l, int r, int rt) {
if(l == r) {
s[flag][rt] = val;
return;
}
int mid = (l + r) / 2;
if(pos <= mid) update(flag, op, pos, val, l, mid, 2 * rt);
else update(flag, op, pos, val, mid + 1, r, 2 * rt + 1);
if(op == 0) s[flag][rt] = min(s[flag][2 * rt], s[flag][2 * rt + 1]);
else s[flag][rt] = max(s[flag][2 * rt], s[flag][2 * rt + 1]);
}
int getmin(int flag, int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
return s[flag][rt];
}
int mid = (l + r) / 2;
int left = INF, right = INF;
if(L <= mid) left = getmin(flag, L, R, l, mid, 2 * rt);
if(R > mid) right = getmin(flag, L, R, mid + 1, r, 2 * rt + 1);
return min(left, right);
}
int getmax(int flag, int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
return s[flag][rt];
}
int mid = (l + r) / 2;
int left = 0, right = 0;
if(L <= mid) left = getmax(flag, L, R, l, mid, 2 * rt);
if(R > mid) right = getmax(flag, L, R, mid + 1, r, 2 * rt + 1);
return max(left, right);
}
int main() {
#ifdef ZHOUZHENTAO
freopen("test.in", "r", stdin);
#endif
scanf("%d", &T);
while(T --) {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d%d", &x[i], &y[i]);
}
build(0, INF, 1, n, 1);
L[1] = 1;
update(0, 0, 1, 1, 1, n, 1);
for(int i = 2; i <= n; i ++) {
update(0, 0, i, i, 1, n, 1);
int left = 1, right = i, ans = -1;
while(left <= right) {
int mid = (left + right) / 2;
if(x[mid] > x[i] - y[i]) ans = mid, right = mid - 1;
else left = mid + 1;
}
L[i] = getmin(0, ans, i, 1, n, 1);
update(0, 0, i, L[i], 1, n, 1);
}
build(0, 0, 1, n, 1);
R[n] = n;
update(0, 1, n, n, 1, n, 1);
for(int i = n - 1; i >= 1; i --) {
update(0, 1, i, i, 1, n, 1);
int left = i, right = n, ans = -1;
while(left <= right) {
int mid = (left + right) / 2;
if(x[mid] < x[i] + y[i]) ans = mid, left = mid + 1;
else right = mid - 1;
}
R[i] = getmax(0, i, ans, 1, n, 1);
update(0, 1, i, R[i], 1, n, 1);
}
/*
for(int i = 1; i <= n; i ++) {
cout << i << " " << L[i] << " " << R[i] << endl;
}
*/
for(int i = 1; i <= n; i ++) {
g[i].clear();
}
for(int i = 1; i <= n; i ++) {
g[R[i]].push_back(i);
}
build(0, INF, 0, n, 1);
build(1, INF, 0, n, 1);
update(0, 0, 0, 0, 0, n, 1);
update(1, 0, 0, 0, 0, n, 1);
for(int i = 1; i <= n; i ++) {
dp[i] = getmin(1, L[i] - 1, i - 1, 0, n, 1) + 1;
dp[i] = min(dp[i], getmin(0, 0, i - 1, 0, n, 1) + 1);
update(1, 0, i, dp[i], 0, n, 1);
update(0, 0, i, dp[i], 0, n, 1);
for(int j = 0; j < g[i].size(); j ++) {
update(0, 0, g[i][j] - 1, INF, 0, n, 1);
}
}
/*
for(int i = 1; i <= n ; i ++) {
printf("dp[%d] = %d\n", i, dp[i]);
}
*/
printf("%d\n", dp[n]);
}
return 0;
}
I - 二数
构造一下最大的小于等于 $n$ 的二数,以及最小的大于等于 $n$ 的二数,取小的就是答案。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
#define LL long long
char s[1000111];
int main() {
int T;
scanf("%d", &T);
while (~scanf("%s", &s)) {
int k=strlen(s);
if(s[0]=='1'&&k==1){
printf("0\n");
continue;
}
int flag=0;
for(int i=0;i<k;i++){
if((s[i]-'0')%2==1){
if(s[i]=='9'){
flag=-1;
break;
}
for(int j=i+1;j<k;j++){
if(s[j]>'4'){
flag=1;
break;
}
else if(s[j]<'4'){
flag=-1;
break;
}
}
if(flag!=0) break;
flag=-1;
break;
}
}
if(flag==0){
printf("%s",s);
}
else if(flag==-1){
int i=0;
for(i=0;i<k;i++){
if((s[i]-'0')%2==1){
if(s[i]>'1'||i>0)
printf("%c",s[i]-1);
break;
}
printf("%c",s[i]);
}
i++;
for(;i<k;i++){
printf("%c",'8');
}
}
else if(flag==1){
int i=0;
for(i=0;i<k;i++){
if((s[i]-'0')%2==1){
printf("%c",s[i]+1);
break;
}
printf("%c",s[i]);
}
i++;
for(;i<k;i++){
printf("%c",'0');
}
}
printf("\n");
}
return 0;
}
J - 小Y写文章
二分答案 $x$,需要验证答案小于等于 $x$ 能否做到。验证:可以考虑为有 $n + 1$ 个空位,需要 $m$ 个物品去填充。如果某个物品可以放在某个位置,就连边,容量为 1。需要额外增加一个节点,如果某个位置可以不放物品,这个节点就和那个位置连边。看看网络最大流是不是为 $n+1$ 即可。如果是,则说明存在放置的方案。
/*******************************
Judge Result : AC
*******************************/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 500 + 10;
const int INF = 0x7FFFFFFF;
struct Edge
{
int from, to, cap, flow;
Edge(int u, int v, int c, int f) :from(u), to(v), cap(c), flow(f){}
};
vector<Edge>edges;
vector<int>G[maxn];
bool vis[maxn];
int d[maxn];
int cur[maxn];
int S, T;
void init()
{
for (int i = 0; i < maxn; i++)
G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
// cout << from << " -> " << to << " " << cap << endl;
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
int w = edges.size();
G[from].push_back(w - 2);
G[to].push_back(w - 1);
}
bool BFS()
{
memset(vis, 0, sizeof(vis));
queue<int>Q;
Q.push(S);
d[S] = 0;
vis[S] = 1;
while (!Q.empty())
{
int x = Q.front();
Q.pop();
for (int i = 0; i<G[x].size(); i++)
{
Edge e = edges[G[x][i]];
if (!vis[e.to] && e.cap>e.flow)
{
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[T];
}
int DFS(int x, int a)
{
if (x == T || a == 0)
return a;
int flow = 0, f;
for (int &i = cur[x]; i<G[x].size(); i++)
{
Edge e = edges[G[x][i]];
if (d[x]+1 == d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0)
{
edges[G[x][i]].flow+=f;
edges[G[x][i] ^ 1].flow-=f;
flow+=f;
a-=f;
if(a==0) break;
}
}
if(!flow) d[x] = -1;
return flow;
}
int dinic(int s, int t)
{
int flow = 0;
while (BFS())
{
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
int a[maxn], b[maxn];
int n, m;
int check(int limit) {
init();
int s = 0;
int t = n + m + 2;
S = n + m + 3;
T = n + m + 4;
AddEdge(S, s, INF);
AddEdge(t, T, INF);
AddEdge(n + m + 5, 0 + 1 + m, 1);
AddEdge(n + m + 5, n + 1 + m, 1);
for(int i = 1; i < n ; i ++) {
if(abs(a[i] - a[i + 1]) <= limit) {
AddEdge(n + m + 5, i + 1 + m, 1);
}
}
AddEdge(s, n + m + 5, n + 1 - m);
for(int i = 1; i <= m; i ++) {
AddEdge(s, i, 1);
}
for(int i = 1; i <= m; i ++) {
for(int j = 0; j <= n; j ++) {
if(j == 0) {
if(abs(b[i] - a[1]) <= limit) {
AddEdge(i, j + 1 + m, 1);
}
} else if(j < n) {
if(abs(b[i] - a[j]) <= limit && abs(b[i] - a[j + 1]) <= limit) {
// cout << i << " " << j << endl;
AddEdge(i, j + 1 + m, 1);
}
} else {
if(abs(b[i] - a[n]) <= limit) {
AddEdge(i, j + 1 + m, 1);
}
}
}
}
for(int j = 0; j <= n; j ++) {
AddEdge(j + 1 + m, t, 1);
}
if(dinic(S, T) == n + 1) return 1;
return 0;
}
int main() {
#ifdef ZHOUZHENTAO
freopen("test.in", "r", stdin);
#endif
int T;
scanf("%d", &T);
while(T --) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
}
for(int i = 1; i <= m; i ++) {
scanf("%d", &b[i]);
}
int left = 0, right = 2e9, ans;
while(left <= right) {
int mid = (left + right) / 2;
if(check(mid)) ans = mid, right = mid - 1;
else left = mid + 1;
}
printf("%d\n", ans);
}
return 0;
}
K - 树上最大值
有个东西叫线性基:支持插入元素,合并两个集合,计算集合中选一些数异或值最大的方法。所有操作复杂度均为 $O(log(value))$。有了这个方法这题就很简单了。
枚举所有子树,A 集合在这个子树中,B 集合在子树以外部分。因此可以转化为区间问题,子树内用树形dp加上上面那个方法做就好了,子树外就是两个区间的并集,用前缀和后缀的并集即可。
/*******************************
Judge Result : AC
*******************************/
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 2;
const int INF = 0x7FFFFFFF;
struct Base{
int a[30];
void init(){for(int i=0;i<30;i++)a[i]=0;}
void up(int &a, int b) {if(b>a)a=b;}
void ins(int x){for(int i=29;~i;i--)if(x>>i&1){if(a[i])x^=a[i];else{a[i]=x;break;}}}
int ask(){
int t=0;
for(int i=29;~i;i--)up(t,t^a[i]);
return t;
}
};
Base merge(const Base& b1, const Base& b2) {
Base res = b1;
for(int i = 0; i < 30; i ++) {
if(b2.a[i]) res.ins(b2.a[i]);
}
return res;
}
int A[maxn], sz;
int L[maxn], R[maxn];
int h[maxn], nx[2 * maxn], to[2 * maxn], cnt;
int n, val[maxn];
Base node[maxn], pre[maxn], suf[maxn];
void dfs(int x, int fa) {
sz ++;
A[sz] = x;
L[x] = sz;
node[x].init();
node[x].ins(val[x]);
for(int i = h[x]; i != -1; i = nx[i]) {
if(to[i] == fa) continue;
dfs(to[i], x);
node[x] = merge(node[x], node[to[i]]);
}
R[x] = sz;
}
void add(int x, int y) {
to[cnt] = y;
nx[cnt] = h[x];
h[x] = cnt ++;
}
int main() {
#ifdef ZHOUZHENTAO
freopen("test.in", "r", stdin);
#endif
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
h[i] = -1;
scanf("%d", &val[i]);
}
for(int i = 0; i < n - 1; i ++) {
int x, y;
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
dfs(1, -1);
pre[0].init();
for(int i = 1; i <= n; i ++) {
pre[i] = pre[i - 1];
pre[i].ins(val[A[i]]);
}
suf[n + 1].init();
for(int i = n; i >= 1; i --) {
suf[i] = suf[i + 1];
suf[i].ins(val[A[i]]);
}
int ans = 0;
for(int i = 1; i <= n; i ++) {
Base tmp = node[i];
Base left, right;
left.init();
right.init();
if(L[i] != 1) left = pre[L[i] - 1];
if(R[i] != n) right = suf[R[i] + 1];
ans = max(ans, tmp.ask() + merge(left, right).ask());
}
printf("%d\n", ans);
return 0;
}
L - K序列
$dp_{i,j}$ 表示前 $i$ 个数,和对 $mod$ 取模的结果为 $j$ 的子序列的最长长度。开个滚动数组就好了。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
#define LL long long
int num[100111];
int dp[2][10001111];
int n, k;
int main() {
while (~scanf("%d %d", &n, &k)) {
for (int i = 0; i < n; ++ i) {
scanf("%d", num + i);
num[i] %= k;
}
int wei = 0;
memset(dp, -1, sizeof dp);
dp[wei][0] = 0;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < k; ++ j) {
dp[wei ^ 1][(j + num[i]) % k] = dp[wei][(j + num[i]) % k];
if (dp[wei][j] != -1)
dp[wei ^ 1][(j + num[i]) % k] = max(dp[wei][(j + num[i]) % k], dp[wei][j] + 1);
}
wei ^= 1;
}
printf("%d\n", dp[wei][0]);
}
return 0;
}

浙公网安备 33010602011771号