CF 1437 題解

A

判斷一下前面能否空出來就就行,也就是 \(l \geq r-l+1\)

#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 main(){
    int T;scanf("%d",&T);
    while(T--){
        int l,r;scanf("%d%d",&l,&r);
        puts(l >= r-l+1 ? "YES" : "NO");
    }
    return 0;
}

B

相當於不能有子串 1100

那麼如果有 11 肯定在後面有一個地方有 00 (反證),所以每次操作最多可以消去兩個這樣的連續的東西,求出有多少個相鄰相同的位置 \(x\),答案就是 \(\lceil \frac{x}{2} \rceil\)

#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 = 1e5 + 5;
char str[MAXN];

int main(){
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        scanf("%s",str+1);
        int ans = 0;
        FOR(i,2,n) if(str[i] == str[i-1]) ans++;
        ans = (ans+1)>>1;
        printf("%d\n",ans);
    }
    return 0;
}

C

首先可以發現一個東西:將匹配方式連邊,一定存在一種分配方式使得最優解沒有包含關係的線段,因爲包含關係可以通過交換變成相交,並且代價不變。

排序後可以設 \(f_{i,j}\) 表示考慮了前 \(i\) 個數,最小的 \(j\) 滿足 \(j\) 開始後面一段數字都沒有被用過的代價,轉移直接枚舉下一個填啥就行了。

#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 = 600+5;
int n,a[MAXN];
bool vis[MAXN];
int f[2][MAXN],now;

int main(){
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        FOR(i,1,n) scanf("%d",a+i);
        std::sort(a+1,a+n+1);
        CLR(f,0x3f);
        f[now = 0][0] = 0;
        FOR(i,1,n){
            CLR(f[now^1],0x3f);
            FOR(j,0,3*n){
                if(f[now][j] == 0x3f3f3f3f) continue;
                FOR(k,j+1,3*n){
                    f[now^1][k] = std::min(f[now^1][k],f[now][j]+std::abs(a[i]-k));
                }
            }
            now ^= 1;
        }
        int ans = 1e9;
        FOR(i,1,3*n) ans = std::min(ans,f[now][i]);
        printf("%d\n",ans);
    }
    return 0;
}

D

感性理解一下,我們每次取出深度最小的點,接上連續的一段遞增序列作爲它的後繼即可。

#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 = 2e5 + 5;
int n,a[MAXN];

int main(){
    int T;scanf("%d",&T);
    while(T--){
        scanf("%d",&n);FOR(i,1,n) scanf("%d",a+i);
        std::priority_queue<P,std::vector<P>,std::greater<P> > q;
        q.push(MP(0,1));int p = 2;
        int ans = 0;
        while(!q.empty()){
            int dep = q.top().fi;q.pop();ans = std::max(ans,dep);
            int las = -1;std::vector<int> v;
            while(p <= n && las <= a[p]) las = a[p++],v.pb(las);
            for(auto x:v) q.push(MP(dep+1,x));
        }
        printf("%d\n",ans);
    }
    return 0;
}

E

如果沒有不能選的限制就是經典 dp:我們觀察兩個位置 \(i<j\) 能同時不改變的條件是 \(a_j-a_i-1 \geq j-i-1\),也就是 \(a_i-i \leq a_j-j\),最多能不修改的數也就是設 \(b_i=a_i-i\)\(b_i\) 的非嚴格最長上升子序列長度。

這個題限定某些數不能改變,先判完無解後相當於變成了強制選取某些位置的最長上升子序列,對於每段分別做即可。

注意這裏 infty 開大點。。要不然判無解會判錯。。。真正考試對拍的時候還是多試試 corner case 吧。。

#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 = 5e5 + 5;
int n,k;
LL a[MAXN];
std::vector<int> S;
std::vector<LL> SS;

struct BIT{
    #define lowbit(x) ((x)&(-(x)))
    int tree[MAXN],ts[MAXN],now;

    inline void reset(){
        ++now;
    }

    inline void add(int pos,int x){
        while(pos < MAXN){
            if(ts[pos] != now) tree[pos] = 0,ts[pos] = now;
            tree[pos] = std::max(tree[pos],x);
            pos += lowbit(pos);
        }
    }

    inline int query(int pos){
        int res = 0;if(!pos) return 0;
        while(pos){
            if(ts[pos] != now) tree[pos] = 0,ts[pos] = now;
            res = std::max(res,tree[pos]);
            pos -= lowbit(pos);
        }
        return res;
    }
}bit;

int b[MAXN];

int main(){
    scanf("%d%d",&n,&k);
    FOR(i,1,n) scanf("%d",a+i);
    a[0] = -1e18;a[n+1] = 1e18;
    FOR(i,0,n+1) SS.pb(a[i]-i);
    std::sort(all(SS));SS.erase(std::unique(all(SS)),SS.end());
    FOR(i,0,n+1) b[i] = std::lower_bound(all(SS),a[i]-i)-SS.begin()+1;
    S.pb(0);
    FOR(i,1,k){
        int x;scanf("%d",&x);
        S.pb(x);
    }
    S.pb(n+1);
    FOR(i,1,(int)S.size()-1){
        if(a[S[i]]-a[S[i-1]] < S[i]-S[i-1]){
            puts("-1");
            return 0;
        }
    }
    int ans = 0;
    FOR(i,1,(int)S.size()-1){
        auto work = [&](int l,int r){
            bit.reset();
            int res = 0;
            FOR(i,l,r){
                if(b[i] < b[l]) continue;
                res = bit.query(b[i])+1;
                bit.add(b[i],res);
            }
            return r-l+1-res;
        };
        ans += work(S[i-1],S[i]);
    }
    printf("%d\n",ans);
    return 0;
}

F

居然讓我想了一會。。不過也是簡單題

直接做我們可能需要記錄哪些數選了,狀態會爆炸。我們考慮不斷往後面加數,如果這個序列的最大值確定了,那麼這個序列不改變最大值的前提下的最大長度就確定了,如果知道了目前長度,我們就能知道不改變最大值前提下這個位置的數有多少種可能。

於是設 \(f_{i,j}\) 表示放了 \(i\) 個數,最大值是 \(j\) 的方案數,預處理 \(las_i\) 表示最大值是 \(i\) 的時候序列的最長長度,\(nxt_i\) 表示最大值是 \(i\) 要改變最大值可以放的最小的數,\(cnt_i\) 表示值爲 \(i\) 的數的個數,轉移:

  • 不改變最大值:\(f_{i,j}\times (las_j-i+1) \to f_{i+1,j}\)
  • 改變最大值:\(f_{i,j}\times cnt_k \to f_{i+1,k}(k \geq nxt_j)\)

用一些前綴和技巧可以優化到 \(O(n^2)\)

看了一個比較 nb 的做法,大概是我們 dp 的時候只在放滿的時候轉移(也就是最大值爲 \(j\) 的時候我們只考慮狀態 \(f_{las_j,j}\)),這個就是設 \(f_i\) 表示最大值爲 \(i\) 的方案數,這樣長度就是 \(las_i+1\),轉移的時候枚舉上一次的最大值,乘上一個係數選進去就行了。

\[\begin{aligned} f_i &= \sum_j f_j\binom{n-las_j-2}{las_i-las_j-1}(las_i-las_j-1)!\\ &= \frac{1}{(n-las_i-1)}\sum_j f_j (n-las_j-2)! \end{aligned} \]

然後前綴和優化一下就 \(O(n)\) 了。還是要考慮大多數 dp 狀態都是無用的,只對真正有用的決策即可。

#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 = 5000+5;
const int ha = 998244353;
int a[MAXN],n;
int f[2][MAXN],cf[MAXN];
// 選了 i 個數  最大值是 j
std::vector<int> S;
int las[MAXN];// 選了 i 之後 還有幾個數可以放
int nxt[MAXN];// 選了 i 之後 下一個必須選啥
int cnt[MAXN];

inline void add(int &x,int y){
    x += y-ha;x += x>>31&ha;
}

int main(){
    scanf("%d",&n);
    FOR(i,1,n) scanf("%d",a+i);
    std::sort(a+1,a+n+1);
    FOR(i,1,n) S.pb(a[i]);S.erase(std::unique(all(S)),S.end());
    FOR(i,1,n) cnt[std::lower_bound(all(S),a[i])-S.begin()+1]++;
    int m = S.size();
    FOR(i,1,m){
        las[i] = las[i-1];
        while(las[i]+1 <= n && a[las[i]+1]*2 <= S[i-1]) las[i]++;
    }
    nxt[0] = 1;
    FOR(i,1,m){
        nxt[i] = -1;
        FOR(j,i+1,m){
            if(S[i-1]*2 <= S[j-1]) {nxt[i] = j;break;}
        }
    }
    int now = 0;f[now][0] = 1;
    FOR(i,1,n){
        CLR(f[now^1],0);CLR(cf,0);
        FOR(j,0,m){
            if(!f[now][j]) continue;
            int t = las[j]-i+1+1;
            if(j == 0) t = 0;
            if(t > 0) add(f[now^1][j],1ll*f[now][j]*t%ha);
            if(nxt[j] != -1) add(cf[nxt[j]],f[now][j]);
        }
        FOR(j,1,m) add(cf[j],cf[j-1]),add(f[now^1][j],1ll*cf[j]*cnt[j]%ha);
        now ^= 1;
//        FOR(i,0,m) printf("%d ",f[now][i]);puts("");
    }
    printf("%d\n",f[now][m]);
    return 0;
}

G

建 AC 自動機,我們枚舉詢問串的每一個後綴,走到對應的節點上,這個串的子串就是 fail 樹的祖先,所以相當於要支持單點修改,查詢到根的一條鏈上的最大值,樹剖即可。\(O(n \log^2 n)\)

題解做法一:我們發現對於一個點,它在 fail 樹上到根的路徑上終止節點最多有 \(O(\sqrt n)\) 個(因爲 \(\sum_{i=1}^n i = O(n^2)\)),所以記錄一下每個點上面距離最近的終止節點,暴力跳就好了。\(O(q\sqrt n)\)

一個比較 nb 的離線單 log 做法:先把詢問和修改掛在樹上,我們經過一個點 apply 它的所有操作,現在只有了操作時間的限制,我們用一個線段樹,第 \(i\) 個位置維護操作時間爲 \(i\) 的答案,不難發現要支持區間對一個數取 \(\max\)撤銷,單點求值。因爲只需要單點求值,搞個標記永久化就好了,這樣就只會改變 \(O(\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 = 3e5 + 5;

int ch[MAXN][26],fail[MAXN],tot = 1,rt = 1;
int ps[MAXN];

inline void insert(char str[],int id){
    int len = strlen(str+1),v = rt;
    FOR(i,1,len){
        int o = str[i]-'a';
        if(!ch[v][o]) ch[v][o] = ++tot;
        v = ch[v][o];
    }
    ps[id] = v;
}

std::vector<int> G[MAXN];

inline void build(){
    std::queue<int> q;
    FOR(i,0,25){
        if(ch[rt][i]) q.push(ch[rt][i]),fail[ch[rt][i]] = rt;
        else ch[rt][i] = rt;
    }
    while(!q.empty()){
        int v = q.front();q.pop();
        FOR(i,0,25){
            if(ch[v][i]) q.push(ch[v][i]),fail[ch[v][i]] = ch[fail[v]][i];
            else ch[v][i] = ch[fail[v]][i];
        }
    }
    FOR(i,2,tot) G[fail[i]].pb(i);
}

int mx[MAXN<<2];
#define lc ((x)<<1)
#define rc ((x)<<1|1)

inline void update(int x,int l,int r,int p,int d){
    if(l == r){
        mx[x] = d;
        return;
    }
    int mid = (l + r) >> 1;
    if(p <= mid) update(lc,l,mid,p,d);
    else update(rc,mid+1,r,p,d);
    mx[x] = std::max(mx[lc],mx[rc]);
}

inline int query(int x,int l,int r,int L,int R){
    if(l == L && r == R) return mx[x];
    int mid = (l + r) >> 1;
    if(R <= mid) return query(lc,l,mid,L,R);
    if(L > mid) return query(rc,mid+1,r,L,R);
    return std::max(query(lc,l,mid,L,mid),query(rc,mid+1,r,mid+1,R));
}

int dfn[MAXN],sz[MAXN],tp[MAXN],fa[MAXN],son[MAXN];
inline void dfs1(int v){
    sz[v] = 1;
    for(auto x:G[v]){
        fa[x] = v;
        dfs1(x);sz[v] += sz[x];
        if(sz[son[v]] < sz[x]) son[v] = x;
    }
}

inline void dfs2(int v,int tp=1){
    static int ts = 0;dfn[v] = ++ts;::tp[v] = tp;
    if(son[v]) dfs2(son[v],tp);
    for(auto x:G[v]){
        if(x == son[v]) continue;
        dfs2(x,x);
    }
}

inline int query(int x){
    int res = -1;
    while(tp[x] != 1){
        res = std::max(res,query(1,1,tot,dfn[tp[x]],dfn[x]));
        x = fa[tp[x]];
    }
    res = std::max(res,query(1,1,tot,dfn[1],dfn[x]));
    return res;
}

int n,m;
std::multiset<int> S[MAXN];
int val[MAXN];
char str[MAXN];

int main(){
    scanf("%d%d",&n,&m);
    CLR(mx,-1);
    FOR(i,1,n){
        scanf("%s",str+1);
        insert(str,i);
        S[ps[i]].insert(0);
    }
    build();
    dfs1(1);dfs2(1);
    FOR(i,1,n) update(1,1,tot,dfn[ps[i]],*S[ps[i]].rbegin());
    while(m--){
        int opt;scanf("%d",&opt);
        if(opt == 1){
            int p,x;scanf("%d%d",&p,&x);
            S[ps[p]].erase(S[ps[p]].find(val[p]));
            val[p] = x;
            S[ps[p]].insert(val[p]);
            update(1,1,tot,dfn[ps[p]],*S[ps[p]].rbegin());
        }
        else{
            scanf("%s",str+1);int len = strlen(str+1);
            int v = 1,res = -1;
            FOR(i,1,len){
                v = ch[v][str[i]-'a'];
                res = std::max(res,query(v));
            }
            printf("%d\n",res);
        }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章