CF1941 題解

yshpdyt發表於2024-03-12

前言:

本場 \(div3\) 屬於是閱讀理解了。

本人於賽後 \(12\) 分 和 \(18\) 分過掉了 \(G\)\(F\),警鐘敲爛。

A

題意

兩個陣列 \(a\)\(b\) 長為 \(n,m\),判斷多少種組合方式滿足 \(a_i+b_j \le k\),其中 \(n,m\le 100\)

Sol

\(n,m\) 很小直接 \(O(nm)\) 暴力匹配。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,s;
ll a[N],b[N];
void sol(){
    cin>>n>>m>>s;
    ll res=0;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=m;i++){
        cin>>b[i];
        for(int j=1;j<=n;j++){
            if(a[j]+b[i]<=s)res++;
        }
    }
    cout<<res<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll T;
    cin>>T;
    while(T--)sol();

    return 0;
}

B

題意

有一個序列,你可以執行如下操作任意次:

選擇一個 \(2\le i \le n-1\),然後執行:

\[a_{i-1} \to a_{i-1}-1 \]

\[a_{i} \to a_{i}-2 \]

\[a_{i+1} \to a_{i+1}-1 \]

是否可能將所有元素變成 \(0\)

Sol

注意到使 \(a_1\) 變成 \(0\)\(i=2\) 操作,不妨更改操作為:

選擇一個 \(1\le i \le n-2\),然後執行:

\[a_{i} \to a_{i}-1 \]

\[a_{i+1} \to a_{i+1}-2 \]

\[a_{i+2} \to a_{i+2}-1 \]

這樣每個元素只能改變後面而無法改變前面。於是從前往後掃直接模擬操作讓當前元素變成 \(0\),發現當前元素為負數,說明無解。如果最後 \(a_{n-1}\neq 0\)\(a_{n} \neq 0\) 也說明無解,否則有解。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n,a[N];
void sol(){
    cin>>n;
    ll res=0;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n-2;i++){
        if(a[i]<0){
            cout<<"NO\n";
            return;
        }
        a[i+1]-=a[i]*2;
        a[i+2]-=a[i];
        a[i]=0;
    }
    if(a[n-1]!=0||a[n]!=0){
        cout<<"NO\n";
        return ;
    }
    cout<<"YES\n";
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll T;
    cin>>T;
    while(T--)sol();

    return 0;
}

C

題意

一個字串,如果其連續子串含有 piemap,說明該字串不優美。你可以每次選擇刪除一個字母,最最小操作次數讓該字串變得優美。

Sol

注意到 piemap 都含有 p ,我們只需要刪除一個字母就可以讓其沒有這兩個單詞,所以肯定刪除 p 是最划算的。

從左到右掃是否有這兩個單詞,如果有刪去一個 p 即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 2000005
#define endl "\n" 
#define fi fisrt
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n;
string s;
void sol(){
    cin>>n>>s;
    s=" "+s;
    ll res=0;
    for(int i=1;i<=n;i++){
        if(s[i]=='p'){
            if(i>=3&&s[i-1]=='a'&&s[i-2]=='m')res++;
            else if(i+2<=n&&s[i+1]=='i'&&s[i+2]=='e')res++;
        }
    }
    cout<<res<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll T;
    cin>>T;
    while(T--)sol();

    return 0;
}

D

題意

一個有 \(n\) 個數字的輪盤,數字順時針依次排列,給定旋轉次數和起始點。

對於每次旋轉,給定旋轉長度,以及三種旋轉方向:

順時針,逆時針,和任意方向。

你需要給出最後所有可能的結束點,按從小到大輸出。

Sol

一個點 \(x\) 順時針走 \(k\) 步位於的點是 \(((x+k-1) \mod n)+1\)
一個點 \(x\) 逆時針走 \(k\) 步位於的點是 \(((x+n-k-1)\mod)+1\)

開一個佇列,取出上一次的終點,按照題意旋轉後再塞進去即可。

注意到可能有重複的,可以開個桶記錄 i 次操作點 x 是否已經旋轉過。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 2000005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

queue<pair<ll,ll> >q;
ll n,m,st;
set<ll>res;
map<pair<ll,ll>,ll>mp;
void sol(){
    while(!q.empty())q.pop();
    res.clear();
    cin>>n>>m>>st; 
    mp.clear();
    q.push({st,0});
    for(int i=1;i<=m;i++){
        char c;
        ll x;
        cin>>x>>c;
        while(!q.empty()){
            auto t=q.front();
            if(t.se==i)break;;
            q.pop();
            if(mp[t])continue;
            mp[t]=1;\
            if(c=='0'){
                q.push({(t.fi+x-1)%n+1,i});
                continue;
            }   
            if(c=='1'){
                q.push({(t.fi+n-x-1)%n+1,i});
                continue;
            }         
            q.push({(t.fi+x-1)%n+1,i});
            q.push({(t.fi+n-x-1)%n+1,i});
        }
    }
    while(!q.empty()){
        res.insert(q.front().fi);
        q.pop();
    }
    cout<<res.size()<<endl;
    for(auto y:res)cout<<y<<" ";
    cout<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll T;
    cin>>T;
    while(T--)sol();

    return 0;
}

E

題意

一個 \(n\times m\) 大小的河,\(a_{i,j}=h\),表示當前位置河的深度是\(h\),兩邊(\(a_{i,1}\)\(a_{i,n}\))是平坦的陸地,也就是深度為 \(0\),你可以選擇一行修一座寬為 \(1\) 的橋,橋需要滿足以下條件:

  1. 選擇若干位置作為橋墩,特別的,兩岸必須作為橋墩。
  2. 兩個橋墩中間相隔的位置不超過 \(d\)
  3. 選擇 \((i,j)\) 位置修橋,需要花費 \(a_{i,j}+1\) 的代價。

現在你需要修一座連續的寬為 \(k\) 的大橋,也就是 \(k\) 座寬為 \(1\) 的橋拼起來,不要求這些橋的橋墩位於同一列,求最小花費。

資料範圍:\(n\times m \le 2\times 10^5\)

Sol

注意到每一行的修橋最小花費方案獨立,可以分開求解。

是個比較裸的dp,設 \(f[j]\) 表示 在 \((i,j)\) 位置必須修橋的最小花費,不難得到:

\[f[j]=\min _{k=j-d-1}^{j-1} f[k]+a[i][j]+1 \]

每一行的答案就是 \(f[m]\)

然後這個是 \(O(m^2)\),肯定過不去,容易發現可以單調佇列最佳化,不會的可以去做做滑動視窗。動態維護前 \(d\) 個位置的答案,並且保證隊首到隊尾單調遞增。

具體來說,隊頭與當前位置超過 \(d\) 的直接彈出,然後透過隊首轉移,因為隊首一定是符合條件的最優解,然後當前答案塞入佇列尾部,如果當前答案比尾部答案小,就把尾部彈出,因為尾部一定不會成為答案了。

求出每一行的修橋最小費用後就簡單了,記錄一下當前選擇連續 \(k\) 行的答案,最後取最小值即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 2000005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll f[N],g[N],a[N];
ll n,m,k,d;
deque<ll>q;
void check(ll x){
    while (!q.empty())q.pop_front();
    g[1]=0;
    q.push_front(1);
    for(int i=1;i<=m;i++){
        while(!q.empty()&&i-q.front()-1>d)q.pop_front();
        g[i]=g[q.front()]+(a[i]+1);
        while (!q.empty()&&g[i]<g[q.back()])q.pop_back();
        q.push_back(i);
    }
    f[x]=g[m];
}
void sol(){
    cin>>n>>m>>k>>d;
    ll res=inf,sum=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[j];
        }
        check(i);
        sum+=f[i];
        if(i<k)continue;
        res=min(res,sum);
        sum-=f[i-k+1];
    }
    cout<<res<<endl;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll T;
    cin>>T;
    while(T--)sol();

    return 0;
}

F

本人由於閒的沒事搞了個去重,所以沒有完全清空陣列,導致賽時虛空對拍 \(1h22min\) 沒有發現異常,痛失 AK。

題意

三個序列 \(a,b,c\),長度分別為 \(n,m,k\),其中 \(a\) 嚴格單調遞增。
你可以選擇最多一組(也就是可以不選)\(b_i\)\(c_j\) ,把 \(b_i+c_j\) 插入到 \(a\) 當中,並且仍然保證單調遞增的順序不變,求插入後如下式子的值:

\[\min(\max_{i=2}^{n+1}(a_i-a_{i-1})) \]

也就是最小化相鄰兩項之間差的最大值。

Sol

最小化最大值,不難想到二分。

二分這個差值的最大值 \(mid\),然後先 \(O(n)\) 掃一遍 \(a\) 陣列,如果有兩個及以上的相鄰項差大於 \(mid\) 就是不合法,如果沒有差大於 \(mid\) 就是合法,可以先判斷掉,然後剩下只有一項的情況。

設唯一的 \(p\)\(a[p+1]-a[p]>mid\)

這種情況要在 \(b,c\) 裡面選,為了方便可以先把 \(b,c\) 排序去重,不難得到一個和的範圍:\([a[p+1]-mid,a[p]+mid]\),如果和不在這個範圍內,一定會使得存在差大於 \(mid\),同時這個區間可能是無解的,也可以提前判斷。

知道了這點,我們可以 \(O(m)\) 列舉 \(b\) 的元素,然後二分 \(t_1=a[p+1]-mid-b[i]\)\(t_2=a[p]+mid-b[i]\)\(c\) 中的位置,用 lower_bound 實現。滿足以下任意條件即可說明有解:

\[t_1-t_2 \ge 1 \]

\[a[p+1]-mid-b[i]=c[t_1] \]

\[a[p+1]-mid-b[i]=c[t_2] \]

具體原因畫個圖就出來了,也很好感性理解,不多介紹。

時間複雜度為 \(O(m \log k \log V )\),隨便透過。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 400005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,m,k;
ll a[N],b[N],c[N];
bool check(ll x){
    ll p=0;
    for(int i=1;i<n;i++){
        if(a[i+1]-a[i]>x){
            if(p)return 0;
            p=i;
        }
    }
    if(!p)return 1;
    if(a[p+1]-a[p]>2*x)return 0;
    ll le=a[p+1]-x;
    ll ri=a[p]+x;
    for(int i=1;i<=m;i++){
        if(b[i]>ri)break;
        ll t=b[i];
        ll t1=lower_bound(c+1,c+k+1,le-t)-(c);
        ll t2=lower_bound(c+1,c+k+1,ri-t)-(c);
        if(abs(t2-t1)>0||le-t==c[t1]||ri-t==c[t2])return 1;
    }
    return 0;
}
void sol(){
    cin>>n>>m>>k;
    ll pm=m;
    ll pk=k;
    ll l=0,r=-inf;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(i>1)r=max(r,a[i]-a[i-1]);
    }
    for(int i=1;i<=m;i++)cin>>b[i];
    for(int i=1;i<=k;i++)cin>>c[i];
    sort(b+1,b+m+1);
    m=unique(b+1,b+m+1)-(b+1);
    sort(c+1,c+k+1);
    k=unique(c+1,c+k+1)-(c+1);
    while(l<r){
        ll mid=(l+r)/2;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l<<endl;
    for(int i=1;i<=n;i++)a[i]=0;
    for(int i=1;i<=pm;i++)b[i]=0;
    for(int i=1;i<=pk;i++)c[i]=0;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll T;
    cin>>T;
    while(T--)sol();

    return 0;
}

G