Codeforces Round 1029 (Div. 3) A-G 题解
感觉题目出的很不错的一场div3
f题赛时看成了,a数组是从1到n的排列,想了半天是什么妙妙题目
A. False Alarm
找第一个和最后一个,关着的门的位置
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,x;
cin>>n>>x;
vector<int> a(n+1);
int p1=0,p2=0;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]){
p2=i;
if(!p1) p1=i;
}
}
if(p1==0){
cout<<"NO\n";
}
else{
if(x>=p2-p1+1){
cout<<"YES\n";
}
else cout<<"NO\n";
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
B. Shrink
显然可以把最大的放在中间,例如 1 3 5 7 9 8 6 4 2
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<int> a(n+1);
int now=1;
for(int l=1,r=n;l<=r;l++,r--){
a[l]=now++;
if(l==r) break;
a[r]=now++;
}
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
C. Cool Partition
从前往后贪心,只要当前这一段里的数,能够覆盖掉上一段中所有出现的数,就可以切一刀,把当前这一段切下来,过程中可以用map或set维护当前段和上一段出现的值
这里的覆盖指,上一段出现的所有数,在当前段都出现过
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
int ans=0;
map<int,int> mp,tmp;
vector<int> a(n+10);
for(int i=1;i<=n;i++){
cin>>a[i];
tmp[a[i]]=1;
if(mp.count(a[i])){
mp.erase(a[i]);
}
if(mp.size()==0){
ans++;
mp=tmp;
tmp.clear();
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
D. Retaliation
从数组全0开始反推,假设进行了x次操作一和y次操作二,且x>y,则
可以把x分成:y+ (x-y)
此时操作可以看成,y次操作一和操作二,以及(x-y)次操作一
y次操作一和操作二后,数组的值一定相同,值一定是 y*(1+n)
剩下的操作一定是操作一。
因为此时数组的值相同,且进行的全是操作1,则数组一定是递增的。
此时可以得到结论,如果数组不是有序的,则一定非法
如果数组是递减的,说明操作二的数量更多,为了统一,可以reverse数组,把递减变成递增,交换操作二和操作一的次数。
此时,因为每次操作一,都会让相邻的两个数差值增加1,所以,数组相邻两个数的差值一定相同,且操作一的次数就是这个差值(因为每次操作一就会让差值+1)
设差值为val,val就是(x-y)
此时,可以让每个位置,都减去 i * val, 也就是(x-y)次操作一产生的数值
此时,数组里的每个数应该相同,且是(1+n)的倍数。否则非法
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<int> a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
if(!is_sorted(a.begin()+1,a.end())){
for(int l=1,r=n;l<=r;l++,r--){
swap(a[l],a[r]);
}
if(!is_sorted(a.begin()+1,a.end())){
cout<<"NO\n";
return;
}
}
sort(a.begin()+1,a.end());
int val=a[2]-a[1];
for(int i=2;i<=n;i++){
if(a[i]-a[i-1]!=val){
cout<<"NO\n";
return;
}
}
//val为公差的等差数列
for(int i=1;i<=n;i++){
a[i]-=i*val;
if(a[i]<0){
cout<<"NO\n";
return;
}
}
for(int i=2;i<=n;i++){
if(a[i]!=a[i-1]){
cout<<"NO\n";
return;
}
}
if(a[1]%(1+n)!=0){
cout<<"NO\n";
return;
}
cout<<"YES\n";
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
E. Lost Soul
先考虑没有删除操作的情况
假设有一个数x
在a,b数组中有三种情况:
x , x
_ , _
或
x , _
x , _
或
x , _ , _
x , _ , x (x的上下位置可以互换)
假设第一个x出现的位置是i,则这三种情况都可以让,前i个位置匹配上
现在考虑对第1,3中情况,删除一些东西,
x , _ , _ , _ , x
_ , _ , _ , _ , _ ,
可以把中间三列全删掉
x , _ , _ , _ , _
x , _ , _ , _ , x
可以把中间三列全删掉
假设第一个x出现的位置是i,则这三种情况,都可以删除一些数,让前i个位置匹配上
代码实现可以从后往前,用map分别维护a和b数组
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n;
cin>>n;
vector<int> a(n+1),b(n+1);
int ans=0;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
map<int,int> mpa,mpb;
for(int i=n;i>=1;i--){
if(mpa[a[i]]) ans=max(ans,i);
if(mpb[b[i]]) ans=max(ans,i);
if(a[i]==b[i]) ans=max(ans,i);
if(i>1 && (mpb[a[i-1]] || mpa[b[i-1]])) ans=max(ans,i-1);
mpa[a[i]]++;
mpb[b[i]]++;
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
F. Wildflower
因为只有1,2两个值,则最多有两个叶节点
如果叶节点只有一个,则树是一条链,答案是2^n
如果叶节点有两个,分别是a,b,u=lca(a,b)
则 u 和 u 以上的点,可以随便选
对于a和b,假设a是深度更深的
设b到u中间有t个点,(算上b,不算u)
如果a等于1,则b一定等于2,此时从b开始,t个点一定是确定的,从a开始,t+1个点一定是确定的
如果a等于2,则b一定等于1,此时从b开始,t个点一定是确定的,从a开始,t个点一定是确定的
不确定的点,选1选2都可
在纸上画一个,有两个叶子的树,手推一下就能看出来
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 1e9+7;
void solve(){
int n;
cin>>n;
vector<vector<int>> g(n+10);
vector<int>fa(n+10),dep(n+10),sz(n+10),son(n+10),top(n+10);
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
auto dfs1=[&](auto dfs1,int u,int pre)->void {
fa[u]=pre;
dep[u]=dep[pre]+1;
sz[u]=1;
for(auto v:g[u]){
if(v==pre) continue;
dfs1(dfs1,v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
};
auto dfs2=[&](auto dfs2,int u,int tp)->void {
top[u]=tp;
if(!son[u]) return;
dfs2(dfs2,son[u],tp);
for(auto v:g[u]){
if(v==son[u] || v==fa[u]) continue;
dfs2(dfs2,v,v);
}
};
auto lca=[&](int a,int b)->int {
while(top[a]!=top[b]){
if(dep[top[a]]<dep[top[b]]) swap(a,b);
a=fa[top[a]];
}
if(dep[a]>dep[b]) return b;
else return a;
};
dfs1(dfs1,1,0);
dfs2(dfs2,1,1);
int leaf=0,a=0,b=0;
for(int i=2;i<=n;i++){
if(g[i].size()==1){
leaf++;
if(a==0) a=i;
else b=i;
}
}
int ans=1;
if(leaf>2){
cout<<0<<endl;
return;
}
if(leaf==1){
for(int i=1;i<=n;i++){
ans*=2;
ans=ans%mod;
}
cout<<ans<<endl;
return;
}
int u=lca(a,b);
for(int i=1;i<=dep[u];i++){
ans*=2;
ans%=mod;
}
a=dep[a],b=dep[b];
if(a==b){
cout<<ans*2%mod<<endl;
return;
}
if(a<b) swap(a,b);//a是较深的点
int diff=a-b-1;
ans*=3;
for(int i=1;i<=diff;i++){
ans=ans*2%mod;
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}
G. Omg Graph
两种做法,最短路和并查集,只写了最短路,并查集之后补
稍微改变题目中最短路的定义,定义1到n的距离是:从1到n路径上的边权最大值。
要找到从1到n的最短路径,即让从1到n路径上的边权最大值最小
dijkstra找到,dist1:从1到每个点的最短路径, dist2:和从n到每个点的最短路径
再枚举每一条边{u,v,w},w作为答案中的min值,max值即为,max{w,dist1[u],dist2[v]}
枚举 (min+max)的最小值,即为答案
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
using pii=pair<int,int>;
using ll = long long;
using ull = unsigned long long;
//using i128 = __int128_t;
const ll inf = 1e18;
const int mod = 998244353;
void solve(){
int n,m;
cin>>n>>m;
vector<vector<pii>> g(n+10);
while(m--){
int a,b,w;
cin>>a>>b>>w;
g[a].push_back({b,w});
g[b].push_back({a,w});
}
auto dij=[&](int rt) {
vector<int> dist(n+10,inf),st(n+10);
dist[rt]=0;
priority_queue<pii,vector<pii>,greater<pii>> q;
q.push({0,rt});
while(q.size()){
auto [w,u]=q.top();
q.pop();
if(st[u]) continue;
st[u]=1;
for(auto [v,d]:g[u]){
if(max(d,w)<dist[v]){
dist[v]=max(d,w);
q.push({max(d,w),v});
}
}
}
return dist;
};
auto dist1=dij(1),dist2=dij(n);
int ans=inf;
for(int u=1;u<=n;u++){
for(auto [v,w]:g[u]){
ans=min(ans,max({dist1[u],dist2[v],w})+w);
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int ct=1;
cin>>ct;
while(ct--){
solve();
}
return 0;
}

浙公网安备 33010602011771号