CF1455 題解

A

\(g(x)\) 實際是求原數和去除後綴 \(0\) 後的數的比值,發現第一個不同的值一定會在 \(10^x\) 達到,所以答案就是字符串長度。

B

我們先假設走了 \(m\) 步,並且都是走第一種,那麼相當於就是走了 \(\frac{m(m+1)}{2}\),發現將第 \(i\) 個位置改成第二種操作會減少 \(i+1\),我們先找到最小的 \(m\) 滿足 \(\frac{m(m+1)}{2} \geq x\) ,設 \(r=\frac{m(m+1)}{2}-x\),發現一定有 \(r \leq m\),而我們發現所有位置更改後能減少的量分別是 \(2,3,\ldots,m+1\),發現只有 \(r=1\) 的時候不能被表示出來,所以 \(r=1\) 的時候讓 \(m+1\) 就是答案了。

C

注意這兩個人的策略都是最大化自己的勝利步數,顯然後手可以一直不接球等到先手耗盡體力的時候將球打回去,剩下的球先手都接不到,所以後手最大勝利次數就是 \(y\),先手由於最後一次球被接住了,勝利次數是 \(x-1\)

D

貪心模擬,假設當前手裏的數字是 \(x\),正在考慮第 \(i\) 個位置:

  • \(a_{i-1} > a_i\):如果換不了(\(a_i \leq x\)) 就直接輸了,否則我們一定會交換,看下是否滿足條件
  • \(a_{i-1} \leq a_i\):如果 \(x < a_i\),因爲這個序列是不降的,就意味着以後都不能交換了,所以這時候判一下 \(i+1\) 後綴是否有序,如果不有序你一定要交換;如果 \(x \geq a_i\) 就可以不管。
#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 500+5;
int n,x,a[MAXN];
bool f[MAXN];

inline void Solve(){
    scanf("%d%d",&n,&x);
    FOR(i,1,n) scanf("%d",a+i);
    int p = 1;
    ROF(i,n,2){
        if(a[i-1] > a[i]){
            p = i;break;
        }
    }
    if(p == 1){
        puts("0");
        return;
    }
    int ans = 0;
    if(x < a[1]) std::swap(x,a[1]),++ans;
    FOR(i,2,p){
        if(a[i-1] > a[i]){
            if(a[i] <= x){
                puts("-1");return;
            }
            std::swap(a[i],x);++ans;
            if(a[i-1] > a[i]){
                puts("-1");return;
            }
        }
        else if(x < a[i] && i != p){
            std::swap(a[i],x);++ans;
        }
    }
    printf("%d\n",ans);
}

int main(){
    int T;scanf("%d",&T);
    while(T--) Solve();
    return 0;
}

E

我們設正方形左下角 \((x,y)\) 邊長爲 \(d\),枚舉四個點分別對應正方形的哪些點,現在有三個變量 \(x,y,d\),但我們發現貢獻式子最終是形如若干個 \(|x-x_i|,|x+d-x_i|,|y-y_i|,|y+d-y_i|\) 的和,如果將 \(d\) 看成常量,發現 \(x,y\) 一定會取讓某個絕對值爲 \(0\) 的點,枚舉取什麼,剩下就變成了求 \(\sum_i |d+c_i|\) 的最大值,也是一定會取某個零點的位置,都算算就好了。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<LL,LL>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

LL xx[5],yy[5],x[5],y[5];
int p[5];

inline LL mabs(LL x){
    return x < 0 ? -x : x;
}

std::vector<LL> S;

inline LL gao(){
    std::sort(all(S));
    int t = S.size()>>1;
    LL p = -S[t-1],res = 0;
    for(auto x:S) res += abs(x+p);
    return res;
}

inline void Solve(){
    FOR(i,1,4) scanf("%lld%lld",xx+i,yy+i),p[i] = i;
    LL ans = 1e18;
    do{
        FOR(i,1,4) x[p[i]] = xx[i],y[p[i]] = yy[i];
        for(auto i:{1,3}){
            for(auto j:{1,2}){
                S.clear();
                LL c = abs(x[1]-x[i])+abs(x[3]-x[i])+abs(y[1]-y[j])+abs(y[2]-y[j]);
                for(auto k:{2,4}) S.pb(x[i]-x[k]);
                for(auto k:{3,4}) S.pb(y[j]-y[k]);
                std::vector<LL> T = S;
                LL mn = 1e18;
                mn = std::min(mn,gao());
                S=T;FOR(k,0,1) S[k] = -S[k];
                mn = std::min(mn,gao());
                S=T;FOR(k,2,3) S[k] = -S[k];
                mn = std::min(mn,gao());
                S=T;FOR(k,0,1) S[k] = -S[k];
                mn = std::min(mn,gao());
                c += mn;
                ans = std::min(ans,c);
            }
        }
    }while(std::next_permutation(p+1,p+4+1));
    printf("%lld\n",ans);
}

int main(){
    int T;scanf("%d",&T);
    while(T--) Solve();
    return 0;
}

F

這題非常好的地方在於 dp 狀態表示的不一定是個數,還可以是個字符串(

\(f_i\) 表示操作完了 \(1 \ldots i\) ,前綴的最小字典序,轉移的時候分類討論 \(i+1,i+2\) 的操作:

  • U/D\(f_{i} \to f_{i+1}\)
  • L\(f_i \to f_{i+1}\)
  • R+U/D\(f_{i} \to f_{i+2}\)
  • RL\(f_i \to f_{i+2}\)

直接轉移即可,複雜度 \(O(n^2)\)

可以用滾動數組+二分哈希做到 \(O(n \log n)\)

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 500+5;
int n,k;
int mn[26];
std::string f[MAXN],str;

inline std::string ctos(char x){
    return std::string(1,x);
}

inline void Solve(){
    std::cin >> n >> k >> str;str = "0"+str;
    mn[0] = 0;FOR(i,1,k-2) mn[i] = i-1;mn[k-1] = 0;
    f[0].clear();FOR(i,1,n) f[i] = "{";
    FOR(i,0,n-1){
        f[i+1] = std::min(f[i+1],f[i]+ctos('a'+mn[str[i+1]-'a']));
        if(i) f[i+1] = std::min(f[i+1],f[i].substr(0,i-1)+ctos(str[i+1])+f[i].back());
        if(i != n-1) f[i+2] = std::min(f[i+2],f[i]+ctos('a'+mn[str[i+2]-'a'])+ctos(str[i+1]));
        if(i && i != n-1) f[i+2] = std::min(f[i+2],f[i].substr(0,i-1)+ctos(str[i+2])+f[i].back()+ctos(str[i+1]));
    }
    std::cout << f[n] << std::endl;
}

int main(){
    std::ios::sync_with_stdio(false);
    int T;std::cin >> T;
    while(T--) Solve();
    return 0;
}

G

dp 合併可以啓發式合併。。這個很妙

先考慮沒有 if 咋做:設 \(f_{i,x}\) 表示執行了前 \(i\) 條語句,當前值是 \(x\) 的答案,直接做是 \(O(n^2)\) 的。

觀察轉移相當於全局加上一個值和求一個最小值並單點賦值,用數據結構維護可以做到 \(O(n \log n)\)。這裏可以直接用一個 set 維護最小值,map 維護每個下標對應的在 set 裏的值,再維護個整體加標記即可。我們只需要維護可能的值的 dp 值就好了。

if 咋做呢?我們發現 if 內部是獨立的,可以做一樣的 dp,只是 dp 的初值需要改一下,所以我們遇到 if 就進去做 dp ,做完後考慮如何合併這兩個塊:我們可以用啓發式合併,將大小小的每一種 dp 值都合併到大的裏,注意要特判一下 if 條件給的值對應的 dp 值(這裏不能單純去 min,需要覆蓋)。

啓發式合併複雜度對的原因在於一次轉移可以放縮看成加入一個元素,於是複雜度就是顯然的 \(O(n \log^2 n)\) 了。細節挺多的。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

int n,s;

struct Node{// 全局加 單點修改 詢問最小值
    LL tag;
    std::map<int,LL> mp;// 位置 i 在 set 內的值
    std::multiset<LL> S;
    Node(LL tag=0) : tag(tag) {}
    inline void update(int p,LL d){
        if(p == s) return;
        if(mp.count(p)) S.erase(S.find(mp[p]));
        mp[p] = d-tag;
        S.insert(mp[p]);
    }

    inline LL get(int p){
        if(!mp.count(p)) return 1e18;
        return mp[p]+tag;
    }

    inline void upmin(int p,LL d){
        if(p == s) return;
        if(get(p) > d) update(p,d);
    }

    inline LL query(){
        return *S.begin() + tag;
    }
};

std::vector<Node> st;
std::vector<int> oo;

int main(){
    scanf("%d%d",&n,&s);
    Node v;v.update(0,0);
    st.pb(v);oo.pb(-114514);
    FOR(i,1,n){
        char opt[12];scanf("%s",opt);
        if(opt[0] == 's'){
            int x,y;scanf("%d%d",&x,&y);
            if(st.back().mp.empty()) continue;
            LL c = st.back().query();
            st.back().tag += y;st.back().upmin(x,c);
        }
        if(opt[0] == 'i'){
            int x;scanf("%d",&x);
            LL c = st.back().get(x);
            Node v;if(c != 1e18) v.update(x,c);
            st.pb(v);oo.pb(x);
        }
        if(opt[0] == 'e'){
            int tp = (int)st.size()-1,o = oo.back();bool flag = 1;
            if(st[tp].mp.size() > st[tp-1].mp.size()) std::swap(st[tp],st[tp-1]),flag = 0;
            for(auto x:st[tp].mp){
                if(x.fi != o) st[tp-1].upmin(x.fi,x.se+st[tp].tag);
                else{
                    if(flag) st[tp-1].update(x.fi,x.se+st[tp].tag);
                }
            }
            st.pop_back();oo.pop_back();
        }
    }
    printf("%lld\n",st[0].query());
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章