总结:CSP2024-S模拟赛

A. 人事调动

长得非常像一道模拟,但是由于set会去重,map开的大小会达到 $N^2$ ,所以,我们可以使用multiset(其增删改查时间复杂度近似:O(log N),即总时间复杂度为O(N log N))。

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

const int N=100005;

int n,q;
int a[N],b[N];
multiset<int> mp[N];
multiset<int> ans;

int main()
{
freopen("work.in","r",stdin);
freopen("work.out","w",stdout);

ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

cin>>n>>q;
for(int i=1;i<=n;i++)
{
cin>>a[i]>>b[i];
mp[b[i]].insert(a[i]);
}

for(int i=1;i<N;i++)
{
if(mp[i].empty())
{
continue;
}
ans.insert(*(--mp[i].end()));
}

while(q--)
{
int c,d;
cin>>c>>d;

int id=b[c];
ans.erase(ans.find(*(--mp[id].end())));
mp[id].erase(mp[id].find(a[c]));
if(!mp[id].empty())
{
ans.insert(*(--mp[id].end()));
}

if(!mp[d].empty())
{
ans.erase(ans.find(*(--mp[d].end())));
}
mp[d].insert(a[c]);
ans.insert(*(--mp[d].end()));

b[c]=d;


cout<<(*ans.begin())<<"\n";
}

return 0;
}


B. 失意

比较一眼的贪心,以区间左端点从小到大排序,用优先队列从小到大维护右端点,每次队列里有m个元素,就比较一次大小,超过m就把右端点最小的弹出去。每次比较记录此时的最大L和最小R(答案即R-L),最后再循环一边找到符合的m个区间。

注意:要特判答案为0的时候,任意输出m个区间即可

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

const ll N=1000005;

ll Num,n,m;
struct node
{
ll l,r,id;
bool operator <(node a)
{
return l<a.l;
}
} a[N];

priority_queue<ll> pq;
bool f;

ll ans,id1,id2;

int main()
{
freopen("failure.in","r",stdin);
freopen("failure.out","w",stdout);

ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

cin>>Num;

cin>>n>>m;
for(ll i=1;i<=n;i++)
{
ll x,y;
cin>>x>>y;
a[i]={x,y,i};
}
sort(a+1,a+n+1);

for(ll i=1;i<=n;i++)
{
pq.push(-a[i].r);
while(pq.size()>m)
{
pq.pop();
}
if(pq.size()==m)
{
if(-pq.top()-a[i].l>ans)
{
ans=-pq.top()-a[i].l;
id1=a[i].l,id2=-pq.top();
}
}
}

if(id1==0&&id2==0)
{
id1=1e9;
}
cout<<ans<<"\n";

for(ll i=1;i<=n;i++)
{
if(a[i].l<=id1&&a[i].r>=id2)
{
cout<<a[i].id<<" ";
m--;
}
if(!m)
{
cout<<"\n";
return 0;
}
}

return 0;
}


C. 位于LIS概率

又是C>>>>>>D,扣C结果在还有3分钟结束的时候想到了正解,错失D大好90pts

70pts的做法就是没有线段树/树状数组优化的正解:求出整个数列的LIS长度mx;对于每个 $i∈{1…n}$ 求出以i结尾与以i开头的LIS长度 $x1$, $x2$ 和数量 $t1$, $t2$ ,如果其 $x1+x2-1=mx$,说明这是一种LIS,i在LIS中的情况数+= $t1\times t2$,最后用快速幂求逆元即可。

用树状数组维护比较短(改成线段树只要改架构就行了)。

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
125
126
127
128
129
130
131
132
#include<bits/stdc++.h>

using namespace std;

#define ll long long

const ll N=1000005,mod=998244353;

ll n,m;
ll a[N],b[N];

ll ans[N];

struct node
{
ll x,t; //长度,数量

} tr[N],pre[N],suf[N];

node mx;

ll qpow(ll a,ll b)
{
ll ret=1;
while(b)
{
if(b&1)
{
ret=ret*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ret;
}

void calc(node &x,node y) //合并
{
if(x.x==y.x)
{
x.t=(x.t+y.t)%mod;
}
else if(x.x<y.x)
{
x=y;
}
}

void update(ll x,node y)
{
while(x<=m)
{
calc(tr[x],y);
x+=x&(-x);
}
}

node query(ll x)
{
node ret={0,0};
while(x)
{
calc(ret,tr[x]);
x-=x&(-x);
}

return ret;
}

int main()
{
freopen("lis.in","r",stdin);
freopen("lis.out","w",stdout);

ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

cin>>n;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
b[i]=a[i];
}

sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;

for(ll i=1;i<=n;i++)
{
a[i]=lower_bound(b+1,b+m+1,a[i])-b;
}

for(ll i=1;i<=n;i++)
{
pre[i]=query(a[i]-1);
pre[i].x++;
calc(pre[i],{1,1});
update(a[i],pre[i]);

calc(mx,pre[i]);
}

memset(tr,0,sizeof tr);

for(ll i=n;i;i--)
{
a[i]=m-a[i]+1;
suf[i]=query(a[i]-1);
suf[i].x++;
calc(suf[i],{1,1});
update(a[i],suf[i]);
}

for(ll i=1;i<=n;i++)
{
if(pre[i].x+suf[i].x-1==mx.x)
{
ans[i]=pre[i].t*suf[i].t%mod;
}
}

ll tt=qpow(mx.t,mod-2);
for(ll i=1;i<=n;i++)
{
cout<<ans[i]*tt%mod<<" ";
}

cout<<"\n";

return 0;
}


D. 排队

暴力有90早知道不扣C了

补题之后感觉不是很复杂,不知道做法有没有假掉。

首先由题目我们明确了一些排序规则:

  • 子节点在父节点前面
  • 编号小的的子节点在编号大的子节点前面

因此,我们可以用dfs给每一个节点排序,并用优先队列维护空房间顺序。

对于操作1(来人):弹出x次优先级最高房间,输出最后一个编号

对于操作2(走人):我们发现对于被移动的人x,他只会影响他的祖先,倍增,直到到空房间,输出其上跳步数即可。

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

const int N=1e5+5;

int n,t;

vector<int> e[N];
int par[N][25];

int id[N];
int tot;

bool vis[N];

void dfs(int x,int f)
{
par[x][0]=f;
vector<int> son;
son.clear();

for(auto y:e[x])
{
if(y==f)
{
continue;
}
son.push_back(y);
}

sort(son.begin(),son.end());
for(auto y:son)
{
dfs(y,x);
}

id[x]=++tot;
}

struct node
{
int v;
bool operator < (const node &x) const
{
return id[v]>id[x.v];
}
};

priority_queue<node>q;


int main()
{
freopen("queue.in","r",stdin);
freopen("queue.out","w",stdout);

ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

cin>>n>>t;
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
e[a].push_back(b);
e[b].push_back(a);
}

dfs(1,0);

for(int i=1;i<=20;i++)
{
for(int j=1;j<=n;j++)
{
par[j][i]=par[par[j][i-1]][i-1];
}
}

for(int i=1;i<=n;i++)
{
q.push((node){i});
}

while(t--)
{
int op,x;
cin>>op>>x;

if(op==1)
{
int v=0;
for(int i=1;i<=x;i++)
{
v=q.top().v;
q.pop();
vis[v]=1;
}
cout<<v<<"\n";

}
else
{
int cnt=0;
for(int i=20;i>=0;i--)
{
if(vis[par[x][i]])
{
x=par[x][i];
cnt+=(1<<i);
}
}

vis[x]=0;
q.push((node){x});

cout<<cnt<<"\n";
}
}

return 0;
}

END…

AtCoder Beginner Contest 355 赛后总结

比赛链接

非常美丽地在1h内做完了前4题,结果第五题交互题,死活不对。。。

题解

A. Who Ate the Cake?

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

int a,b;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>a>>b;
if(a!=b)
{
cout<<6-a-b<<"\n";
}
else
{
cout<<-1<<"\n";
}

return 0;
}

B. Piano 2

直接排序然后对比即可,就是要注意C数组的大小为n+m,因为这个博主整了2发罚时。

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

int n,m;
int c[205];

map<int,bool> mp;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>c[i];
mp[c[i]]=1;
}
for(int i=0;i<m;i++)
{
cin>>c[i+n];
}

sort(c,c+m+n);

bool f=0;
for(int i=0;i<n+m;i++)
{
if(mp[c[i]])
{
if(f==1)
{
cout<<"Yes\n";
return 0;
}
else
{
f=1;
}
}
else
{
f=0;
}
}

cout<<"No\n";

return 0;
}



C. Bingo 2

纯模拟,但是注意卡常。一是要使用break;二是不用初始化,把每个点的值先填上

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

int n,t;
int a[200005];
map<int,int> mp;

int g[2005][2005];

int ans,ans1=1e9;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>t;
for(int i=0;i<t;i++)
{
cin>>a[i];
mp[a[i]]=i+1;
}

for(int i=1;i<=n;i++)
{
bool f=1; ans=0;
for(int j=1;j<=n;j++)
{
if(!mp[n*(i-1)+j])
{
f=0;
break;
}
else
{
ans=max(mp[n*(i-1)+j],ans);
}
}
if(f==1)
{
ans1=min(ans1,ans);
}
f=1; ans=0;
for(int j=1;j<=n;j++)
{
if(!mp[n*(j-1)+i])
{
f=0;
break;
}
else
{
ans=max(mp[n*(j-1)+i],ans);
}
}
if(f==1)
{
ans1=min(ans1,ans);
}
}

bool f=1; ans=0;
for(int i=1;i<=n;i++)
{
if(!mp[n*(i-1)+i])
{
f=0;
break;
}
else
{
ans=max(mp[n*(i-1)+i],ans);
}
}
if(f==1)
{
ans1=min(ans1,ans);
}
f=1; ans=0;
for(int i=1;i<=n;i++)
{
if(!mp[n*(i-1)+n-i+1])
{
f=0;
break;
}
else
{
ans=max(mp[n*(i-1)+n-i+1],ans);
}
}
if(f==1)
{
ans1=min(ans1,ans);
}

cout<<(ans1==1e9?-1:ans1)<<"\n";;

return 0;
}


D. Intersecting Intervals

最水D题。

存一下所有开始时间和结束时间,然后按时间顺序排,注意由于区间左右皆开,所以开始要排在结束前面

然后如果遇到开始时间,ans+现有的未完区间,并且未完区间数+1,否则-1

请开long long

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

bool cmp(pair<ll,ll> x,pair<ll,ll> y)
{
if(x.first==y.first)
{
return x.second<y.second;
}
return x.first<y.first;
}

ll n,s,t;
vector<pair<ll,ll> > v;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n;

for(ll i=0;i<n;i++)
{
cin>>s>>t;
v.push_back({s,0});
v.push_back({t,1});
}
sort(v.begin(),v.end(),cmp);

ll k=0,ans=0;
for(ll i=0;i<v.size();i++)
{
if(v[i].second==0)
{
ans+=k;
k++;
}
else
{
k--;
}
}

cout<<ans<<"\n";

return 0;
}


E. Guess the Sum

为什么加了输入输出流解绑就会出现输入问题qwq,烦

参考jiangly大神的做法

(=_=) E又>F了

非常匪夷所思地使用了最短路

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

int n,l,r;
int v;
int pre[300005];
queue<int> q;

int main()
{
// ios::sync_with_stdio(0);
// cin.tie(0),cout.tie(0);

cin>>n>>l>>r;
r++;

q.push(l);
memset(pre,-1,sizeof pre);
pre[l]=l;

while(!q.empty())
{
int x=q.front();
q.pop();

for(int i=1;i<=(1<<n);i*=2)
{
for(auto y:{x-i,x+i})
{
if(y<0||y>(1<<n))
{
continue;
}
if(pre[y]==-1)
{
pre[y]=x;
q.push(y);
}
}
if(x&i)
{
break;
}
}
}

ll ans=0;
for(int i=r;i!=l;i=pre[i])
{
int b=i,a=pre[i];
int t=1;
if(a>b)
{
swap(a,b);
t=-t;
}
cout<<"? "<<__lg(b-a)<<" " <<a/(b-a)<<"\n";

int ret;
cin>>ret;
ans=(ans+t*ret+100)%100;
}

cout<<"! "<<ans<<"\n";

return 0;
}

F. MST Query

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
#include<bits/stdc++.h>

using namespace std;

#define ll long long

using namespace std;

ll n,m;
ll a[400005],b[400005],c[400005];
ll par[200005];
ll vis[400005];
ll dp[400005];

ll find(ll x)
{
return par[x]==x?x:par[x]=find(par[x]);
}

void merge(ll x,ll y)
{
x=find(x),y=find(y);
par[x]=y;
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>m;
for(ll i=1;i<n+m;i++)
{
cin>>a[i]>>b[i]>>c[i];
}

for(ll l=1;l<=10;l++)
{
for(ll i=1;i<=n;i++)
{
par[i]=i;
}
for(ll i=1;i<n+m;i++)
{
if(c[i]>l)
{
continue;
}
if(find(a[i])!=find(b[i]))
{
merge(a[i],b[i]);
if(!vis[i])
{
dp[i]+=l;
vis[i]=1;
}
}
else if(vis[i])
{
dp[i]-=l;
vis[i]=0;
}
}
}

ll ans=0;
for(ll i=1;i<n+m;i++)
{
ans+=dp[i];
if(i>=n)
{
cout<<ans<<"\n";
}
}

return 0;
}

G. Baseball

AtCoder Beginner Contest 352 赛后总结

比赛链接

非常难绷地交了6发罚时。。。

题解

A. AtCoder Line

应该没什么好讲的

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
#include<bits/stdc++.h>

using namespace std;

int n;
int x,y,z;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>x>>y>>z;
if(x<y)
{
swap(x,y);
}
if(x>=z&&y<=z)
{
cout<<"Yes\n";
}
else
{
cout<<"No\n";
}

return 0;
}


B. Typing

又是双指针,典

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
#include<bits/stdc++.h>

using namespace std;

string s,t;

int main()
{

ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>s>>t;

for(int i=0,j=0;i<t.size();)
{
while(t[i]!=s[j]&&i<t.size())
{
i++;
}
if(i==t.size())
{
break;
}
cout<<i+1<<" ";
i++,j++;
}
cout<<"\n";

return 0;
}


C. Standing On The Shoulders

由于头高一定比肩高高,而肩高综合是一定的,因此只需要按照头高和肩高之差拍序,找到差最大的即可。

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
#include<bits/stdc++.h>

using namespace std;

long long n;
struct node
{
int a,b,d;
} x[200005];

bool cmp(node a,node b)
{
return a.d>b.d;
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n;
for(long long i=0;i<n;i++)
{
cin>>x[i].a>>x[i].b;
x[i].d=x[i].b-x[i].a;
}
sort(x,x+n,cmp);
long long ans=0;
for(long long i=1;i<n;i++)
{
ans+=x[i].a;
}
ans+=x[0].b;

cout<<ans<<"\n";

return 0;
}


D. Permutation Subsequence

主要是赛时非常抽线地想到了滑动窗口并立刻放弃,转头使用了ST表。

Part1. ST表

说实话,当我看到还有人用线段树时,我意识到我不是一个人。 思路其实很简单,存一下每个元素的索引。答案即是,每连续 k 个元素中最大索引与最小索引之差的最小值。

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
#include<bits/stdc++.h>

using namespace std;

int n,k,p;
int id[200005];
int ans=1e9;

int f1[200005][20],f2[200005][20];

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>p;
id[p]=i;
}

//ST表板子部分
for(int i=1;i<=n;i++)
{
f1[i][0]=f2[i][0]=id[i];
}
for(int j=1;(1<<j)<=n;j++)
{
for(int i=1;i+(1<<(j-1))<=n;i++)
{
f1[i][j]=max(f1[i][j-1],f1[i+(1<<(j-1))][j-1]);
f2[i][j]=min(f2[i][j-1],f2[i+(1<<(j-1))][j-1]);
}
}

int s=log2(k);
for(int i=1;i+k-1<=n;i++)
{
ans=min(ans,max(f1[i][s],f1[i+k-1-(1<<s)+1][s])-min(f2[i][s],f2[i+k-1-(1<<s)+1][s]));
}

cout<<ans<<"\n";

return 0;
}

跑到了49ms,过肯定是能过的

Part.2 滑动窗口

其实思路是一样的,但是用单调队列来维护

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
#include<bits/stdc++.h>

using namespace std;

int n,k,p;
int id[200005];
int ans=1e9;

deque<int> mn,mx;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>p;
id[p]=i;
}

//滑动窗口板子部分
for(int i=1;i<=n;i++)
{
//超过k个值了所以这个元素可以丢掉了
while(!mn.empty()&&i-mn.front()>=k)
{
mn.pop_front();
}
while(!mx.empty()&&i-mx.front()>=k)
{
mx.pop_front();
}

//如果放进来的值超过了原来的极值,把原来的极值扔掉
while(!mn.empty()&&id[i]<=id[mn.back()])
{
mn.pop_back();
}
while(!mx.empty()&&id[i]>=id[mx.back()])
{
mx.pop_back();
}
//直到不超过原来的极值,把它放在最后面维持单调性
mn.push_back(i);
mx.push_back(i);

if(i>=k)
{
ans=min(ans,id[mx.front()]-id[mn.front()]);
}
}

cout<<ans<<"\n";

return 0;
}

跑到了14ms的绝佳成绩

但其实我觉得这两种方法都可以算正解吧(?

它们的板子都是黄题。


To Be Continued…

Codeforces Round 943 (Div. 3) 赛后总结

比赛链接

我服了做得太烂了。。。

还差36分变绿,死一会儿。。。

题解

A. Maximize?

典,暴力枚举即可

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
#include<bits/stdc++.h>

using namespace std;

int t,x;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>t;
while(t--)
{
cin>>x;
int mx=0,id=0;
for(int i=1;i<x;i++)
{
if(__gcd(x,i)+i>mx)
{
mx=__gcd(x,i)+i;
id=i;
}
}
cout<<id<<"\n";
}

return 0;
}

B. Prefiquence

双指针,也很典

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
#include<bits/stdc++.h>

using namespace std;

int t,n,m;
string a,b;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>t;
while(t--)
{
cin>>n>>m>>a>>b;
int cn1=b.size(),cn2=a.size();
int i=0,j=0;
while(i<cn1&&j<cn2)
{
if(a[j]==b[i])
{
j++;
}
if(j==cn2)
{
break;
}
i++;
}
cout<<j<<"\n";
}
return 0;
}


C. Assembly via Remainders

比较简单的构造,保证每个$a[i-1]>x[i]$即可

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
#include<bits/stdc++.h>

using namespace std;

int t;
int n;
long long x[505],a[505];
long long pre;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>t;
while(t--)
{
cin>>n;
for(int i=2;i<=n;i++)
{
cin>>x[i];
}
a[1]=x[2]+1;
for(int i=2;i<=n;i++)
{
a[i]=a[i-1]+x[i];
while(a[i]<=x[i+1])
{
a[i]+=a[i-1];
}
}
for(int i=1;i<=n;i++)
{
cout<<a[i]<<" ";
}
cout<<"\n";
}

return 0;
}

D. Permutation Game

zhy女士讨论的思路甚至是对的。

大概是一个类似于线性dp的思路,以min(循环节长度, $k$ )为循环,这样循环大小不会超过 $2×10^5$

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
#include<bits/stdc++.h>

using namespace std;

long long t;
long long n,k,pb,ps;
long long a[200005],p[200005];
bool vis[200005];

long long score(long long x,long long k)
{
memset(vis,0,sizeof vis);

long long mx=0,sum=0;
while(!vis[x]&&k>0) //如果第一个循环节没有循环完并且还没有走k步
{
vis[x]=1;
mx=max(mx,sum+k*a[x]);
//sum+k*a[x]是走到这个点就不动了,与下一层循环里的再走一步再停下进行比较
sum+=a[x]; //走一步
k--; //还剩几步
x=p[x]; //下一个
}

return mx;
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>t;
while(t--)
{
long long n,k,pb,ps;
cin>>n>>k>>pb>>ps;
for(long long i=1;i<=n;i++)
{
cin>>p[i];
}
for(long long i=1;i<=n;i++)
{
cin>>a[i];
}
long long A=score(pb,k),B=score(ps,k);
if(A>B) //比较
{
cout<<"Bodya\n";
}
else if(A<B)
{
cout<<"Sasha\n";
}
else
{
cout<<"Draw\n";
}
}

return 0;
}

E. Cells Arrangement

烂透构造题hmm,你知道我看到代码时有多崩溃吗=(

oh,原来读题读错了,人家让我求max,我搁这儿求min。。。(笑不出来

虽然有很多别的构造方式但是这里介绍一下官方题解的最短方法

注意到:

$n=2$ 时,随便放即可

n=2.png

$n=3$ 时,在上一个的基础上最外圈的上面填一个点,与剩下两个的距离不相等且不等于1

n=3.png

$n=4$ 同理

在一张 $n×n$ 的图里,我们一共可以得到 $1$ ~ $2(n-1)$ 的 $2(n-1)$ 个不同曼哈顿距离对角线上的点与右下产生所有偶数距离,与右下上一个点产生所有奇数距离

其实也没有很难qwq,代码不需要注释了吧…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>

using namespace std;

int t,n;

int main()
{
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n-2;i++)
{
cout<<i<<" "<<i<<"\n";
}
cout<<n-1<<" "<<n<<"\n"<<n<<" "<<n<<"\n";
}

return 0;
}

F.Equal XOR Segments

感觉这次的题都有点浓厚数学的成分

本题我借鉴了一下题解,发现 $k>3$ 是没有必要的,因为我们可以把任意连续三个异或和相同的序列合并成一个,即 $x⊕x⊕x=x$,这样就可以减少两个子序列

同时 $k=2$ 时, $x⊕x=0$

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
#include<bits/stdc++.h>

using namespace std;

long long t;
long long n,q;
long long a[200005];

map<long long,vector<long long> > id;

int main()
{
cin>>t;
while(t--)
{
cin>>n>>q;

for(auto &i:id)
{
i.second.clear();
}
id[0].push_back(0);

for(long long i=1;i<=n;i++)
{
cin>>a[i];
a[i]^=a[i-1]; //异或前缀和
id[a[i]].push_back(i);
}

while(q--)
{
long long l,r;
cin>>l>>r;

//如果头尾的异或和一样,那么说明这个序列可以被分成2个异或和相等的区间
if(a[r]==a[l-1])
{
cout<<"YES\n";
continue;
}

//我们将[l,r]分为[l,s],[s+1,t]和[t+1,r], 我们必须检查a[l-1]=a[t]和a[s]=a[r]
//同时满足s<t
long long t= *--lower_bound(id[a[l-1]].begin(),id[a[l-1]].end(),r);
long long s= *lower_bound(id[a[r]].begin(),id[a[r]].end(),l);

if(t>s)
{
cout<<"YES\n";
}
else
{
cout<<"NO\n";
}
}
cout<<"\n";
}
}

G&H.Division + LCP

一个简化版一个强化版,合一起做

这题似乎需要一种名字叫做 Z函数(扩展kmp) 的东西

【模板】扩展 KMP/exKMP(Z 函数)

  • Z函数可以解决的问题:

    线性求解字符串str以第i位开始的后缀与str的最长公共前缀(LCP),Z[i]表示第i位开始的后缀与str的最长公共前缀

然后这个题目说让我们求把这个字符串分成k段的LCP最大值,其中有一个子段一定是原字符串的前缀,所以最后得到的k个子段也肯定是原字符串的前缀

如果 $k$ 不大于 $\sqrt{n}$ ,那么只需要枚举不超过 $\sqrt{n}$ 次;否则用二分算答案即可。

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
#include<bits/stdc++.h>

using namespace std;

int t,n;
int z[200005];
int ans[200005];

void Z(string s) //Z函数
{
int l=0,r=0;
for(int i=1;i<n;i++)
{
if(i<=r&&z[i-l]<r-i+1)
{
z[i]=z[i-l];
}
else
{
z[i]=max(0,r-i+1);
while(i+z[i]<n&&s[z[i]]==s[i+z[i]])
{
z[i]++;
}
}
if(i+z[i]-1>r)
{
l=i,r=i+z[i]-1;
}
}
}

int f(int len) //计算不小于len长度的最长前缀总数
{
int cnt=1;
for(int i=len;i<n;)
{
if(z[i]>=len)
{
cnt++,i+=len;
}
else
{
i++;
}
}
return cnt;
}

int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);

ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>t;
while(t--)
{
memset(z,0,sizeof z);
memset(ans,0,sizeof ans);

int l,r;
string s;
cin>>n>>l>>r;
cin>>s;

Z(s);

// for(int i=0;i<n;i++)
// {
// cout<<z[i]<<" ";
// }
// cout<<"\n";

//如果k>sqrt(n),那么len<sqrt(n)
int sq=ceil(sqrt(n));
for(int i=sq;i>0;i--) //直接枚举即可
{
int cn=f(i);
ans[cn]=max(ans[cn],i);
}
for(int i=n-1;i>0;i--)
{
ans[i]=max(ans[i],ans[i+1]);
}

//如果k<sqrt(n)
for(int i=1;i<=sq;i++)
{
int L=0,R=n/i;
while(L<R)
{
int mid=(L+R+1)/2;
if(f(mid)>=i)
{
L=mid;
}
else
{
R=mid-1;
}
}
ans[i]=L;
}
for(int i=l;i<=r;i++)
{
cout<<ans[i]<<" ";
}
cout<<"\n";
}

return 0;
}


Finished.

AtCoder Beginner Contest 350 赛后总结

比赛链接

RT。这十分恶毒的Microsoft Defender在我使用美丽的Dev-c++和Code::Blocks时疯狂地弹弹窗让我扫描病毒威胁,又由于洛谷在线IDE的格式非常不自动化(指tab要自己tab)因此是我罚时+=5*5min,(╯▔皿▔)╯。

赛时没做出来的会全码写注释

题解

A. Past ABCs

依旧非常简单,不多说了放代码。

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
#include<bits/stdc++.h>

using namespace std;

string s;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>s;
if(s[0]=='A'&&s[1]=='B'&&s[2]=='c')
{
int t=(s[3]-'0')*100+(s[4]-'0')*10+s[5]-'0';
if(t>=1&&t<350&&t!=316)
{
cout<<"Yes\n";
return 0;
}
}
cout<<"No\n";

return 0;
}


B. Dentist Aoki

这个题面真的好癫qwq

按题目模拟就行了没有什么思维含量,但是身为一名蒟蒻,还是交了一遍罚时,原因:没有本地运行代码导致for循环的起始写错了。其实可以用来熟悉bitset的使用(?),遂放一篇bitset的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>

using namespace std;

int n,q;
bitset<1005> b;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>q;
for(int i=0;i<q;i++)
{
int t;
cin>>t;
b.flip(t);
}
cout<<n-b.count()<<"\n"; //如果在一开始将bitset初始化为全1,就输出b.count()

return 0;
}

C. Sort

好了,就是这道令我罚时+=4的题力!(蠢蠢的很安心

四发都错在没有将swap了之后的id更改,谁能想到atcoder给的样例一个都查不出来(气

非常直白,存一下每个 $a_i$ 的索引,再遍历,如果 $a_i≠i$ 那么就把 $a_i$ 和 $a$ 中值为 $i$ 的元素交换,然后用vector存即可。

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
#include<bits/stdc++.h>

using namespace std;

int n;
int a[200005],id[200005];
vector<pair<int,int> > s;

int main()
{
cin>>n ;
for(int i=1;i<=n;i++)
{
cin>>a[i];
id[a[i]]=i;
}
for(int i=1;i<=n;i++)
{
if(a[i]!=i)
{
swap(a[i],a[id[i]]);
s.push_back({i,id[i]});
id[a[id[i]]]=id[i];
}
}
cout<<s.size()<<"\n" ;
for(int i=0;i<s.size();i++)
{
cout<<s[i].first<<" "<<s[i].second<<"\n";
}

return 0 ;
}

D. New Friends

某位zhy同学没有开龙龙导致WA掉,让我们恭喜他

就是让每一个连通图都变成完全图,所以使用 冰茶几 并查集维护,求出把所有图变成完全图之后的总边数,再减掉原有的变数即可。

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
#include<bits/stdc++.h>

using namespace std;

long long n,m;
long long par[200005],cnt[200005];
long long ans;

long long findf(long long x)
{
return (par[x]==x)?x:par[x]=findf(par[x]);
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>m;
for(long long i=1;i<=n;i++)
{
par[i]=i;
}
for(long long i=0;i<m;i++)
{
long long a,b;
cin>>a>>b;
par[findf(a)]=findf(b);
}
for(long long i=1;i<=n;i++)
{
cnt[findf(i)]++;
}

for(long long i=1;i<=n;i++)
{
ans+=(cnt[i]-1)*cnt[i]/2;
}

cout<<ans-m<<"\n";

return 0;
}


E. Toward 0

学校刚教完概率与期望,我却不会做,是__导致我这样的

看到1e18的数据范围直接被MLE劝退,愣是没有想到其实可以用map,是__导致我这样的!

骰子只有1-6的点数,所以每次到的值都是可以写作 $⌊\frac{𝑛}{2^𝑖3^𝑗5^𝑘}⌋$ (因为 $⌊\frac{⌊\frac{n}{x}⌋}{y}⌋=⌊\frac{⌊\frac{n}{y}⌋}{x}⌋=⌊\frac{n}{xy}⌋$ )

所以我们就可以聪明地使用map数组dp,得到以下递推公式:

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
#include<bits/stdc++.h>

using namespace std;

long long n,a,x,y;
map<long long,double> dp;

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>a>>x>>y;
dp[0]=0;
for(long long i=1;i<=n;i*=2)
{
for(long long j=1;i*j<=n;j*=3)
{
for(long long k=1;i*j*k<=n;k*=5)
{
dp[n/(i*j*k)]=0; //先新建可能取到的dp索引对应的dp数组的位置(因为使用map)
}
}
}

for(map<long long,double>::iterator i=dp.begin();i!=dp.end();i++)
{
if(!i->first)
{
continue;
}
double p=x+dp[i->first/a],q=6.0*y; //p是选择/a的期望,q是选择抛骰子的期望
for(int j=2;j<=6;j++)
{
q+=dp[i->first/j];
}
q/=5;
i->second=min(p,q); //递推,最小代价
}

cout<<fixed<<setprecision(6)<<dp[n]<<"\n";

return 0;
}


F. Transpose

非常经典的栈,试问Atcoder,为何E>F。

其实感觉难度不是很大,不知道为什么是绿并F题,但话说我一直都很讨厌这种 (((()))(()) 的题QAQ

同样也是直接模拟,但是首先利用栈来记录这个字符串所有括号的位置和字母是否最终被反转

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
#include<bits/stdc++.h>

using namespace std;

string s;
int p[500005]; //用于配对左括弧和右括弧的位置
stack<int> st; //用于存储左括弧

void output(int x,int y)
{
if(x<=y)
{
for(int i=x;i<=y;i++)
{
if(s[i]=='(')
{
if(p[i]!=i+1) //如果括号组内不为空
{
output(p[i]-1,i+1); //整体翻转输出
}
i=p[i]; //跳到下一组
}
else
{
cout<<s[i];
}
}
}
else
{
for(int i=x;i>=y;i--)
{
if(s[i]==')')
{
if(p[i]!=i-1)
{
output(p[i]+1,i-1); //整体翻转输出
}
i=p[i]; //跳到下一组
}
else
{
cout<<s[i];
}
}
}
}


int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>s;
for(int i=0;i<s.size();i++)
{
if(s[i]=='(')
{
st.push(i);
}
else if(s[i]==')')
{
p[st.top()]=i;
p[i]=st.top();
st.pop();
}
else
{
//如果这个字符在奇数个括号组里,就说明它会被大小写反转,偶数则会被反转回来
if(st.size()&1)
{
if(s[i]>='a'&&s[i]<='z')
{
s[i]-='a'-'A';
}
else
{
s[i]-='A'-'a';
}
}
}
}
output(0,s.size()-1);
cout<<"\n";

return 0;
}

G. Mediator

一道被建议评橙的G题(现在是蓝),Atcoder怎么总能水暴力(?

于是投喂一篇暴力(它甚至不需要吸氧就能过我真的

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
// Let us water a brute force ! 
#include<bits/stdc++.h>

using namespace std;

const long long mod=998244353;

long long n,q;
vector<long long> e[200005];
bool t[200005];

void geti(long long &op,long long &u,long long &v,long long xl) //如题
{
op=1+(((op*(1+xl))%mod)%2);
u=1+(((u*(1+xl))%mod)%n);
v=1+(((v*(1+xl))%mod)%n);
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>q;
long long xl=0;

while(q--)
{
memset(t,0,sizeof t);

long long op,u,v;
cin>>op>>u>>v;

geti(op,u,v,xl);

if(op==1)
{
e[v].push_back(u);
e[u].push_back(v); //存边
continue;
}

for(long long i:e[u]) //遍历与u相邻的点
{
t[i]=1;
}

bool flag=0;
for(long long i:e[v])
{
if(t[i]) //判断是否与v相邻
{
cout<<i<<"\n";
xl=i;
flag=1;
break;
}
}
if(!flag) //否则输出0
{
cout<<0<<"\n";
xl=0;
}
}

return 0;
}

好吧下面是正解

使用启发式合并。如果两个点有一个共同的邻点,那么有三种可能:

  1. u是v的祖父
  2. v是u的祖父
  3. u和v是兄弟

所以依然使用带权并查集进行维护。

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
#include<bits/stdc++.h>

using namespace std;

const long long mod=998244353;

long long n,q;
vector<long long> e[200005];
int f[200005],par[200005],siz[200005];
//分别为点i的父亲,点i的根,点i的深度

void geti(long long &op,long long &u,long long &v,long long xl)
{
op=1+(((op*(1+xl))%mod)%2);
u=1+(((u*(1+xl))%mod)%n);
v=1+(((v*(1+xl))%mod)%n);
}

void dfs(int x) //遍历并确定每个点的父亲节点
{
for(auto i:e[x])
{
if(i==f[x])
{
continue;
}
f[i]=x;
dfs(i);
}
}

int query(int x,int y)
{
if(f[x]==f[y]&&f[x]) //如果两点为兄弟节点
{
return f[x];
}
if(f[f[x]]==y) //x的祖父为y
{
return f[x];
}
if(f[f[y]]==x) //y的祖父为x
{
return f[y];
}
return 0;
}

int find(int x) //找根
{
return x==par[x]?x:par[x]=find(par[x]);
}

void merge(int x,int y) //合并
{
int fx=find(x),fy=find(y);
if(fx==fy)
{
return;
}
par[fy]=fx;
siz[fx]+=siz[fy];
siz[fy]=0;
}

int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);

cin>>n>>q;
for(int i=1;i<=n;i++) //初始化
{
par[i]=i;
siz[i]=1;
}

long long xl=0;

while(q--)
{
long long op,u,v;
cin>>op>>u>>v;

geti(op,u,v,xl);

if(op==1)
{
if(siz[find(u)]<siz[find(v)])
{
swap(u,v);
}
e[v].push_back(u);
e[u].push_back(v);
f[v]=u;
merge(u,v);
dfs(v);
continue;
}

xl=query(u,v);
cout<<xl<<"\n";
}

return 0;
}

wow写完啦!!!!!

感谢观看呀

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×