冲刺 NOIP 400pts + 之神仙专题

冲刺专题之 \(DP\)

\(T_A\) Helping People

$$codeforces$$

题意

给定一个长为 \(n\) 序列 \(A\) , 其中有 \(q\) 个区间 \([l , r]\) 定义为 \(B_i\) , 对于每一个区间 , 其有 \(P_i\) 的概率使 \(A\) 序列从 \(l\)\(r\) 之间的数增加 \(1\) . 问最后整个区间的最大值的期望。其中 , \(n \le 10^5 , q \le 5 \times 10^3\)

对于 \(\forall B_i , B_j (1 \le i , j \le q 且 i \ne j)\) $B_i \cap B_j = \varnothing 或 B_i \subseteq B_j 或 B_j \subseteq B_i $ (即对于两个区间来说,其不相交或被包含)

样例输入,输出

3 5
1 2 3
1 3 0.500
2 2 0.250
1 2 0.800
1 1 0.120
2 2 0.900
4.465000000

本题为 \(SPJ\) , 只要输出与正解之差 \(\le 10^{-6}\) 即可通过。

题解

一言难尽。

现在我们发现这个区间的性质完全满足建树的条件,于是我们建树,并跑树形 \(DP\)

并设一个超级原点 \(0\)\([1 , n]\) , 其增加的概率为 \(0\)

我们设 \(front_i\) 为区间 \(i\) 的区间最大值。

现在考虑设 \(dp_{i , j}\) 为第 \(i\) 号节点 , 最大值为 \(front_i + j\)\(\bf{概率}\) (本题的期望就是个幌子)

\(sum_{i , j}\)\(dp_i\) 数组的前缀和。即 \(\le front_i + j\) 的总概率。

若我们枚举到 \(x\) 号节点 :


第一步

我们现在不考虑 \(x\) 会增加 \(1\) :

\(mid_{i , j}\) 数组的意义为第 \(i\) 号节点 , 不考虑 \(i\) 会增加 \(1\) , 最大值为 \(front_i + j\) 的概率 .

得到初步转移:

定义 \(SUM\) 初为 : \(\prod\limits_{z \in son_x}sum[ \ z \ ][ \ front_x + i - front_z \ ]\)

\[mid[ \ x \ ][ \ i \ ] = \sum_{y \in son_x }\frac{ SUM }{ sum[ \ y \ ][ \ front_x + i - front_y \ ] } \times mid[ \ y \ ][ \ front_x + i - front_y \ ] \ \ \ (i \in [ \ 0 \ , \ q \ ]) \]

\[SUM -= \frac{ SUM }{ sum[ \ y \ ][ \ front_x + i - front_y \ ] } \times mid[ \ y \ ][ \ front_x + i - front_y \ ] \]

(对于每一个 \(y\) 的运算之后 \(SUM\) 进行此次运算 )

当然,如果 \(sum[ \ y \ ][ \ front_x + i - front_y \ ] = 0\) 的话 , 此时直接为 \(0\) (别问,问就是没注意错了 \(114514\) 遍,每次 \(\color{red}{WA}\) \(Test \ 20\) )

考虑此式的正确性:

\(\prod\limits_{z \in son_x}sum[ \ z \ ][ \ front_x + i - front_z \ ]\) : 这个东西意味着 对于 \(x\) 的每一个儿子 , 取的值均 \(\le front_x + i\) 的概率 ;

\(\frac{ SUM }{ sum[ \ y \ ][ \ front_x + i - front_y \ ] }\) : 是不考虑 \(y\) 区间 , 其他区间最大值均 \(\le front_x + i\) 的概率 ;

我们 \(\times mid[ \ y \ ][ \ front_x + i - front_y \ ]\) 保证了整个 \(x\) 区间 最大值为 \(y\) 区间的 \(front_x + i\)

但如果 \(SUM\) 不变的话 , 对于两个区间 \(y , z \in son_x\) 均有最大值是 \(front_x + i\) 的情况时,

那么 \(y , z\) 均为 \(front_x + i\) 时的概率将会被计算两次。

此句减的操作是去重的过程。


第二步

我们进行考虑父区间 \(x\) 对于答案的影响。

我们发现当 \(x\) 的最大值和其某个子区间相同时 , \(i = 1 | \ | 0\) 的时候需要分讨一下 (原因下面提到)

对于 \(i \in [2 \ , \ q]\) 时显然不用再考虑那些被 \(x\) 包含却未被 \(y \in son_x\) 包含的点中有最大值。因为即使 \(x\) 选 , 也只会把上面的点变为 \(front_x + 1\) ,不会变为 \(front_x + i\) .

我们得到这样的式子:

\[dp[ \ x \ ][ \ i \ ] = mid[ \ x \ ][ \ i \ ] \times \left( 1 - P[ \ x \ ] \right) + mid[ \ x \ ][ \ i - 1 \ ] \times P[x] \]

显然

但是对于 $ i = 1 , i = 0$ 情况 , 我们需要对 \(x\) 区间进行分讨了。

如果 \(x\) 区间的最大值不在其子节点区间里:

如果 \(i = 1\) 时 , \(x\) 区间进行加一时 , 变为 \(front_x + 1\) , 最大值可能是他 , 所以这个就保证了区间最大值为 \(front_x + 1\) , 所以剩下的子区间可选的得到的概率为 :

\[dp[ \ x \ ][ \ 1 \ ] = P[ \ x \ ] \times \sum_{y \in son_x }sum[ \ y \ ][ \ front_x - front_y \ ] \]

注意你会 \(+ 1\) 的 , 所以不能超出 \(front_x\) .

对于 \(i = 0\) 时 , 因为最大值就在已定了,所以剩下的只要选的不超过 \(front_x\) 就可以了.

\[dp[ \ x \ ][ \ 0 \ ] = \left(1 - P[ \ x \ ]\right) \times \sum_{y \in son_x }sum[ \ y \ ][ \ front_x - front_y \ ] \]

如果 \(front_x\) 出现在其子区间里:

易得:

\[dp[ \ x \ ][ \ 1 \ ] = mid[ \ x \ ][ \ 0 \ ] \times P[ \ x \ ] + mid[ \ x \ ][ \ 1 \ ] \times \left( 1 - P[ \ x \ ]\right) \]

\[dp[ \ x \ ][ \ 0 \ ] = mid[ \ x \ ][ \ 0 \ ] \times \left(1 - P[ \ x \ ] \right) \]

\(code\)

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 1e5 + 100 ; 
const int M = 5e3 + 100 ; 
inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 
 
    while ( c < '0' || c > '9' ) {
        f = c == '-' ? -f : f ; 
        c = getchar() ; 
    }
 
    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }
 
    return x * f ; 
}
 
int st[N][20] , lg[N] ; 
inline void build_ST( int n ) {
    for ( int i = 2 ; i <= n ; ++ i ) {
        lg[i] = lg[i >> 1] + 1 ; 
    }
 
    for ( int j = 1 ; j <= lg[n] ; ++ j ) {
        for ( int i = 1 ; i + ( 1 << j ) - 1 <= n ; ++ i ) {
            st[i][j] = max( st[i][j - 1] , st[i + ( 1 << ( j - 1 ) )][j - 1] ) ; 
        } 
    }
}
inline int MAX_ST( int l , int r ) {
    int k = lg[r - l + 1] ; 
    return max( st[l][k] , st[r - ( 1 << k ) + 1][k] ) ; 
}
 
class edge {
    public:
        int to , next ; 
}e[M] ; int head[M] , cnt ; bool vis[M] ; 
inline void add( int x , int y ) {
    cnt ++ ; 
    e[cnt].to = y ; 
    e[cnt].next = head[x] ; 
    head[x] = cnt ; 
}
 
int n , q ; 
double dp[M][M] , sum[M][M] ; 
class Node {
    public: 
        int l , r , front ; 
        double p ; 
}b[M] ; 
bool cmp( Node a , Node b ) {
    if ( a.l == b.l ) return a.r > b.r ; 
    return a.l < b.l ; 
}
 
void dfs_find( int x ) {
    for ( int i = x + 1 ; i <= q ; ++ i ) {
        if ( b[x].r < b[i].l ) break ; 
 
        if ( b[x].l <= b[i].l && b[i].r <= b[x].r && !vis[i] ) {
            vis[i] = 1 ; 
            add(x,i) ; dfs_find(i) ; 
        }
    }
}

void dfs( int x ) {
    bool flag = 0 ;
    for ( int i = head[x] ; i ; i = e[i].next ) {
        dfs(e[i].to) ; 
        
        if ( b[x].front == b[e[i].to].front ) flag = 1 ; 
    }
    long double sum0 = 1 ;  bool flak = 0 ; 

    for ( int i = 0 ; i <= q ; ++ i ) {
        long double sumi = 1 ; flak = 0 ; 

        for ( int j = head[x] ; j ; j = e[j].next ) {
            int y = e[j].to ; 

            if ( b[x].front + i - b[y].front <= q ) 
                if ( sum[y][b[x].front + i - b[y].front] > 0 )
                    sumi *= sum[y][b[x].front + i - b[y].front] ; 
                else flak = 1 ; 
        }

        if ( !flak )
            for ( int j = head[x] ; j ; j = e[j].next ) {
                int y = e[j].to ; 
                
                if ( b[x].front + i - b[y].front <= q ) 
                    if ( sum[y][b[x].front + i - b[y].front] > 0 ) {
                        dp[x][i] += sumi / sum[y][b[x].front + i - b[y].front] * dp[y][b[x].front + i - b[y].front] ; 
                        sumi -= sumi / sum[y][b[x].front + i - b[y].front] * dp[y][b[x].front + i - b[y].front] ; 
                    }
            }
    }
    
    for ( int i = q ; i >= 2 ; -- i ) {
        dp[x][i] = dp[x][i - 1] * b[x].p + dp[x][i] * ( 1 - b[x].p ) ; 
    }

    if ( !flag ) {
        long double sum_0 = 1 ; 
        for ( int i = head[x] ; i ; i = e[i].next ) {
            int y = e[i].to ; 

            if ( b[x].front - b[y].front <= q ) {
                sum_0 *= sum[y][b[x].front - b[y].front] ; 
            }
        }

        dp[x][1] = ( 1 - b[x].p ) * dp[x][1] + b[x].p * sum_0 ; 
        dp[x][0] = ( 1 - b[x].p ) * sum_0 ; 
    }
    else {
        dp[x][1] = dp[x][0] * b[x].p + dp[x][1] * ( 1 - b[x].p ) ;    
        dp[x][0] = dp[x][0] * ( 1 - b[x].p ) ; 
    }

    sum[x][0] = dp[x][0] ; 
    for ( int i = 1 ; i <= q ; ++ i ) {
        sum[x][i] = sum[x][i - 1] + dp[x][i] ; 
    }
}
 
inline void Query_Expect() {
    long double ans = 0 ; 
    int maxl = b[0].front ; 

    for ( int i = 0 ; i <= q ; ++ i ) {

        ans += ( maxl + i ) * dp[0][i] ; 
    }
 
    printf( "%.9Lf\n" , ans ) ; 
}
 
signed main() {
#ifndef ONLINE_JUDGE
    freopen( "1.in" , "r" , stdin ) ; 
    freopen( "1.out", "w" ,stdout ) ; 
#endif 
    
    n = read() ; q = read() ; 
    for ( int i = 1 ; i <= n ; ++ i ) {
        st[i][0] = read() ; 
    }

    build_ST( n ) ; 
 
    for ( int i = 1 ; i <= q ; ++ i ) {
        b[i].l = read() ; b[i].r = read() ; 
        b[i].front = MAX_ST( b[i].l , b[i].r ) ; 
        scanf( "%lf" , &b[i].p ) ; 
    }

    sort( b + 1 , b + q + 1 , cmp ) ; 
    b[0].l = 1 , b[0].r = n , b[0].p = 0 ; vis[0] = 1 ; 
    b[0].front = MAX_ST( 1 , n ) ; 

    dfs_find(0) ; dfs(0) ; 

    Query_Expect() ; 
}

\(T_B\) Bird

十分简单的背包,看代码

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 1e4 + 100 ; 
const int M = 1e3 + 10 ; 

int dp[N] ; 
int up[M][N] , n , b , x , w ; 
int num[N] , cost[N] ; 
int total[N] ; 

inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar( ) ; 
    
    while ( c < '0' || c > '9' ) {

        if ( c == '-' ) f = -f ; 
        
        c = getchar( ) ; 
    }

    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar( ) ; 
    }

    return x * f ; 
}

signed main() {

    #ifndef ONLINE_JUDGE
        freopen( "1.in" , "r" , stdin ) ; 
        freopen( "1.out", "w", stdout ) ;  
    #endif

    n = read() , w = read() , b = read() , x = read() ; 
    
    for ( int i = 1 ; i <= n ; ++ i ) {
        num[i] = read() ; 

        total[i] = total[i - 1] + num[i] ; 
    }
    for ( int i = 1 ; i <= n ; ++ i ) {
        cost[i] = read() ; 
    }
    
    memset( dp , 128 , sizeof(dp) ) ; 
    dp[0] = w ; 

    for ( int i = 1 ; i <= n - 1 ; ++ i ) {
        for ( int j = total[i] ; j >= 0 ; -- j ) {
            for ( int k = 0 ; k <= num[i] && j - k >= 0 ; ++ k ) {

                if ( dp[j - k] >= 0 && dp[j - k] - cost[i] * k >= 0 ) {

                    dp[j] = max( min( dp[j - k] - cost[i] * k + x , j * b + w ) , dp[j] ) ; 

                }
            }
        }
    }
    for ( int j = total[n] ; j >= 0 ; -- j ) {
        for ( int k = 0 ; k <= num[n] && j - k >= 0 ; ++ k ) {
            
            if ( dp[j - k] >= 0 && dp[j - k] - cost[n] * k >= 0 )
                dp[j] = max( min( dp[j - k] - cost[n] * k , j * b + w ) , dp[j] ) ; 

        }
    }

    for ( int i = total[n] ; i >= 0 ; -- i ) {

        if ( dp[i] >= 0 ) {
            cout << i ; 
            return 0 ; 
        }

    }
}

\(T_C\) Positions in Permutations

来看这里

$$my \ blogs$$

\(T_H\) Tavas in Kansas

$$codeforces$$

题意

nishishui123 和 HANGRY_Sol 玩游戏( 原神 ) 给定一个 \(m\) 条边带权无向图 , 边权给定。 HANGRY_Sol在 \(S\) , nishishui123在 \(t\) , HANGRY_Sol 开始,选定一个数 \(x\) ,使得目前操作的人的获得分数加等于 \(dist_i \le x\) 的点的点权。选过一个点后,其权值消失。对于每次操作,至少获得一个点。求最后谁赢。

\(T_I\) Game on Sum

看这里

$$my \ blogs$$

冲刺专题之字符串

前置知识:SA

$$my \ blogs$$

\(T_B\) Yet Another LCP Problem

$$codeforces$$

前置

首先要先会一个东西,首先看一下 AHOI2013差异 , 差异luogu

其实要求的只是这个:

\[\sum_{1 \le i < j \le n}LCP\left(T_i , T_j\right) \]

(\(T_i\) 表示以 \(i\) 为头的后缀 , \(n \le 5 \times 10^5\) )

其实这个题意会误导人,我们换一下:

对于串 \(s\) , 求其中选两个后缀的 \(LCP\) 的和。

虽然两个没任何区别,但是我们就可以打破顺序,按照 \(Rank\) 的顺序进行求解。

这时我们就可以用单调栈来进行求解。

\(top\) 为从 \(i\) (表示的为 \(Rank\) )往上数第一个 < \(height_i\)\(Rank\) 值。

\(dp_i\) 为到 \(i\) 是的贡献值。

st[top]
...
...
i

那从 \(st_{top} + 1\)\(i\)\(height\) 均相等,那这一块的贡献值显然为 \((i - st_{top}) \times height_i\)

可能上面的和 \(i\) 也会有 \(LCP\) 值,所以再加上 \(dp_{st_{top}}\)

核心代码

CODE
for ( int i = 1 ; i <= n ; ++ i ) {
    while ( !sta.empty() && height[sta.top()] >= height[i] ) sta.pop() ; 

    dp[i] = (i - sta.top()) * height[i] + dp[sta.top()] ; 

    ans += dp[i] ; 
}

cout << ans ; 

对于本题

给定长度为 \(n\) 的字符串 \(s\)\(q\) 个查询。每个查询是一对整数集合 \(a_1,a_2,\dots,a_k\)\(b_1,b_2,\dots,b_l\) 。计算每个查询的: $$\sum_{i = 1}^{k}\sum_{j = 1}^{l}LCP(T_{a_i} , T_{b_j})$$

题解

考虑将 \(a\) , \(b\) 合成一个 \(c\) , 并将 \(a\) , \(b\) , \(c\) 的任选两个的 \(LCP\) 和全求出来,最后答案为:

\[sum_c - sum_b - sum_a \]

Code

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 2e5 + 10 ; 
inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 
    while ( c < '0' || c > '9' ) {
        if ( c == '-' ) f = -f ; 
        c = getchar() ; 
    }
    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }
    return x * f ; 
}
char s[N] ; int height[N] ; 
int ST[N][20] , lg[N] ; 
int SA[N] , Rank[N] , Lstrk[N] , key1[N] , cnt[N] , m = 127 , n , q , p , id[N] ; 
int fir[N] , sec[N] , thi[N << 1] ; 
int hei_fir[N] , hei_sec[N] , hei_thi[N << 1] ; 
int dp[N << 1] ; 
class Monotonic_STACK {
    public:
        int st[N << 1] , T_POINT ; 
        inline void push( int x ) {
            st[++ T_POINT] = x ; 
        }
        inline void pop() {
            -- T_POINT ; 
        }
        inline bool empty() {
            return T_POINT ? 0 : 1 ; 
        }
        inline void clear() {
            T_POINT = 0 ; 
        } 
        inline int top() {
            return st[T_POINT] ; 
        }
        Monotonic_STACK() ; 
} sta ; 
Monotonic_STACK::Monotonic_STACK() {
    memset(st , 0 , sizeof(st)) ; 
    T_POINT = 0 ; 
}
inline int max( int a , int b ) {
    return a > b ? a : b ; 
}
inline int min( int a , int b ) {
    return a < b ? a : b ; 
}
inline int MinST(int l , int r) {
    int k = lg[r - l + 1] ; 
    return min( ST[l][k] , ST[r - (1 << k) + 1][k] ) ; 
}
inline void Build_ST() {
    for ( int i = 2 ; i <= n ; ++ i ) {
        lg[i] = lg[i >> 1] + 1 ; 
    }
    for ( int j = 1 ; j <= lg[n] ; ++ j ) {
        for ( int i = 1 ; i + (1 << j) - 1 <= n ; ++ i ) {
            ST[i][j] = min( ST[i][j - 1] , ST[i + (1 << (j -1))][j - 1] ) ; 
        }
    }
}
inline int Query( int l , int r ) {
    if ( l == r ) return n - l + 1 ; 

    return Rank[l] < Rank[r] ? MinST(Rank[l] + 1 , Rank[r]) : MinST( Rank[r] + 1 , Rank[l] ) ;  
}
signed main() {
#ifndef ONLINE_JUDGE
    freopen( "1.in" , "r" , stdin ) ; 
    freopen( "1.out", "w" ,stdout ) ; 
#endif
    auto compare = [](int x , int y , int j) {
        return Lstrk[x] == Lstrk[y] && Lstrk[x + j] == Lstrk[y + j] ; 
    } ; 
    n = read() , q = read() ; 
    for ( int i = 1 ; i <= n ; ++ i ) {
        cin >> s[i] ; cnt[Rank[i] = s[i]] ++ ; 
    }
    for ( int i = 1 ; i <= m ; ++ i ) cnt[i] += cnt[i - 1] ; 
    for ( int i = n ; i >= 1 ; -- i ) SA[cnt[Rank[i]] --] = i ; 
    for ( int j = 1 ; ; j <<= 1 , m = p ) {
        p = 0 ; 
        for ( int i = n ; i > n - j ; -- i ) id[++ p] = i ; 
        for ( int i = 1 ; i <= n ; ++ i ) {
            if ( SA[i] - j > 0 ) id[++ p] = SA[i] - j ; 
        } 
        memset( cnt , 0 , sizeof(cnt) ) ; 
        for ( int i = 1 ; i <= n ; ++ i ) cnt[key1[i] = Rank[id[i]]] ++ ; 
        for ( int i = 1 ; i <= m ; ++ i ) cnt[i] += cnt[i - 1] ; 
        for ( int i = n ; i >= 1 ; -- i ) SA[cnt[key1[i]] --] = id[i] ; 
        memcpy( Lstrk , Rank , sizeof(Lstrk) ) ; 
        p = 0 ; 
        for ( int i = 1 ; i <= n ; ++ i ) {
            if ( compare(SA[i] , SA[i - 1] , j) ) Rank[SA[i]] = p ; 
            else Rank[SA[i]] = ++ p ; 
        }
        if ( n == p ) break ; 
    }
    for ( int i = 1 , k = 0 ; i <= n ; ++ i ) {
        if ( !Rank[i] ) continue ; 
        if (k) -- k ; 
        while ( s[i + k] == s[SA[Rank[i] - 1] + k] ) ++ k ; 
        height[Rank[i]] = k ; 
	    ST[Rank[i]][0] = k ;    
    }
    Build_ST() ; 
    int x , y , z ; 
    while ( q -- ) {
        x = read() , y = read() ; 
        for ( int i = 1 ; i <= x ; ++ i ) { 
            fir[i] = read() ; thi[i] = fir[i] ; 
        }
        for ( int i = 1 ; i <= y ; ++ i ) {
            sec[i] = read() ; thi[x + i] = sec[i] ; 
        }
        z = x + y ; 
        stable_sort( fir + 1 , fir + x + 1 , [](int alpha , int beta) {
            return Rank[alpha] < Rank[beta] ; 
        } ) ; 
        stable_sort( sec + 1 , sec + y + 1 , [](int alpha , int beta) {
            return Rank[alpha] < Rank[beta] ; 
        } ) ; 
        stable_sort( thi + 1 , thi + z + 1 , [](int alpha , int beta) {
            return Rank[alpha] < Rank[beta] ; 
        } ) ; 
        for ( int i = 1 ; i <= x ; ++ i ) hei_fir[i] = Query(fir[i - 1] , fir[i]) ; 
        for ( int i = 1 ; i <= y ; ++ i ) hei_sec[i] = Query(sec[i - 1] , sec[i]) ; 
        for ( int i = 1 ; i <= z ; ++ i ) hei_thi[i] = Query(thi[i - 1] , thi[i]) ; 
        int ans = 0 ; 
        sta.clear() ; 
        for ( int i = 1 ; i <= z ; ++ i ) {
            while ( !sta.empty() && hei_thi[sta.top()] >= hei_thi[i] ) sta.pop() ;
            dp[i] = dp[sta.top()] + (i - sta.top()) * hei_thi[i] ; 
            ans += dp[i] ; 
            sta.push(i) ; 
        }
        sta.clear() ; 
        for ( int i = 1 ; i <= x ; ++ i ) {
            while ( !sta.empty() && hei_fir[sta.top()] >= hei_fir[i] ) sta.pop() ; 
            dp[i] = dp[sta.top()] + (i - sta.top()) * hei_fir[i] ; 
            ans -= dp[i] ; 
            sta.push(i) ; 
        }
        sta.clear() ; 
        for ( int i = 1 ; i <= y ; ++ i ) {
            while ( !sta.empty() && hei_sec[sta.top()] >= hei_sec[i] ) sta.pop() ; 
            dp[i] = dp[sta.top()] + (i - sta.top()) * hei_sec[i] ; 
            ans -= dp[i] ; 
            sta.push(i) ; 
        }
        cout << ans << '\n' ; 
    }
}

\(T_F\) String Distance

$$codeforces$$

本题解来之不易,请大家珍惜。


题意

定义包含两个字符串参数的函数 \(f(a , b)\) 表示将 \(a , b\) 两个字符串任意一个字符串对其某一个子区间进行按字符大小排序,称为一次操作,若 \(a\) 进行 \(\alpha\) 此操作 , \(b\) 进行 \(\beta\) 次操作,使得这两个字符串完全相同,则 \(f(a , b) = min\{\alpha + \beta\}\) , 即进行最少的操作使得变为相同串。

如果不能变为相同的 \(f(a , b) = 1337\)

现在给定 \(n\) 个包含小写字母的、不存在任何两个相同的字符串、长度相同为 \(len\) 的字符串 \(s_1 \dots s_n\) , 求:

\[\sum_{i = 1}^{n}\sum_{j = i + 1}^{n}f(s_i , s_j) \]

\(n \le 2\times 10^5 , len \le 2 \times 10^5 , n \times len \le 2 \times 10^5\)

题解(根号分治)

首先,显然的如果构成两个字符串的字符不完全相同 , 则一定不能变为相同的。

对于构成两字符串的字符完全相同的两字符串 \(a , b\) 来说 :

如果 \(f(a , b) > 1\) ,我们考虑对每一个字符串选择从 \(1\)\(len\) 的区间都排序,次数为 \(2\) , 两字符串相等。

所以对于两个字符串 \(a , b\) 来说, \(f\) 函数有且仅有 \(3\) 个取值: \(1337 , 1 , 2\) (没有 \(0\) , 因为没有相同串)

如果 \(f(a , b) = 1\) 时满足这种情况:

  • 构成 \(a , b\) 的字符完全相同
  • 存在一段子区间,某一个串在此段为单调不降的,且剩余部分 (前缀和后缀) 两个串完全相等。

我们求出 \(f = 1337\) 的情况,并将由字符的构成的串分为一个个块,对于每个块分别处理

\(n^2 \times len\) 做法

这个 显然 很好想啊,暴力枚举每两个串,求出对应的每个 \(f(s_i , s_j)\) 的值。

时间复杂度为 \(n^2 \times len\) .

\(n \times len^2\) 做法

证明引理

对于两个串,如果存在一个子区间(中间串),任意一个串满足在此子区间中为单调不降趋势串但不是在此位置的最长单调不降趋势串(意思是在头和尾往左或往右都不能再扩展长度),且此子区间再两字符串的补集完全相同(前缀和后缀 , 意思是进行一次交换就可以使两字符串完全相同) 那么将中间串扩展到此位置的单调不降趋势串,依然只用交换一次。

我们可以这么去想:如果次数为 \(1\) , 则前缀和后缀均相同,那扩展到此位置的单调不降趋势串后,前缀和后缀依然相同。所以次数仍为 \(1\) .


我们考虑枚举中间串的首尾,设目前再字符串 \(i\) .

设将其中间串扣去后余下的串的哈希值为 \(Hash_i\) .

记两个哈希表为 \(Hasher1 , Hasher2\) ,

\(Hasher1\) 统计存在多少个哈希值为 \(HASH\) 的,且中间串不为单调不降的串的个数 ;

\(Hasher2\) 表示是否存在一个哈希值为 \(HASH\) 且中间串为此位置最长单调不降串。

  • 如果中间串为单调不降的,但是不是此位置的最长,则跳过,防止记重。

  • 如果不为单调不降的,\(Hasher1_{Hash_i} ++\)

  • 如果为单调不降且为此位置的最长 , \(Hasher2_{Hash_i} = 1\)

最后的答案为:

\[\sum Hasher1_{p[i]} \times Hasher2_{p[i]} \]

( \(p\) 数组表示此次中间串枚举过程中出现的所有前缀后缀拼起来的串的 \(hash\) 值 )

显然复杂度为 \(n \times len^2\)

组编为代码

考虑什么时候用 \(n^2\times len\) :

我们列一个等式:

$n^2 \times len = n \times len^2 $ , 则\(n = len\)

\(\because n \times len = 2 \times10^5 , \therefore n = 4 \sqrt{5} \times 100\) 时为两种方法切换点。

但因为哈希表比较离谱,所以让 \(n^2 \times len\) 多跑一点

Code

代码里有些错误,我也懒得改了

CODE
#include <bits/stdc++.h>
using namespace std ; 
#define int long long 
unordered_map <int , int> Hash1 , Hash2 ; 
const int down = 26 ; 
const int down2 = 25 ; 
const int N = 2e5 + 10 ; 
const long long Funji = 1.6 * 1e9 ; 
#define ULL unsigned long long 
ULL does[N] ; ULL does2[N] ; 
inline int read() {
	int x = 0 , f = 1 ; 
	char c = getchar() ; 

	while ( c < '0' || c > '9' ) {
		if ( c == '-' ) f = -f ; 

		c = getchar() ; 
	}

	while ( c >= '0' && c <= '9' ) {
		x = x * 10 + c - '0' ; 
		c = getchar() ; 
	}

	return x * f ; 
}

inline int min( int a , int b ) {
	return a < b ? a : b ; 
}
inline int max( int a , int b ) {
	return a > b ? a : b ; 
}

vector<int>head[N] , tail[N] ; 
vector<ULL>Hasher[N] ; 
vector<char> s[N] ; 
int Hashp[N] , knumbols ; 
long long n , len ; 
int duan[N] ; char Reader ; 
char s1[N] ; int numbol ; 
short SSR[N] ; int length ; 
int cnt[N] ; 

class Node {
	public : 
		int id ; 
		ULL Hash ; 

	Node() {
		id = Hash = 0 ; 
	}
} a[N] ; 

inline void Reset() {
	for ( int i = 1 ; i <= knumbols ; ++ i ) {
		Hashp[i] = 0 ; 
	} knumbols = 0 ;

	unordered_map <int , int> ().swap(Hash1) ; 
	unordered_map <int , int> ().swap(Hash2) ; 
}

void Search( int ider , int right , int l , int r , int idy ) {
	if ( ider == right + 1 ) {
		return ; 
	}

	int now = a[ider].id ; 

	ULL HASHERI ; 

	if ( l ) HASHERI = Hasher[now][l - 1] + Hasher[now][len - 1] - Hasher[now][r] ; 
		else HASHERI = Hasher[now][len - 1] - Hasher[now][r] ; 

	if ( head[now][r] - head[now][l] + 1 == r - l + 1 && ( head[now][l] != 1 || ( r != len - 1 && head[now][r + 1] != 1 ) ) ) {
		Search( ider + 1 , right , l , r , idy ) ; 

		return ; 
	} else {
		if ( head[now][r] - head[now][l] + 1 == r - l + 1 && head[now][l] == 1 && ( r == len - 1 || head[now][r + 1] == 1 ) ) {
			
			Hash2[HASHERI] = 1 ; 

			Search( ider + 1 , right , l , r , idy ) ; 

			return ; 
		}

		++ Hash1[HASHERI] ; 

		if ( Hash1[HASHERI] == 1 ) {
			Hashp[++ knumbols] = HASHERI ; 
		}

		Search( ider + 1 , right , l , r , idy ) ; 
	}
}

signed main() {
#ifndef ONLINE_JUDGE
	freopen( "1.in" , "r" , stdin ) ; 
	freopen( "1.out" , "w" , stdout ) ; 
#endif
	long long ans = 0 ; 

	does[0] = 1 ; 
	for ( int i = 1 ; i <= 2e5 ; ++ i ) {
		does[i] = does[i - 1] * down ; 
	} 
	does2[0] = 1 ; 
	for ( int i = 1 ; i <= 2e5 ; ++ i ) {
		does2[i] = does2[i - 1] * down2 ; 
	}

	n = read() ; 
	cin >> s1 + 1 ; 
	len = strlen(s1 + 1) ; 

	for ( int i = 1 ; i <= len ; ++ i ) {
		s[1].push_back(s1[i]) ; 
		s1[i] = 0 ; 
	}

	for ( int i = 2 ; i <= n ; ++ i ) {
		for ( int j = 1 ; j <= len ; ++ j ) {
			cin >> Reader ; 
			s[i].push_back(Reader) ; 
		}
	} 

	for ( int i = 1 ; i <= n ; ++ i ) {
		for ( int j = 0 ; j < len ; ++ j ) {
			SSR[j] = s[i][j] - 'a' ; 
		}

		stable_sort( SSR + 0 , SSR + len ) ; 

		for ( int j = 0 ; j < len ; ++ j ) {
			a[i].Hash += SSR[j] * does2[len - 1 - j] ; 
		}
		
		a[i].id = i ; 
	}

	sort( a + 1 , a + n + 1 , [](Node alpha , Node beta) {
		return alpha.Hash < beta.Hash ; 
	} ) ; 

	for ( int i = 1 ; i <= n ; ++ i ) {
		if ( a[i].Hash != a[i - 1].Hash || i == 1 ) { 
			duan[++ numbol] = i ; 
		}
	}

	duan[numbol + 1] = n + 1 ; 

	int clen = n ; 

	for ( int i = 1 ; i <= numbol ; ++ i ) {
		int Count = duan[i + 1] - 1 - duan[i] + 1 ; 
		ans += Count * ( clen - Count ) * 1337 ; 
		clen -= Count ; 
	}

	if ( (long long)(n * n * len) <= Funji ) {

		for ( int k = 1 ; k <= numbol ; ++ k ) {
			int left = duan[k] , right = duan[k + 1] - 1 ; 
			int what_can_I_Say = 0 ; 

			for ( int i = left ; i <= right ; ++ i ) {
				for ( int j = i + 1 ; j <= right ; ++ j ) {
					int fir = a[i].id , sec = a[j].id ; 
					int front = 0 , back = len - 1 ; 

					while ( s[fir][front] == s[sec][front] && front < len ) {
						front ++ ; 
					} front -- ; 

					while ( back >= 0 && s[sec][back] == s[fir][back] && back != front ) {
						back -- ; 
					} back ++ ; 

					bool flag1 = 0 , flag2 = 0 ; 

					for ( int l = front + 2 ; l <= back - 1 ; ++ l ) {
						if ( s[fir][l] < s[fir][l - 1] ) {
							flag1 = 1 ; 
						}

						if ( s[sec][l] < s[sec][l - 1] ) {
							flag2 = 1 ; 
						}
					}

					if ( !flag1 || !flag2 ) { 
						what_can_I_Say ++ ; 
					}
				}
			}

			ans += ( ( right - left + 1 ) * ( right - left ) / 2 - what_can_I_Say ) * 2 + what_can_I_Say ; 
		}

		cout << ans ; 

		return 0 ; 
	} else {

		for ( int i = 1 ; i <= n ; ++ i ) {
			for ( int j = 0 ; j < len ; ++ j ) {
				if ( j == 0 ) Hasher[i].push_back((s[i][j] - 'a') * does[len - 1]) ; 
				else {
					Hasher[i].push_back(Hasher[i][j - 1] + does[len - 1 - j] * (s[i][j] - 'a')) ; 
				}
			}
		} 

		for ( int i = 1 ; i <= n ; ++ i ) {
			for ( int j = 1 ; j <= len + 1 ; ++ j ) {
				head[i].push_back(0) ; 
			}

			head[i][0] = 1 ; 

			for ( int j = 1 ; j < len ; ++ j ) {
				if ( s[i][j] < s[i][j - 1] ) {
					head[i][j] = 1 ; 
				} else {
					head[i][j] = head[i][j - 1] + 1 ; 
				}
			} 
		}

		for ( int k = 1 ; k <= numbol ; ++ k ) {
			int numer = 0 ; 
			int left = duan[k] , right = duan[k + 1] - 1 ; 

			for ( int i = 0 ; i < len ; ++ i ) { // 枚举中间串的左端
				for ( int j = i ; j < len ; ++ j ) { // 右端
					
					if ( i == j ) continue ; 

					Reset() ; 

					Search(left , right , i , j , k) ; 

					for ( int l = 1 ; l <= knumbols ; ++ l ) {
						numer += Hash1[Hashp[l]] * Hash2[Hashp[l]] ; 
					} 
				}
			}

			ans += ( ( duan[k + 1] - 1 - duan[k] + 1 ) * ( duan[k + 1] - 1 - duan[k] ) / 2 - numer ) * 2 + numer ; 
		}

		cout << ans ; 

		return 0 ; 
	}
}

\(T_G\) x-prime Substrings

简化题意:

定义一个数字串 \(s_{l \dots r}\) 的和为 ; \(\sum\limits_{i = l}^{r}s_i\)

对于数字串来说定义一个串是 x - QZS 的,当且仅当:

  1. 这个数字串的和为 \(x\) .

  2. 这个数字串的任意一个真子串和不整除 \(x\) .

现在给定一个目标串 \(s\) ,问你在其中至少删去几个字符才不含有一个 m - QZS 串, \(m\) 给定。

\(|s| \le 1000 , m \le 20\)

题解。

我们发现 m - QZS 串的个数在 \(m = 19\) 时得到最大,但仅有 \(2399\) 个,发现硬排的话显然可以 , 爆 \(DFS\) 很棒。

如果用所有的 m - QZS 串扔进一个 \(ACAM\) 中。题意就变成了这样:

给定一个 \(AC\) 自动机 , 再给定一个目标串 , 问你至少要跳过几个字符,才不到达任何一个有标记的节点。

\(dp_{i , j}\) , 表示在目标串上匹配了 \(i\) 个 , 在 \(AC\) 自动机的 \(j\) 号节点。

设其 \(s_i\) 儿子为 \(k\) .
若此边合法,则:

\(dp_{i , k} = dp_{i - 1, j}\)

当然选择直接跳过:

\(dp_{i , k} = dp_{i - 1 , k} + 1\)


这里分析一下一种错误 \(dp\) 方式:

设法一样,就是如果此边不合法:就使其的儿子的值 \(+1\) .

错误之点在于:你是不知道任何其儿子的状态的,而且并不是到了最后时在转眼。

最好的方式是将状态直接存在父亲 \(j\) 上,由其儿子进行转移。

code

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 1e3 + 10 ; 
inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 

    while ( c < '0' || c > '9' ) {
        if ( c == '-' ) f = -f ; 

        c = getchar() ; 
    }

    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }

    return x * f ; 
}

char s[N] ; int n , m ; 
int cuse[21] , nums , Numer ; 
int bebuild[21] , sum[21] ; 
int bevis[21] ; int vis[(int)(1e6 + 100)] ; 
int dp[2][(int)(1e6 + 100)] ; 

class AC_Auto_Mation {
    public :
        struct One_Node {
            int son[10] , fail , back , ans ; 
        } trie[(int)(1e6 + 100)] ; 

        int numbol ; queue<int> q ; 

        void Insert( int Cekas[] , int len ) {
            int pos = 1 ; 
            
            for ( int i = 1 ; i <= len ; ++ i ) {
                if ( !trie[pos].son[Cekas[i]] ) {
                    trie[pos].son[Cekas[i]] = ++ numbol ; 
                    pos = numbol ; 
                } else {
                    pos = trie[pos].son[Cekas[i]] ; 
                }
            }

            trie[pos].back = 1 ; 
        } 

        void Get_Fail() {
            while ( !q.empty() ) {
                q.pop() ; 
            }
            
            for ( int i = 1 ; i < 10 ; ++ i ) {
                trie[0].son[i] = 1 ; 
            }

            q.push(1) ; 

            while ( !q.empty() ) {
                int k = q.front() ; q.pop() ; 

                for ( int i = 1 ; i < 10 ; ++ i ) {
                    int y = trie[k].son[i] , field = trie[k].fail ; 

                    if ( !y ) {
                        trie[k].son[i] = trie[field].son[i] ; 
                        continue ; 
                    }

                    trie[y].fail = trie[field].son[i] ; 
                    q.push(y) ; 
                }
            }
        }

        queue<int> q1 , q2 ; 

        inline void DP() {
            while ( !q1.empty() ) {
                q1.pop() ; 
            }

            while ( !q2.empty() ) {
                q2.pop() ; 
            }

            q1.push(1) ; 

            for ( int i = 1 ; i <= n ; ++ i ) {

                for ( int j = 1 ; j <= numbol ; ++ j ) {
                    dp[(i + 1) & 1][j] = 0x7f7f7f7f ; 
                }

                while ( !q1.empty() ) {
                    vis[q1.front()] = 0 ; 
                    q2.push(q1.front()) ; q1.pop() ; 
                }

                while ( !q2.empty() ) {
                    int k = q2.front() ; q2.pop() ; 
                    int y = trie[k].son[s[i] - '0'] ; 

                    if ( !trie[y].back ) {
                        dp[(i + 1) & 1][y] = min( dp[(i + 1) & 1][y] , dp[i & 1][k] ) ; 

                        if ( !vis[y] ) {
                            vis[y] = 1 ; q1.push(y) ; 
                        }
                    } 

                    dp[(i + 1) & 1][k] = min( dp[(i + 1) & 1][k] , dp[i & 1][k] + 1 ) ; 

                    if ( !vis[k] ) {
                        vis[k] = 1 ; 
                        q1.push(k) ; 
                    }
                }
            }
        }

        AC_Auto_Mation() {
            numbol = 1 ; 
            memset( trie , 0 , sizeof(trie) ) ; 
        }
} tree ; 

void dfs( int val , int pos , int me ) {
    if ( val == me ) {
        tree.Insert( bebuild , pos - 1 ) ; 
        
        return ; 
    }

    for ( int i = 1 ; i <= Numer ; ++ i ) {
        if ( pos != 1 ) {
            
            if ( sum[pos - 1] + cuse[i] == me ) {
                sum[pos] = sum[pos - 1] + cuse[i] ; 
                bebuild[pos] = cuse[i] ; 
                dfs(sum[pos] , pos + 1 , me) ; 
                continue ; 
            }

            bool flag = 0 ; 
            
            for ( int j = 1 ; j < pos ; ++ j ) {
                if ( sum[pos - 1] + cuse[i] > me || !bevis[sum[pos - 1] + cuse[i] - sum[j - 1]] ) {
                    flag = 1 ; break ; 
                }
            }

            if ( !flag ) {
                sum[pos] = sum[pos - 1] + cuse[i] ; 
                bebuild[pos] = cuse[i] ; 
                dfs(sum[pos] , pos + 1 , me) ; 
            }
        } else {
            bebuild[pos] = cuse[i] ; 
            sum[pos] = cuse[i] ; 
            dfs(sum[pos] , pos + 1 , me) ; 
        }
    }
}

signed main() {
#ifndef ONLINE_JUDGE
    freopen( "1.in" , "r" , stdin ) ; 
    freopen( "1.out", "w" ,stdout ) ; 
#endif

    cin >> s + 1 ; n = strlen( s + 1 ) ; 
    m = read() ; 

    if ( m == 1 ) {
        int ans = 0 ; 

        for ( int i = 1 ; i <= n ; ++ i ) {
            ans = s[i] == '1' ? 1 + ans : ans ; 
        }

        cout << ans << '\n' ; 

        return 0 ; 
    }

    for ( int i = 1 ; i <= min( (int)9 , m ) ; ++ i ) {
        if ( m % i != 0 ) {
            cuse[++ Numer] = i ; 
        }
    }

    if ( m <= 9 )
        cuse[++ Numer] = m ; 

    for ( int i = 1 ; i <= m ; ++ i ) {
        if ( m % i != 0 ) {
            bevis[i] = 1 ; 
        }
    }

    dfs( 0 , 1 , m ) ; 

    tree.Get_Fail() ; 

    tree.DP() ; 

    int ans = 0x7f7f7f7f ; 

    for ( int i = 1 ; i <= tree.numbol ; ++ i ) {
        ans = min( dp[(n + 1) & 1][i] , ans ) ;  
    }

    cout << ans ; 
}

\(T_H\) Scissors

$$codeforces$$

题意

给定一个长度为 \(n\) 目标串,给到一个长度为 \(m\) 的模式串 , 你在模式串中按顺序剪出两个不重叠的长度为 \(k\) 的子串,并按 原序 拼在一起 , 输出一种合法的解。

\(k \times 2 \le m \le n \le 5 \times 10^5\)

题解

我好像是第一篇用 \(KMP\)\(ST\) 表混用的题解。

首先我们定义两个数组 \(Matchfront_i\)\(Matchback_i\) .

\(Matchfront_i\)\(i \ ( 1 \le i \le n )\) 个,即由 \(1\) 开始到 \(i\) 的对于目标串的前缀的后缀匹配到模式串的前缀长度。例:

\[abcabcaab(目标串) \]

\[abcab(模式串) \]

对于 \(Matchfront_4 = 4\)

\(Matchback_i\)\(i\) 个即由 \(i\) 开始到 \(n\) 的对于目标串的后缀的前缀匹配模式串的后缀长度。例:

还是对于上面的例子,

\(Matchback_8 = 2\)

我们为了满足能剪出两个 \(k\) , 由 \(i = k → n-k+1\)

\(m - Matchfront_i\) 是后面的必有的 \(Matchback_j\)

\(\therefore\) 需枚举。但将 \(j\) 枚举一遍就 \(T\) 了,所以我们拿个 \(ST\) 表存一下其中的最大值。

我们还需要考虑反着的,即

\(m - Matchback_i\)\(Matchfront_j\) 我们还需要判断答案是否合法,具体看代码

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 5e5 + 10 ; 
const int down = 10 ; 
inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 
    while ( c < '0' || c > '9' ) {
        if ( c == '-' ) f = -f ; 
        
        c = getchar() ; 
    }
    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }
    return x * f ; 
}
int n , m , k ; int ST1[N][20] , ST2[N][20] ; int lg[N] ; 
char s[N] , t[N] , tf[N] , sf[N] ; 
int Next_front[N] , Next_back[N] ; 
int Match_front[N] , Match_back[N] ; 
inline int lty1( int l , int r ) {
    int k0 = lg[r - l + 1] ; 
    return max( ST1[l][k0] , ST1[r - ( 1 << k0 ) + 1 ][k0] ) ; 
}
inline int lty2( int l , int r ) {
    int k0 = lg[r - l + 1] ; 
    return max( ST2[l][k0] , ST2[r - ( 1 << k0 ) + 1 ][k0] ) ; 
}
signed main() {
#ifndef ONLINE_JUDGE
    freopen( "1.in" , "r" , stdin ) ; 
    freopen( "1.out" , "w" , stdout ) ; 
#endif 
    n = read() , m = read() , k = read() ; 
    lg[1] = 0 ; 
    for ( int i = 2 ; i <= n ; ++ i ) {
        lg[i] = lg[i >> 1] + 1 ; 
    }
    cin >> s + 1 ; cin >> t + 1 ; 
    for ( int i = 1 ; i <= n ; ++ i ) {
        sf[i] = s[n - i + 1] ; // 反着存目标串
    }
    for ( int i = 1 ; i <= m ; ++ i ) {
        tf[i] = t[m - i + 1] ; // 反着存模式串
    }
    for ( int i = 2 , j = 0 ; i <= m ; ++ i ) {
        while ( j && t[i] != t[j + 1] ) j = Next_front[j] ; 

        if ( t[i] == t[j + 1] ) j ++ ; 

        Next_front[i] = j ; 
    }
    for ( int i = 2 , j = 0 ; i <= m ; ++ i ) {
        while ( j && tf[i] != tf[j + 1] ) j = Next_back[j] ; 

        if ( tf[i] == tf[j + 1] ) j ++ ; 

        Next_back[i] = j ; 
    }
    for ( int i = 1 , j = 0 ; i <= n ; ++ i ) {
        while ( j && ( j == m || s[i] != t[j + 1] ) ) j = Next_front[j] ; 

        if ( s[i] == t[j + 1] ) Match_front[i] = ++ j ; 
    }
    for ( int i = 1 , j = 0 ; i <= n ; ++ i ) {
        while ( j && ( j == m || sf[i] != tf[j + 1] ) ) j = Next_back[j] ; 
        
        if ( sf[i] == tf[j + 1] ) Match_back[n - i + 1] = ++ j ; 
    }
    for ( int i = 1 ; i <= n ; ++ i ) {
        ST1[i][0] = min( k , Match_back[i] ) ; 
        ST2[i][0] = min( k , Match_front[i] ) ; 
    }
    for ( int j = 1 ; j <= lg[n] ; ++ j ) {
        for ( int i = 1 ; i + ( 1 << j ) - 1 <= n ; ++ i ) {
            ST1[i][j] = max( min( ST1[i][j - 1] , k ) , min( ST1[i + ( 1 << ( j - 1 ) )][j - 1] , k ) ) ; 
            ST2[i][j] = max( min( ST2[i][j - 1] , k ) , min( ST2[i + ( 1 << ( j - 1 ) )][j - 1] , k ) ) ; 
        }
    }
    for ( int i = 1 ; i <= k - 1 ; ++ i ) {
        if ( Match_front[i] == m ) {
            cout << "Yes" << '\n' ; 
            cout << 1 << ' ' << k + 1 << '\n' ; 
            
            return 0 ; 
        }
    }
    for ( int i = n - k + 1 ; i <= n ; ++ i ) {
        if ( Match_front[i] == m ) {
            cout << "Yes" << '\n' ; 
            cout << 1 << ' ' << n - k + 1 << '\n' ; 
            
            return 0 ; 
        } 
    }
    for ( int i = k ; i <= n - k ; ++ i ) {
        if ( m - min( Match_front[i] , k ) <= lty1( i + 1 , n - k + 1 ) ) {
            for ( int j = i + 1 ; j <= n - k + 1 ; ++ j ) {
                if ( m - min( Match_front[i] , k ) <= Match_back[j] ) {
                    if ( Match_front[i] <= k ) { 
                        if ( j + Match_back[j] - m + Match_front[i] <= n - k + 1 ) { // 使达到的位置的字符串能被剪下来
                            cout << "Yes" << '\n' ; 
                            cout << i - k + 1 << ' ' << j + Match_back[j] - m + Match_front[i] << '\n' ; 
                        } else continue ; 
                    } else {
                        if ( j + Match_back[j] - m + k <= n - k + 1 ) {// 使达到的位置的字符串能被剪下来
                            cout << "Yes" << '\n' ; 
                            cout << i - k + 1 << ' ' << j + Match_back[j] - m + k << '\n' ; 
                        } else continue ; 
                    }
                    return 0 ; 
                }
            }
        }
    }
    for ( int i = k + 1 ; i <= n - k + 1 ; ++ i ) {
        if ( m - min( Match_back[i] , k ) <= lty2( k , i - 1 ) ) {
            for ( int j = k ; j <= i - 1 ; ++ j ) {
                if ( m - min( Match_back[i] , k ) <= lty2( k , i - 1 ) ) {
                    if ( Match_back[i] <= k ) {
                        if ( Match_front[j] - m + Match_back[i] >= 1 ) {// 使达到的位置的字符串能被剪下来
                            cout << "Yes" << '\n' ; 
                            cout << Match_front[j] - m + Match_back[i] << ' ' << i << '\n' ; 
                        } else continue ; 
                    } else {
                        if ( Match_front[j] - m + k >= 1 ) {// 使达到的位置的字符串能被剪下来
                            cout << "Yes" << '\n' ; 
                            cout << Match_front[j] - m + k << ' ' << i << '\n' ; 
                        } else continue ; 
                    }
                    return 0 ; 
                }
            }
        }
    }
    cout << "No" ; 
    return 0 ; 
}

\(T_I\) Minimax

简化题意;

给定字符串中每种字符的个数,让你构造一个数列满足以下条件:

1.对于其所有的前缀求出其 \(border\) 并比对每个的求出最大值定为 \(f(x)\) ,沟造出来的字符串要使其最小。

2.使之字典序最小

\(n \le 10^5\)

题解

说实话看到这个数据范围的时候挺呆的,甚至想枚举首尾的字符,毕竟刚刚不会 \(T\) .

后来发现完全大力分讨即可。

对于分讨情况,给到下面的几个样例:

cjs
ccc
ccccccccccccccccccccccccccccccccccjs
cccjs
cjsc
ccccjs
ccccccjj

1.对于有一个字符只有一个时, cjs , 这时若把此放在头,则 \(f(x) = 0\) 只需将字典序最小的且个数为一的放在最前面,后面的按字典序输出即可。

2.对于只有一个字符的,那只能瞎 jb 输出了,毕竟都一样 悲

3.对于这几种:

cccjs
ccjs
ccccjs

同有一种构造方式:

ccjcs
ccjs
ccjcsc

4.对于有一堆这种一个的 ccccccccccccccccccccccccccccccccccjs

这么构造 : cjcccccccccccccccccccccccccccccccccs

对于个数小于两个的 ccccccjj 这么构造就寄了,可以这么构造:

cjjccccc

所以...代码:

CODE
#include <bits/stdc++.h>
#define int long long 
using namespace std ; 
const int N = 1e6 + 100 ; 
inline int read() {
    int x = 0 , f = 1 ; 
    char c = getchar() ; 
    
    while ( c < '0' || c > '9' ) {
        f = c == '-' ? -f : f ; 
        c = getchar() ; 
    }
    
    while ( c >= '0' && c <= '9' ) {
        x = x * 10 + c - '0' ; 
        c = getchar() ; 
    }
    
    return x * f ; 
}
    
int T , bucket[100] , n , first , numbol ;  
char s[N] ; bool flag ; 
stack<int>sta ; 
    
signed main() {
#ifndef ONLINE_JUDGE
    freopen( "1.in" , "r" , stdin ) ; 
    freopen( "1.out", "w" ,stdout ) ; 
#endif
    
    auto print = []() {
        for ( int i = 0 ; i < 26 ; ++ i ) {
            while ( bucket[i] > 0 ) {
                bucket[i] -- ; 
                cout << (char)( i + 'a' ) ; 
            }
        }
    
        cout << '\n' ; 
    } ; 
    
    auto Charac = [](int Int_) {
        return (char)( Int_ + 'a' ) ; 
    } ; 
    
    T = read() ; 
    
    while ( T -- ) {
        numbol = 0 ; 
        memset( bucket , 0 , sizeof(bucket) ) ; 
        flag = 0 ; first = -1 ; 
    
        cin >> s + 1 ; n = strlen( s + 1 ) ; 
        for ( int i = 1 ; i <= n ; ++ i ) {
            if ( !bucket[s[i] - 'a'] ) numbol ++ ; 
    
            bucket[s[i] - 'a'] ++ ; 
        }
    
        if ( n == 0 ) continue ; 
    
        for ( int i = 0 ; i < 26 ; ++ i ) {
            if ( first == -1 && bucket[i] ) {
                first = i ; 
            }
    
            if ( bucket[i] == 1 ) {
                cout << (char)( i + 'a' ) ; 
                -- bucket[i] ; 
                print() ; 
                flag = 1 ; break ; 
            } else if ( bucket[i] == n ) {
                print() ; 
                flag = 1 ; break ; 
            }
        }
    
        if ( flag == 1 ) continue ; 
    
        if ( n - ( n - bucket[first] ) * 2 == 2 || ( n - bucket[first] ) * 2 + 1 == n || n == 2 * bucket[first] ) {
    
            while ( !sta.empty() ) {
                sta.pop() ; 
            }
    
            for ( int i = 25 ; i > first ; -- i ) {
                int tmp = bucket[i] ; 
                
                while ( tmp > 0 ) {
                    tmp -- ; 
                    sta.push(i) ; 
                }
            }
    
            int leav = n - bucket[first] ; 
    
            cout << Charac(first) << Charac(first) ; bucket[first] -= 2 ; 
            int tmp = bucket[first] ; 
    
            for ( int i = 1 ; i <= leav ; ++ i ) {
                cout << Charac(sta.top()) ; sta.pop() ; 
                
                if ( tmp ) { 
                    cout << Charac(first) ; tmp -- ;
                }
            }
            
            cout << '\n' ; 
        } else if ( numbol != 2 && bucket[first] > (n >> 1) ) {
            int tmp_fir = -1 , tmp_sec = -1 ; 
            for ( int i = first + 1 ; i < 26 ; ++ i ) {
                if ( bucket[i] && tmp_fir == -1 ) {
                    tmp_fir = i ; 
                } else if ( bucket[i] ) {
                    tmp_sec = i ; break ; 
                }
            }
            
            cout << Charac(first) << Charac(tmp_fir) ; 
            bucket[tmp_fir] -- ; bucket[first] -- ; 
    
            for ( int i = 1 ; i <= bucket[first] ; ++ i ) {
                cout << Charac(first) ; 
            }
            
            bucket[first] = 0 ; 
            cout << Charac(tmp_sec) ; bucket[tmp_sec] -- ; 
    
            print() ; 
        } else {
            
            if ( numbol == 2 && bucket[first] > n - bucket[first] ) {
                cout << Charac(first) ; bucket[first] -- ; 
                int tmp ; 
    
                for ( int i = first + 1 ; i < 26 ; ++ i ) {
                    if ( bucket[i] ) {
                        tmp = i ; break ; 
                    }
                }
     
                for ( int i = 1 ; i <= bucket[tmp] ; ++ i ) {
                    cout << Charac(tmp) ; 
                }
                bucket[tmp] = 0 ; 
    
                print() ; 
            } else {
                cout << Charac(first) ; 
                bucket[first] -- ; 
    
                while ( !sta.empty() ) {
                    sta.pop() ; 
                }
    
                for ( int i = 25 ; i > first ; -- i ) {
                    int tmp = bucket[i] ; 
                    
                    while ( tmp > 0 ) {
                        tmp -- ; 
                        sta.push(i) ; 
                    }
                }
    
                for ( int i = 1 ; i <= bucket[first] ; ++ i ) {
                    cout << Charac(first) << Charac(sta.top()) ; sta.pop() ;  
                }
    
                while ( !sta.empty() ) {
                    cout << Charac(sta.top()) ; sta.pop() ; 
                }
    
                cout << '\n' ; 
            }
            
        }
    }
}

结尾撒花 \(\color{pink}{✿✿ヽ(°▽°)ノ✿}\)

posted @ 2024-03-29 08:09  HANGRY_Sol&Cekas  阅读(83)  评论(5编辑  收藏  举报