CF1039 ABCDEF

A.False Alarm


原题链接

题意简述

\(n\) 个门,门的状态是开或关,你有且仅有一次机会使得关的门打开,每次打开时间长达 \(x(s)\),当过了 \(x(s)\)后由按钮打开的门自动关闭,求是否可能通过所有门?

解题思路

记录一下最早关闭的门和最晚的门间隔时间,当且仅当间隔时间小于等于 \(x\) 的时候可以通过

AC code

void solve(){
    int n,m;cin>>n>>m;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    int st=-1,ed=-1;
    for(int i=1;i<=n;i++){
        if(a[i]==1&&st==-1) st=i;
        if(a[i]==1) ed=i;
    } 
    cout<<(ed-st+1>m?"NO":"YES")<<endl;
}

B.Shrink


原题链接

题意简述

给你一个长度 \(n\),请构造一个排列,使得极大化以下操作次数,操作:选择一个位置满足 $2 \leq i \leq n-1 $,且 $a_{i-1} < a_i < a_{i+1} $,删掉这个数。

解题思路

显然想要删数尽可能多就是要删数后峰值也尽可能多,然后删数从最大的数开始删这样肯定最优。所以考虑从大到小把每个数添加到极大值的左右。那么按奇偶每次添加到数组开头或末尾即可。

AC code

void solve(){
    int n;cin>>n;
    deque<int>Q;
    for(int i=n;i>=1;i--){
        if(i&1) Q.push_front(i);
        else Q.push_back(i);
    }
    for(auto &x:Q) cout<<x<<' ';
    cout<<endl;
}

C.Cool Partition


原题链接

题意简述

给定一个数组,要求极大化划分次数,使得每一段满足以下性质,
1.段非空,2.当前段包含上一段所有元素种类,3.划分时不改变原数组顺序
求划分最大次数

解题思路

贪心的想,能划分就划分,那么第一段一定是数组第一个元素,然后顺序遍历不断添加元素到新的一段直到出现上一段所有元素后增加段数,然后再以新添加的段作为上一段模拟。这样模拟涉及种类数和频繁删除添加操作,考虑用两个 \(set\) 容器,一个存储可能输下一段的元素不断添加,一个储存上一段元素不断删除,当第二个 set 清空后交换两容器即可

AC code

void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    set<int>x,y;
    ll cnt=1;
    x.emplace(a[1]);
    for(int i=2;i<=n;i++){
        y.emplace(a[i]);//虚拟设新的一段
        if(x.count(a[i])) x.erase(a[i]);
        if(x.empty()){
            cnt++;
            swap(x,y);
        }
    }
    if(x.empty()) cnt++;
    cout<<cnt<<endl;
}

D.Retaliation


原题链接

题意简述

给定一个长为 \(n\) 的数组 \(a_1,a_2...,a_n\),当 数组所有元素归零时"爆炸",然后允许你对所有元素同时进行 $ a_i = a_i - i $ 或 \(a_i = a_i - (n-i+1)\)任意次操作,求最终是否可能爆炸?

解题思路

就是求解是否对于每个 \(a_i\) 都存在一个特定的 \(x\)\(y\) ,使得 $x·i+y·(n-i+1)=a_i $成立,由于对于每个数组中元素 \(i\)\(n-i+1\) 是唯一确定的值,所以解这个二元一次方程组只需要两个元素即可,然后数组满足长度一定大于 2 ,所以可以求解。考虑求解方便性,选用两个较为方便的位置,观察到 \(a_1+a_n=(n+1)·(x+y)\),\(a_{i+1}-a_i=x-y\),这样求解出唯一的 \(x\)\(y\),判定一下 \(x\)\(y\) 的合法性然后带回原数组验证即可

AC code

void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    if((a[1]+a[n])%(n+1)!=0) {cout<<"NO"<<endl;return;}
    int sum=(a[1]+a[n])/(n+1);//x+y
    int sub=a[2]-a[1];//x-y
    int x=(sum+sub)/2;
    int y=(sum-sub)/2;
    if(x<0||y<0) {cout<<"NO"<<endl;return;}
    for(int i=1;i<=n;i++){
        if(x*i+y*(n-i+1)!=a[i]) {cout<<"NO"<<endl;return;}
    }
    cout<<"YES"<<endl;
}

E.Lost Soul


原题链接

题意简述

给你两个数组,允许你进行以下操作任意次,\(a_i=b_{i+1},b_i=a_{i+1}\),最后要求极大化$ a_j=b_j $的列数,在操作开始前允许且仅允许一次删除任意列的操作。

解题思路

注意到,如果 \(a_j=b_j\) 成立,那么可以通过 \(a_{j-1}=b_j,b_{j-1}=a_j\)这样操作任意次,那么一定可以使得 \(a_j=b_j\)前面 \(j\) 个数都满足 \(a_j=b_j\),所以倒叙考虑最早满足可以使得 \(a_j=b_j\)的下标 \(j\),那么什么时候可以满足呢,又观察到沿当前数对角线 a_i,b_i+1,a_i+2 这样的位置如果与 b_i 相等,那么就一定可以成为合法的 \(a_i=b_i\) 的下标,然后由于允许在所有操作开始前删去一列,那么我们考虑删去当前枚举的后一列,这样如果当前枚举 \(a_i\) 后一列的合法对角线出现的数字是 \(b_i\),那么一定可行。因为涉及频繁删除和查找数是否存在,考虑使用 multiset 容器,那么就得到以下会 TLE 的代码

TLE code

void solve(){
    int n;cin>>n;
    vector<int>a(n+1),b(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];
    multiset<int>_0,_1;
    int ans=0;
    for(int i=n;i>=1;i--){
        if(i&1){
            if(i+1<=n){
                _0.erase(_0.find(a[i+1]));
                _1.erase(_1.find(b[i+1]));
                if(_0.count(b[i])||_1.count(a[i])) ans=max(ans,i);//删第i+1列
                _0.emplace(a[i+1]);
                _1.emplace(b[i+1]);
            }
            _1.emplace(a[i]);
            _0.emplace(b[i]);
            if(_1.count(b[i])||_0.count(a[i])) ans=max(ans,i);//不删数
        } 
        else{
            if(i+1<=n){
                _1.erase(_1.find(a[i+1]));
                _0.erase(_0.find(b[i+1]));
                if(_1.count(b[i])||_0.count(a[i])) ans=max(ans,i);//删第i+1列
                _1.emplace(a[i+1]);
                _0.emplace(b[i+1]);
            }
            _0.emplace(a[i]);
            _1.emplace(b[i]);
            if(_0.count(b[i])||_1.count(a[i])) ans=max(ans,i);//不删数
        } 
    }
    cout<<ans<<endl;
}

优化思路

\(multiset\) 常数和 \(lg\) 级别复杂度太大了,这题 \(a_i\) 是不会超过 \(n\) 的,那么我们考虑用静态数组平替每一个 \(multiset\)

AC code

const int N=2e5+5;
int mpA[N],mpB[N];
void solve(){
    int n;cin>>n;
    vector<int>a(n+1),b(n+1);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];
    int ans=0;
    for(int i=n;i>=1;i--){
        if(i&1){
            if(i+1<=n){
                --mpB[a[i+1]];
                --mpA[b[i+1]];
                if(mpB[b[i]]||mpA[a[i]]) ans=max(ans,i);//删第i+1列
                ++mpB[a[i+1]];
                ++mpA[b[i+1]];
            }
            ++mpA[a[i]];
            ++mpB[b[i]];
            if(mpA[b[i]]||mpB[a[i]]) ans=max(ans,i);//不删数
        } 
        else{
            if(i+1<=n){
                --mpA[a[i+1]];
                --mpB[b[i+1]];
                if(mpA[b[i]]||mpB[a[i]]) ans=max(ans,i);//删第i+1列
                ++mpA[a[i+1]];
                ++mpB[b[i+1]];
            }
            ++mpB[a[i]];
            ++mpA[b[i]];
            if(mpB[b[i]]||mpA[a[i]]) ans=max(ans,i);//不删数
        } 
    }
    cout<<ans<<endl;
    fill(mpA,mpA+1+n,0);
    fill(mpB,mpB+1+n,0);
}

F.Wildflower


原题链接

题意简述

给你一棵树,以 \(1\) 为根节点,每个节点的值都可以是 \({1,2}\),记 s_i 是 \(i\) 的子树值之和,如果树 \(s_1,s_2,...s_n\)中没有重复的数,那么这个树是特殊的,求使得这个树是特殊的数的节点取值的方式有多少种?

解题思路

因为每个节点取值最多有两种,所以叶子节点数一定要恒小于2的,如果叶子节点数大于 3 ,那么那么叶子节点取值必有一个与两外两个重复,方案数是 0 种。如果只有一个叶子节点,由于从叶子节点到根直链上的数满足递增,所以无论如何取值都不重复,方案数是 \(2^n\) 种。如果叶子节点数有两个。
分类考虑,第一种左右叶子到他们最近公共祖先距离相等,那么左右叶子一定是1,2 2 2 2...,2 2 2 2...这种赋值方式。所以此时种类数就是 \(2^{dep_{LCA(L,R)}}·2\)
第二种,左右叶子节点到最近公共祖先距离不相等,如果长链先取 1 2 2 2一直到和短链长度相同时那么此时取值方式 有 \(2^{abs(r-l)-1}\)种,如果长链先取 2 2 2 2一直到和短链长度相同,此时取值方式有 \(2^{abs(r-l)}\)种,所以此时种类数就是 \(2^{dep_{LCA(L,R)}+abs(r-l-1)·3}\)种,综上,
\( ans(u,v,n) = \begin{cases} 2^n & leafs=1\\ 2^{dep_{LCA(u,v)}}·2 & leafs=2 \land dep_u = dep_v\\ 2^{dep_{LCA(u,v)}}·2^{abs(u-v-1)}·3 & leafs=2 \land dep_u \neq dep_v \\ 0 & leafs\neq2 \end{cases} \)

AC code

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
const ll mod=1e9+7;
const int N=2e5+5;
vector<int>G[N];
vector<int>pow_2(N);
int d[N],fa[N],n;
int anc[N][25];
void dfs(int u,int f){
    fa[u]=f;
    d[u]=d[f]+1;
    for(auto &v:G[u]){
        if(f==v) continue;
        anc[v][0]=u;
        dfs(v,u);
    }
}
void init(){
    for(int j=1;j<=21;j++){
        for(int i=1;i<=n;i++){
            anc[i][j]=anc[anc[i][j-1]][j-1];
        }
    }
}
int LCA(int u,int v){
    if(d[u]<d[v]) swap(u,v);
    for(int i=21;i>=0;i--){
        if(d[anc[u][i]]>=d[v]){
            u=anc[u][i];
        }
    }
    if(u==v) return u;
    for(int i=21;i>=0;i--){
        if(anc[u][i]!=anc[v][i]){
            u=anc[u][i];
            v=anc[v][i];
        }
    }
    return anc[u][0];
}//倍增LCA板子
void clearTest(){
    fill(d+1,d+1+n,0);
    fill(fa+1,fa+1+n,0);
    fill(G+1,G+1+n,vector<int>());
    for(int i=1;i<=n;i++){
        for(int j=0;j<=21;j++){
            anc[i][j]=0;
        }
    }
}
void solve(){
    cin>>n;
    for(int i=1;i<n;i++){
        int x,y;cin>>x>>y;
        G[x].push_back(y);
        G[y].push_back(x);
    }
    dfs(1,0);
    init();
    vector<int>leaf;
    for(int i=2;i<=n;i++){
        if(G[i].size()==1) leaf.push_back(i);
    }
    if(leaf.size()==1){
        cout<<pow_2[n]<<endl;
        clearTest();
        return;
    }
    if(leaf.size()!=2){
        cout<<0<<endl;
        clearTest();
        return;
    }
    int u=leaf[0],v=leaf[1];
    u=LCA(u,v);
    int l=d[leaf[0]]-d[u];//左链到LCA的距离
    int r=d[leaf[1]]-d[u];//右链到LCA的距离
    ll f;//左右链的可能性
    if(l==r) f=2;
    else f=3LL*pow_2[abs(l-r)-1]%mod;//2^abs(r-l)+2^(abs(r-1)-1)=3*(abs(r-l)-1);
    ll ans=f*pow_2[d[u]]%mod;
    cout<<ans<<endl;
    clearTest();
}
int main(){
    cin.tie(0)->ios::sync_with_stdio(false);
    pow_2[0]=1;
    for(int i=1;i<N;i++) pow_2[i]=pow_2[i-1]*2%mod;//预处理2的幂次
    int T=1;cin>>T;
    while(T--) solve();
    return 0;
}
posted @ 2025-06-09 22:58  usedchang  阅读(25)  评论(0)    收藏  举报