赛时困得睡着了

B题 https://www.acwing.com/problem/content/5562/

第二题赛事一直在想模拟加贪心,却发现非常难实现,我们需要转变思维,这时候一般有3种可能

双指针?二分?dp?

后来清醒了一点我们判断一个答案是不是合法很容易,我们再考虑答案求最大值,显然具有单调性,所以我们考虑二分答案,前缀和u优化判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Problem: 摆放棋子
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/5562/
// Memory Limit: 256 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
//# define int long long
#define ull unsigned long long
#define pii pair<int,int>

#define baoliu(x, y) cout << fixed << setprecision(y) << x
#define endl "\n"
#define debug1(x) cerr<<x<<" "
#define debug2(x) cerr<<x<<endl
const int N = 3e5 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-8;
int n, m;
int a[N];
int s[N];
int k;
bool check(int mid){
for(int i=1;i+mid-1<=n;i++){
int r=i+mid-1;
//if(mid==4) cerr<<i<<" "<<r<<" "<<s[r]-s[i-1]<<endl;
if(mid-s[r]+s[i-1]<=k)return true;
}
return false;
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
int l=0,r=n;
while(l<r){
//cerr<<l<<" "<<r<<endl;
int mid=(l+r+1)>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l<<endl;

for(int i=1;i+l-1<=n;i++){
int r=i+l-1;
if(l-s[r]+s[i-1]<=k){
for(int o=i;o<=r;o++)a[o]=1;
for(int j=1;j<=n;j++)cout<<a[j]<<" ";
return ;
}
}

}
int main() {
cin.tie(0);

ios::sync_with_stdio(false);

int t;
//cin>>t;
t=1;
while (t--) {
solve();
}
return 0;
}


C题是个不错的题,本题需要动态建树在线回答直径,似乎只能用倍增,不能其他方法,所以还是要全能。

考察直径的性质,从一次改变入手

前置

思路

定理:

  1. 边权非负时,树的直径求法:任选一个点 aa,找到离它最远的点 bb,再找到离 bb 最远的点 cc,则 bcb \sim c 是一条直径。
  2. 树上任意两点 a,ba, b 间距离 dist(a,b)=depth(a)+depth(b)2×depth(lca(a,b))dist(a, b) = depth(a) + depth(b) - 2 \times depth(lca(a, b)),其中 depth(x)depth(x)xx 到根的距离,lca(a,b)lca(a, b)a,ba, b 两点的最近公共祖先。

性质:

若上一次树的直径是 ABA \sim B,现在要在 uu 下增加两个子节点 x,yx, y,若直径变大,则新直径的一个端点必然是 xxyyx,yx, y 等价,下面仅考虑 xx

  1. dist(A,B)<max(dist(A,x),dist(B,x))dist(A, B) < max(dist(A, x), dist(B, x)),由于未添加节点前 uu 到任何节点的距离小于等于 dist(A,B)dist(A, B),因此 xx 到任何节点的距离最多比 uu 到任何节点的距离多 11,因此 dist(A,x)dist(A,B)+1dist(A, x) \le dist(A, B) + 1。不妨设此时 dist(A,x)>dist(A,B)dist(A, x) > dist(A, B),则 dist(A,x)=dist(A,B)+1dist(A, x) = dist(A, B) + 1。由于 xx 到任何节点的距离不可能超过 dist(A,B)+1dist(A, B) + 1,所以此刻 AxA \sim x 是一条新直径。同理,若 dist(B,x)>dist(A,B)dist(B, x) > dist(A, B)BxB \sim x 是一条新直径。
  2. dist(A,B)max(dist(A,x),dist(B,x))dist(A, B) \ge max(dist(A, x), dist(B, x)),则距离 AA 最远的点是 BB,且距离 BB 最远的点是 AA,因此 ABA \sim B 仍是直径。

时间复杂度:mlog(n)mlog(n),其中 mm 为询问次数,nn 为最大节点数。

debug:嵌套数组下标的中括号混乱,表示不仔细看根本看不出来

debug:数组开的大小应该是len+1,数组开小了,越界出发未定义行为

https://www.acwing.com/solution/content/236111/
参考题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Problem: 树的直径
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/description/5563/
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
//# define int long long
#define ull unsigned long long
#define pii pair<int,int>

#define baoliu(x, y) cout << fixed << setprecision(y) << x
#define endl "\n"
#define debug1(x) cerr<<x<<" "
#define debug2(x) cerr<<x<<endl
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-8;
int n, m;
int a[N];
int len=__lg(N);
int fa[N][20];//绷数组越界出发ub
int dep[N];
//掌握直径的性质:两次dfs在正权树上求直径
//这道题对于每次新加进来的点都需要重新维护倍增信息,
// 首先是在线的,无法使用targan
// 树跑需要提前预处理dfs序,而动态建树会改变重儿子,所以无法预处理两次dfs
int lca(int x,int y){

//cerr<<"start"<<endl;
//cerr<<x<<" "<<y<<endl;
if(dep[x]<dep[y])swap(x,y);
for(int i=len;i>=0;i--){
//不要嵌套数组写,还是多创建变量
if(dep[fa[x][i]]>=dep[y]){
x=fa[x][i];
//cerr<<"y "<<y<<" "<<dep[y]<<endl;
//cerr<<"iter"<<" "<<i<<" "<<x<<" "<<dep[x]<<endl;
}
}
//cerr<<"samehigh"<<" "<<x<<" "<<y<<endl;
if(x==y)return x;
for(int i=len;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
//cerr<<x<<" "<<y<<endl;
x=fa[x][i];y=fa[y][i];
}
}
// cerr<<x<<" "<<y<<endl;
// cerr<<fa[x][0]<<endl;
// cerr<<"end"<<endl;
return fa[x][0];
}
int dis(int x,int y){

int mid=lca(x,y);

//cerr<<x<<" "<<y<<" "<<mid<<endl;
return dep[x]+dep[y]-2*dep[mid];

//cerr<<x<<" "<<y<<" <<mid<<endl;
}
void solve(){
//cout<<"len"<<" "<<len<<endl;
cin>>m;
n=4;
dep[1]=1;

for(int i=2;i<=4;i++){
fa[i][0]=1;
dep[i]=2;
}
int res1=2;int res2=3;
//动态维护树的直径两个端点
int ans=2;
while(m--){
int u;cin>>u;
int v1=++n;
int v2=++n;

//动态维护新加的点的求lca所需信息
dep[v1]=dep[u]+1;
dep[v2]=dep[u]+1;
fa[v1][0]=fa[v2][0]=u;
for(int i=1;i<=len;i++){
fa[v1][i]=fa[fa[v1][i-1]][i-1];
fa[v2][i]=fa[fa[v2][i-1]][i-1];
}
//cerr<<"fa[5][1]"<<" "<<fa[5][1]<<endl;
//cerr<<"fa[7][1]"<<" "<<fa[7][1]<<endl;
int dis1=dis(v1,res1);
int dis2=dis(v1,res2);
if(dis1>=dis2&&dis1>=ans){
ans=dis1;
res2=v1;
}
else if(dis2>=dis1&&dis2>=ans)
{ans=dis2;
res1=v1;
}
cout<<ans<<endl;
}
//for(int i=1;i<=n;i++)cerr<<i<<" "<<dep[i]<<" "<<endl;
}
int main() {
cin.tie(0);

ios::sync_with_stdio(false);

int t;
//cin>>t;
t=1;
while (t--) {
solve();
}
return 0;
}