12-7 ~ 12-19杂题

我已经是一条咸鱼了
随便写下两个星期来干的闲事

[USACO13JAN]Cow Lineup

Description

长度为 N(1N105) 的数字序列 (1ai109) ,最多可删除 k 种数字。求同一种数字能够连续出现的最大长度。

Solution

猫说可以二分。
然后我就开始二分了。
直接二分答案,最大长度。但是要把相同的数字用链表穿起来。判断是否可行的时候,枚举作为答案的那个数字,然后看看是否存在某一段之间存在不同的数字小于等于k
写法比较工业,主席树在线询问区间不同数字个数。
具体怎么用主席树写这个吗,下面有。
每次判断的复杂度均摊下来是 O(Nlog2N) 的。其中扫数字 O(N) ,查询 O(log2N) 。再加上二分所以总复杂度是 O(Nlog22N)

Code

#include <bits/stdc++.h>
using namespace std ;
void Read ( int &x, char c = getchar() ) {
    for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
    for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
const int maxn = 1e5+5 ;
int n, m, k, a[maxn], b[maxn], size[maxn], Head[maxn], Next[maxn], Pre[maxn] ;
int tot, tree[maxn*60], ch[maxn*60][2], rt[maxn] ;
void push_up ( int x ) {
    tree[x] = 0 ;
    if (ch[x][0]) tree[x] += tree[ch[x][0]] ;
    if (ch[x][1]) tree[x] += tree[ch[x][1]] ;
}
int create ( int l, int r, int v ) {
    int x = ++tot, mid = (l+r)>>1 ;
    if (l == r) {
        ++tree[x] ;
        return x ;
    }
    if (v <= mid) ch[x][0] = create(l, mid, v) ;
    else ch[x][1] = create(mid+1, r, v) ;
    push_up(x) ;
    return x ;
}
int insert ( int h, int l, int r, int v, int c ) {
    int x = ++tot, mid = (l+r)>>1 ;
    if (l == r) {
        if (h) tree[x] = tree[h] ;
        tree[x] += c ;
        return x ;
    }
    if (v <= mid) {
        ch[x][0] = insert(ch[h][0], l, mid, v, c) ;
        if (h) ch[x][1] = ch[h][1] ;
    } else {
        ch[x][0] = ch[h][0] ;
        if (h) ch[x][1] = insert(ch[h][1], mid+1, r, v, c) ;
    }
    push_up(x) ;
    return x ;
}
int query ( int h, int l, int r, int x, int y ) {
    if (x <= l && r <= y) return tree[h] ;
    int mid = (l+r)>>1 ;
    if (y <= mid) return query(ch[h][0], l, mid, x, y) ;
    else if (x > mid) return query(ch[h][1], mid+1, r, x, y) ;
    return query(ch[h][0], l, mid, x, mid)+query(ch[h][1], mid+1, r, mid+1, y) ;
}
bool calc ( int x, int value ) {
    int rec, l, r ;
    for ( r = l = Head[x] ; value > 1 ; r = Next[r], value -- ) ;
    if (r == 0) return 0 ;
    for ( ; l && r ; l = Next[l], r = Next[r] ) {
        rec = query(rt[r], 1, n, l, r) ;
        if (rec <= m+1) return 1 ;
    }
    return 0 ;
}
bool judge ( int value ) {
    int i ;
    for ( i = 1 ; i <= n ; i ++ )
        if (size[i] >= value && calc(i, value)) return 1 ;
    return 0 ;
}
int main() {

    int i ;
    Read(n) ; Read(m) ;
    for ( i = 1 ; i <= n ; i ++ ) Read(a[i]), b[i] = a[i] ;
    sort(b+1, b+n+1) ;
    k = unique(b+1, b+n+1)-b-1 ;
    for ( i = 1 ; i <= n ; i ++ )
        ++size[a[i] = lower_bound(b+1, b+k+1, a[i])-b] ;

    for ( i = n ; i ; i -- )
        if (Head[a[i]]) {
            Pre[Head[a[i]]] = i ;
            Next[i] = Head[a[i]], Head[a[i]] = i ;
        }
        else Head[a[i]] = i ;

    rt[1] = create(1, n, 1) ;
    for ( i = 2 ; i <= n ; i ++ ) {
        if (Pre[i]) {
            rt[i] = insert(rt[i - 1], 1, n, Pre[i], -1) ;
            rt[i] = insert(rt[i], 1, n, i, 1) ;
        } else rt[i] = insert(rt[i - 1], 1, n, i, 1) ;
    }
    int l, r, mid, rec = 0 ;
    l = 1, r = n ;
    while (l <= r) {
        mid = (l+r)>>1 ;
        if (judge(mid)) rec = mid, l = mid+1 ;
        else r = mid-1 ;
    }
    printf ( "%d\n", rec ) ;
    return 0 ;
}

[SDOI2009]HH的项链

Description

长度为 N(1N5×104) 的序列, Q(1Q2×105) 次询问,每次询问区间 [Li,Ri] 之间不同数字的个数。

Solution

离线可以用莫队,好久以前写过。
某天晚上闲着无聊,睡觉又太早,就写个板子娱乐一下。
在线做法主席树。
对每个点建立一棵线段树,线段树 i 维护{1,0,1}序列表示原序列的区间 [1,i] 上的值的贡献( {1,0,1} 表示贡献)。
新加入数字 ai ,若该数字以前在位置 pre[i] 出现过,那么线段树 i 就应该在其维护的序列上的位置i设为 1 pre[i]设为 1
例如,序列 {1,5,2,4,3,5,6,7} ,其中第 5 棵线段树所维护的序列应该是{1,1,1,1,1,1,0,0}
每次询问 [L,R] ,只需在第 R 棵线段树上查询[L,R]的和即可。
可持久化即可。
值得注意的是,每次加入一个数可能之前还要修改某个位置为 1 ,也就代表着可能新增 2 条链,即log2N个点。注意数组。

Code

#include <bits/stdc++.h>
using namespace std ;
void Read ( int &x, char c = getchar() ) {
    for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
    for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
const int maxn = 5e4+5 ;
int n, m, a[maxn], b[maxn], pre[maxn] ;
int tree[maxn*60], ch[maxn*60][2], rt[maxn], tot ;
void push_up ( int x ) {
    tree[x] = 0 ;
    if (ch[x][0]) tree[x] += tree[ch[x][0]] ;
    if (ch[x][1]) tree[x] += tree[ch[x][1]] ;
}
int create ( int l, int r, int v ) {
    int x = ++tot, mid = (l+r)>>1 ;
    if (l == r) {
        tree[x] ++ ;
        return x ;
    }
    if (v <= mid) ch[x][0] = create(l, mid, v) ;
    else ch[x][1] = create(mid+1, r, v) ;
    push_up(x) ;
    return x ;
}
int insert ( int h, int l, int r, int v, int value ) {
    int x = ++tot, mid = (l+r)>>1 ;
    if (l == r) {
        tree[x] = tree[h] ;
        tree[x] += value ;
        return x ;
    }
    if (v <= mid) {
        ch[x][0] = insert(ch[h][0], l, mid, v, value) ;
        ch[x][1] = ch[h][1] ;
    } else {
        ch[x][0] = ch[h][0] ;
        ch[x][1] = insert(ch[h][1], mid+1, r, v, value) ;
    }
    push_up(x) ;
    return x ;
}
int query ( int h, int l, int r, int x, int y ) {
    if (h == 0) return 0 ;
    if (x <= l && r <= y) return tree[h] ;
    int mid = (l+r)>>1 ;
    if (y <= mid) return query(ch[h][0], l, mid, x, y) ;
    else if (x > mid) return query(ch[h][1], mid+1, r, x, y) ;
    return query(ch[h][0], l, mid, x, mid)+query(ch[h][1], mid+1, r, mid+1, y) ;
}
void check ( int x, int l, int r ) {
    printf ( "%d [%d,%d]=%d\n", x, l, r, tree[x] ) ;
    int mid = (l+r)>>1 ;
    if (ch[x][0]) check(ch[x][0], l, mid) ;
    if (ch[x][1]) check(ch[x][1], mid+1, r) ;
}
int main() {
    int i, l, r, _ ;
    Read(n) ;
    for ( i = 1 ; i <= n ; i ++ ) Read(a[i]), b[i] = a[i] ;
    sort(b+1, b+n+1) ;
    m = unique(b+1, b+n+1)-b-1 ;
    for ( i = 1 ; i <= n ; i ++ )
        a[i] = lower_bound(b+1, b+m+1, a[i])-b ;
    memset ( b, 0, sizeof b ) ;
    for ( i = 1 ; i <= n ; i ++ )
        pre[i] = b[a[i]], b[a[i]] = i ;
    rt[1] = create(1, n, 1) ;
    for ( i = 2 ; i <= n ; i ++ ) {
        if (pre[i]) {
            rt[i] = insert(rt[i - 1], 1, n, pre[i], -1) ;
            rt[i] = insert(rt[i], 1, n, i, 1) ;
        } else rt[i] = insert(rt[i - 1], 1, n, i, 1) ;
    }
    Read(_) ;
    while (_--) {
        Read(l) ; Read(r) ;
        printf ( "%d\n", query(rt[r], 1, n, l, r) ) ;
    }
    return 0 ;
}

[SCOI2008]奖励关

Description

复制下来占地方
k100,N15

Solution

本来不会做,看到数据范围就会了。
状压DP。
f[i][x] 代表当前是第 i 轮,已经获取过的宝物集合为x
每一轮枚一下出来宝物的种类,状态为 O(K×2N) ,转移为 O(N) 。总复杂度是 O(K×N×2N)

Code

#include <bits/stdc++.h>
using namespace std ;
int n, m, c[105], p[105] ;
double f[105][(1<<15)+5] ;
int main() {
    int i, j, k, s, S ;
    scanf ( "%d%d", &m, &n ) ;
    S = (1<<n)-1 ;
    for ( i = 1 ; i <= n ; i ++ ) {
        scanf ( "%d", c+i ) ;
        while (scanf("%d",&k) && k)
            p[i] |= 1<<(k-1) ;
    }
    for ( i = m-1 ; i >= 0 ; i -- ) {
        for ( s = 0 ; s <= S ; s ++ )
            for ( j = 1 ; j <= n ; j ++ )
                if ( (p[j]&s) == p[j] )
                    f[i][s] += max(f[i + 1][s]/n, (f[i + 1][s|(1<<(j-1))]+c[j])*1.0/n) ;
                else f[i][s] += f[i + 1][s]/n ;
    }
    printf ( "%.6lf\n", f[0][0] ) ;
    return 0 ;
}

[AHOI2009]中国象棋

Description

在一个 N M列的棋盘上,让你放若干个炮(可以是 0 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法,模9999973 N,M100

Solution

此题巧妙无比,看了题解才会做。
套路啊。
反正每一行每一列放的数量只可能是 {0,1,2}
f[i][a][b] 代表前 i 行,有a列放了 0 个炮,有b列放了 1 个炮,那么有Mab列放了 2 个炮。
这一行可以不放,也可以放1个。也可以放 2 个。
分类讨论统计答案即可。
这种状态定的简直妙啊。

Code

#include <bits/stdc++.h>
#define LL long long
using namespace std ;
const int maxn = 105, modd = 9999973 ;
int n, m ;
LL f[maxn][maxn][maxn] ;
LL c ( int x ) { return ((LL)x*(x-1)/2)%modd ; }
int main() {
    int i, a, b ;
    scanf ( "%d%d", &n, &m ) ;
    f[0][m][0] = 1 ;
    for ( i = 0 ; i < n ; i ++ ) {
        for ( a = 0 ; a <= m ; a ++ )
            for ( b = 0 ; a+b <= m ; b ++ ) {
                (f[i + 1][a][b] += f[i][a][b]) %= modd ;
                if (a >= 1) (f[i + 1][a - 1][b + 1] += a*f[i][a][b]%modd) %= modd ;
                if (b >= 1) (f[i + 1][a][b - 1] += b*f[i][a][b]%modd) %= modd ;
                if (a >= 1) (f[i + 1][a - 1][b] += a*b*f[i][a][b]%modd) %= modd ;
                if (a >= 2) (f[i + 1][a - 2][b + 2] += c(a)*f[i][a][b]%modd) %= modd ;
                if (b >= 2) (f[i + 1][a][b - 2] += c(b)*f[i][a][b]%modd) %= modd ;
            }
    }
    LL ans = 0 ;
    for ( a = 0 ; a <= m ; a ++ )
        for ( b = 0 ; a+b <= m ; b ++ )
            (ans += f[n][a][b]) %= modd ;
    printf ( "%lld\n", ans ) ;
    return 0 ;
}

[SCOI2005]最大子矩阵

Description

这里有一个n×m的矩阵,请你选出其中 k 个子矩阵,使得这个k个子矩阵分值之和最大。注意:选出的 k 个子矩阵不能相互重叠。1n100,1m2,1k10

Solution

m 分情况讨论。
m=1。暴力DP。随便搞。

m=2
先记录三个前缀和,单独第1列,单独第2列,两列加起来。
f[a][b][t] 表示第1列最后一个选取的位置是a,第2列最后一个选取的位置是b,一共选了t个矩形。讨论当前新选一个矩形是完全处于第1列还是完全处于第2列还是宽为2。
DP转移的时候枚举前一个矩形的结尾,当前矩形的开头用RMQ在前缀和上查询。
状态为 O(n2×k) ,转移为 O(n) ,总复杂度为 O(n3×k)
网上别人的做法貌似是 O(nk) 的,我还是太辣鸡了。

Code

#include <bits/stdc++.h>
using namespace std ;
const int maxn = 105, zhf = 1e7 ;
int n, m, k, a[maxn][3], g[maxn][10][3], f[maxn][maxn][30], dp[maxn][30] ;
void init ( int t ) {
    int i, j ;
    for ( i = 1 ; i <= n ; i ++ )
        g[i][0][t] = (a[i][t] += a[i - 1][t]) ;
    for ( j = 1 ; j < 10 ; j ++ )
        for ( i = 1 ; i+(1<<j)-1 <= n ; i ++ )
            g[i][j][t] = min(g[i][j-1][t], g[i+(1<<(j-1))][j-1][t]) ;
}
int query ( int l, int r, int t ) {
    if (l > r) return zhf ;
    int len = floor(log(r-l+1)/log(2.0)) ;
    return min(g[l][len][t], g[r-(1<<len)+1][len][t]) ;
}
int check_max ( int &x, int y ) { return x = x>y ? x:y ; }
void solve1() {
    int i, j, l ;
    for ( i = 1 ; i <= n ; i ++ )
        scanf ( "%d", &a[i][0] ) ;
    init(0) ;
    for ( i = 1 ; i <= n ; i ++ ) {
        for ( j = 0 ; j < i ; j ++ )
            for ( l = 1 ; l <= k ; l ++ ) {
                check_max(dp[i][l], dp[j][l-1] + a[i][0] - query(j,i-1,0)) ;
            }
    }
    int ans = 0 ;
    for ( i = 1 ; i <= n ; i ++ )
        for ( j = 1 ; j <= k ; j ++ )
            check_max(ans, dp[i][j] ) ;
    printf ( "%d\n", ans ) ;
}
void solve2() {
    register int ans = 0, i, j, l, r, t ;
    for ( i = 1 ; i <= n ; i ++ ) {
        scanf ( "%d%d", &a[i][0], &a[i][1] ) ;
        a[i][2] = a[i][0] + a[i][1] ;
    }
    init(0) ; init(1) ; init(2) ;
    for ( i = 0 ; i <= n ; i ++ )
        for ( j = 0 ; j <= n ; j ++ ) {
            check_max(f[i][j][(i>=1)+(j>=1)], a[i][0]+a[j][1]) ;
            for ( t = 1 ; t <= k ; t ++ ) {
                for ( l = 0 ; l < i ; l ++ )
                    check_max(f[i][j][t], f[l][j][t-1] + a[i][0] - query(l,i-1,0)) ;
                for ( l = 0 ; l < j ; l ++ )
                    check_max(f[i][j][t], f[i][l][t-1] + a[j][1] - query(l,j-1,1)) ;
                if (i == j && i) {
                    for ( l = 0 ; l < i ; l ++ )
                        for ( r = 0 ; r < i ; r ++ )
                            check_max(f[i][j][t], f[l][r][t-1] + a[i][2] - query(max(l,r),i-1,2)) ;
                }
                check_max(ans, f[i][j][t]) ;
            }
        }
    printf ( "%d\n", ans ) ;
}
int main() {
    scanf ( "%d%d%d", &n, &m, &k ) ;
    if (m == 1) solve1() ;
    else solve2() ;
    return 0 ;
}

[SCOI2005]互不侵犯King

Description

N×N 的棋盘里面放 K 个国王,使他们互不攻击,共有多少种摆放方案。N9

Solution

本来不会做,看了范围才会。
状压DP,转移时枚举上一行状态。
时间复杂度 O(N×22N)
然后也是某个晚上闲着无聊,睡觉又太早就娱乐了一波。

Code

#include <bits/stdc++.h>
#define LL long long
using namespace std ;
LL n, m, f[10][520][100], bt[520] ;
bool judge ( LL a, LL b ) {
    if (a&b) return 0 ;
    a |= b ;
    if (a&(a>>1)) return 0 ;
    return 1 ;
}
int main() {
    LL i, j, s, x, S ;
    cin >> n >> m ;
    S = (1<<n)-1 ;
    for ( s = 0 ; s <= S ; s ++ )
        bt[s] = bt[s>>1] + (s&1) ;
    memset ( f, 0, sizeof f ) ;
    for ( s = 0 ; s <= S ; s ++ )
        if (judge(0, s)) f[1][s][bt[s]] = 1 ;
    for ( i = 2 ; i <= n ; i ++ )
        for ( s = 0 ; s <= S ; s ++ )
            for ( x = 0 ; x <= S ; x ++ )
                if (judge(s, x)) {
                    for ( j = bt[s]+bt[x] ; j <= m ; j ++ )
                        if (f[i - 1][x][j - bt[s]] > 0) f[i][s][j] += f[i - 1][x][j - bt[s]] ;
                }
    LL ans = 0 ;
    for ( s = 0 ; s <= S ; s ++ )
        if (f[n][s][m] > 0) ans += f[n][s][m] ;
    printf ( "%lld\n", ans ) ;
    return 0 ;
}

[USACO5.1]Musical Themes

Description

转化一下题意之后,求最长重复子串。 N5000

Solution

链一波易大佬的DP做法
然而我太菜了不会DP,只好用后缀数组的套路来做。
二分答案,对height分组。都是套路没什么好说的。

Code

#include <bits/stdc++.h>
using namespace std ;
const int maxn = 2e5+5 ;
int rnk[maxn], s[maxn], n, m, sa[maxn], height[maxn], s1[maxn], s2[maxn], c[maxn], a[maxn] ;
void get_sa() {
    int i, p, len ;
    int *x = s1, *y = s2 ;
    for ( i = 1 ; i <= n ; i ++ ) m = max(m, s[i]) ;
    memset ( c, 0, sizeof c ) ;
    for ( i = 1 ; i <= n ; i ++ ) c[x[i] = s[i]] ++ ;
    for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
    for ( i = n ; i ; i -- ) sa[c[x[i]]--] = i ;

    for ( len = 1 ; len < n ; len <<= 1, m = p ) {
        p = 0 ;
        for ( i = n-len+1 ; i <= n ; i ++ ) y[++p] = i ;
        for ( i = 1 ; i <= n ; i ++ )
            if (sa[i] > len) y[++p] = sa[i]-len ;

        memset ( c, 0, sizeof c ) ;
        for ( i = 1 ; i <= n ; i ++ ) c[x[y[i]]] ++ ;
        for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
        for ( i = n ; i ; i -- ) sa[c[x[y[i]]]--] = y[i] ;

        swap(x, y) ;
        p = 1 ;
        x[sa[1]] = 1 ;
        for ( i = 2 ; i <= n ; i ++ )
            if (y[sa[i]]==y[sa[i-1]] && y[sa[i]+len]==y[sa[i-1]+len])
                x[sa[i]] = p ;
            else x[sa[i]] = ++p ;
        if (p >= n) break ;
    }
}
void get_height() {
    int i, j, k = 0 ;
    for ( i = 1 ; i <= n ; i ++ ) rnk[sa[i]] = i ;
    for ( i = 1 ; i <= n ; i ++ ) {
        if (k) -- k ;
        j = sa[rnk[i]-1] ;
        while (s[i+k] == s[j+k]) ++ k ;
        height[rnk[i]] = k ;
    }
}
bool judge ( int len ) {
    int i, minn = sa[1], maxx = sa[1] ;
    for ( i = 2 ; i <= n ; i ++ )
        if (height[i] < len) minn = maxx = sa[i] ;
        else {
            minn = min(minn, sa[i]) ;
            maxx = max(maxx, sa[i]) ;
            if (maxx - minn > len) return 1 ;
        }
    return 0 ;
}
int main() {
    int i, l, r, mid, rec ;
    scanf ( "%d", &n ) ;
    for ( i = 1 ; i <= n ; i ++ ) scanf ( "%d", a+i ) ;
    for ( i = 1 ; i <= n ; i ++ ) s[i] = a[i] - a[i - 1] + 100 ;

    get_sa() ;
    get_height() ;
    l = 1, r = n>>1 ;
    while (l <= r) {
        mid = (l+r)>>1 ;
        if (judge(mid)) rec = mid, l = mid+1 ;
        else r = mid-1 ;
    }
    if (rec < 4) rec = 0 ;
    else ++ rec ;
    printf ( "%d\n", rec ) ;
    return 0 ;
}

[HAOI2008]硬币购物

Description

硬币购物一共有 4 种硬币。面值分别为c1, c2 , c3 , c4 。某人去商店买东西,去了 tot 次。每次带 di ci 硬币,买 si 的价值的东西。请问每次有多少种付款方法。 di,s100000,tot1000

Solution

要是没有硬币的个数限制就背包。
具体叫什么名字忘记了。
加上了硬币个数,就把不合理的强行减去。容斥。
蛇皮操作,写法奇丑无比。

#include <bits/stdc++.h>
#define LL long long
using namespace std ;
void Read ( LL &x, char c = getchar() ) {
    for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
    for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
LL n, m, v[5] ;
LL f[100005] ;
void calc ( LL &rec, LL x, LL value ) {
    if (x >= 0) rec += f[x]*value ;
}
int main() {
    LL i, j, t, rec, a, b, c, d ;
    for ( i = 1 ; i < 5 ; i ++ ) Read(v[i]) ;
    Read(m) ;
    n = 1e5 ;
    f[0] = 1 ;
    for ( i = 1 ; i < 5 ; i ++ )
        for ( j = v[i] ; j <= n ; j ++ )
            f[j] += f[j - v[i]] ;
    for ( i = 1 ; i <= m ; i ++ ) {
        rec = 0 ;
        Read(a) ; Read(b) ; Read(c) ; Read(d) ; Read(t) ;
        ++ a ; ++ b ; ++ c ; ++ d ;
        calc(rec, t, 1) ;

        calc(rec, t - a*v[1], -1) ;
        calc(rec, t - b*v[2], -1) ;
        calc(rec, t - c*v[3], -1) ;
        calc(rec, t - d*v[4], -1) ;

        calc(rec, t - a*v[1] - b*v[2], 1) ;
        calc(rec, t - a*v[1] - c*v[3], 1) ;
        calc(rec, t - a*v[1] - d*v[4], 1) ;
        calc(rec, t - b*v[2] - c*v[3], 1) ;
        calc(rec, t - b*v[2] - d*v[4], 1) ;
        calc(rec, t - c*v[3] - d*v[4], 1) ;

        calc(rec, t - a*v[1] - b*v[2] - c*v[3], -1) ;
        calc(rec, t - a*v[1] - b*v[2] - d*v[4], -1) ;
        calc(rec, t - a*v[1] - c*v[3] - d*v[4], -1) ;
        calc(rec, t - b*v[2] - c*v[3] - d*v[4], -1) ;

        calc(rec, t - a*v[1] - b*v[2] - c*v[3] - d*v[4], 1) ;

        printf ( "%lld\n", rec ) ;
    }
    return 0 ;
}

[USACO06DEC]Milk Patterns

Description

农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个0到1000000之间的数。并且John记录了N(1<=N<=20000)天的牛奶质量值。他想知道最长的出现了至少K(2<=K<=N)次的模式的长度。

Solution

后缀数组的套路题,二分答案,计数。

#include <bits/stdc++.h>
using namespace std ;
void Read ( int &x, char c = getchar() ) {
    for ( x = 0 ; !isdigit(c) ; c = getchar() ) ;
    for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
}
const int maxn = 2e4+5 ;
int n, m, e, s[maxn], b[maxn], sa[maxn], height[maxn], s1[maxn], s2[maxn], rnk[maxn], c[maxn] ;
void init() {
    int i, p, len ;
    int *x = s1, *y = s2 ;
    memset ( c, 0, sizeof c ) ;
    for ( i = 1 ; i <= n ; i ++ ) c[x[i] = s[i]] ++ ;
    for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
    for ( i = n ; i ; i -- ) sa[c[x[i]]--] = i ;

    for ( len = 1 ; len < n ; len <<= 1, m = p ) {
        p = 0 ;
        for ( i = n-len+1 ; i <= n ; i ++ ) y[++p] = i ;
        for ( i = 1 ; i <= n ; i ++ )
            if (sa[i] > len) y[++p] = sa[i]-len ;

        memset ( c, 0, sizeof c ) ;
        for ( i = 1 ; i <= n ; i ++ ) c[x[y[i]]] ++ ;
        for ( i = 1 ; i <= m ; i ++ ) c[i] += c[i - 1] ;
        for ( i = n ; i ; i -- ) sa[c[x[y[i]]]--] = y[i] ;

        swap(x, y) ;
        x[sa[1]] = p = 1 ;
        for ( i = 2 ; i <= n ; i ++ )
            if (y[sa[i]] == y[sa[i-1]] && y[sa[i]+len] == y[sa[i-1]+len])
                x[sa[i]] = p ;
            else x[sa[i]] = ++p ;
        if (p >= n) break ;
    }

    for ( i = 1 ; i <= n ; i ++ ) rnk[sa[i]] = i ;
    len = 0 ;
    for ( i = 1 ; i <= n ; i ++ ) {
        if (len) len-- ;
        p = sa[rnk[i]-1] ;
        while (s[i+len] == s[p+len]) ++ len ;
        height[rnk[i]] = len ;
    }
}

bool judge ( int v ) {
    int i, ans = 0, rec ;
    for ( i = 1 ; i <= n ; i ++ )
        if (height[i] < v) rec = 1 ;
        else ans = max(ans, ++rec) ;
    return ans >= e ;
}
int main() {
    int i, l, r, mid, rec ;
    Read(n) ; Read(e) ;
    for ( i = 1 ; i <= n ; i ++ ) Read(s[i]), b[i] = s[i] ;
    sort(b+1, b+n+1) ;
    m = unique(b+1, b+n+1)-b-1 ;
    for ( i = 1 ; i <= n ; i ++ )
        s[i] = lower_bound(b+1, b+m+1, s[i])-b ;
    init() ;
    l = 1, r = n ;
    while (l <= r) {
        mid = (l+r)>>1 ;
        if (judge(mid)) rec = mid, l = mid+1 ;
        else r = mid-1 ;
    }
    printf ( "%d\n", rec ) ;
    return 0 ;
}

[HAOI2012]高速公路

Description

Y901高速公路是一条由 N1 段路以及 N 个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为1~ N ,从收费站i行驶到 i+1 或从 i+1 行驶到 i 需要收取Vi的费用。高速路刚建成时所有的路段都是免费的。

政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或降价。

无聊的小A同学总喜欢研究一些稀奇古怪的问题,他开车在这条高速路上行驶时想到了这样一个问题:对于给定的 l,r(l<r) ,在第 l 个到第r个收费站里等概率随机取出两个不同的收费站 a b,那么从 a 行驶到b将期望花费多少费用呢?

所有C操作中的 v 的绝对值不超过10000
在任何时刻任意道路的费用均为不超过 10000 的非负整数
N,M105

Solution

显然我们只要考虑道路。
设道路 i 连接点i和点 i+1
对于道路 i ,被选到的概率应该是l i 左边,r i+1 右边的概率。
推下式子。

E=i=lr12Vi×(il+1)×(ri)(rl+1)2=i=lr12Vi[i2+(l+r1)ilr+r](rl+1)2

所以,维护三个值 Vi Vi×i Vi×i2
不用像网上大多数题解说的维护 i2 i ,修改的时候利用公式做前缀差分就行了。
i=1ni=n×(n+1)2i=1ni2=n×(n+1)×(2n+1)6

Code

#include <bits/stdc++.h>
#define LL long long
using namespace std ;
void Read ( LL &x, char c = getchar(), bool f = 0 ) {
    for ( x = 0 ; !isdigit(c) ; c = getchar() ) if (c == '-') f = 1 ;
    for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ;
    if (f) x = -x ;
}
LL gcd ( LL a, LL b ) { return b?gcd(b,a%b):a; }
const int maxn = 1e5+5 ;
LL sum1 ( int x ) { return (LL)x*(x+1)/2 ; }
LL sum2 ( int x ) { return (LL)x*(x+1)*(2*x+1)/6 ; }
LL n, m, tree[maxn*3][3], tag[maxn*3] ;
void push_up ( int h ) {
    tree[h][0] = tree[h<<1][0]+tree[h<<1|1][0] ;
    tree[h][1] = tree[h<<1][1]+tree[h<<1|1][1] ;
    tree[h][2] = tree[h<<1][2]+tree[h<<1|1][2] ;
}
void push_down ( int h, int l, int r ) {
    int mid = (l+r)>>1 ;
    if (tag[h]) {
        tag[h<<1] += tag[h] ;
        tag[h<<1|1] += tag[h] ;

        tree[h<<1][0] += tag[h]*(mid-l+1) ;
        tree[h<<1|1][0] += tag[h]*(r-mid) ;

        tree[h<<1][1] += tag[h]*(sum1(mid)-sum1(l-1)) ;
        tree[h<<1|1][1] += tag[h]*(sum1(r)-sum1(mid)) ;

        tree[h<<1][2] += tag[h]*(sum2(mid)-sum2(l-1)) ;
        tree[h<<1|1][2] += tag[h]*(sum2(r)-sum2(mid)) ;

        tag[h] = 0 ;
    }
}
void update ( int h, int l, int r, int x, int y, LL v ) {
    if (x <= l && r <= y) {
        tag[h] += v ;
        tree[h][0] += (r-l+1)*v ;
        tree[h][1] += v*(sum1(r)-sum1(l-1)) ;
        tree[h][2] += v*(sum2(r)-sum2(l-1)) ;
        return ;
    }
    push_down(h, l, r) ;
    int mid = (l+r)>>1 ;
    if (y <= mid) update(h<<1, l, mid, x, y, v) ;
    else if (x > mid) update(h<<1|1, mid+1, r, x, y, v) ;
    else {
        update(h<<1, l, mid, x, mid, v) ;
        update(h<<1|1, mid+1, r, mid+1, y, v) ;
    }
    push_up(h) ;
}
LL query ( int h, int l, int r, int x, int y, int v ) {
    if (x <= l && r <= y) return tree[h][v] ;
    push_down(h, l, r) ;
    int mid = (l+r)>>1 ;
    if (y <= mid) return query(h<<1, l, mid, x, y, v) ;
    else if (x > mid) return query(h<<1|1, mid+1, r, x, y, v) ;
    return query(h<<1, l, mid, x, mid, v)+query(h<<1|1, mid+1, r, mid+1, y, v) ;
}
char cmd[5] ;
int main() {
    LL l, r, v, rec0, rec1, rec2, rec, d ;
    Read(n) ; Read(m) ;
    while(m--) {
        scanf ( "%s", cmd ) ;
        Read(l) ; Read(r) ;
        if (cmd[0] == 'C') {
            Read(v) ;
            update(1, 1, n, l, r-1, v) ;
        } else {
            rec0 = query(1, 1, n, l, r-1, 0) ;
            rec1 = query(1, 1, n, l, r-1, 1) ;
            rec2 = query(1, 1, n, l, r-1, 2) ;

            rec = -rec2 + rec1*(r+l-1) + rec0*(r-l*r) ;
            v = (r-l+1)*(r-l) ;
            v >>= 1 ;
            d = gcd(rec, v) ;
            rec /= d ;
            v /= d ;
            printf ( "%lld/%lld\n", rec, v ) ;
        }
    }
    return 0 ;
}

[SDOI2011]计算器

练了下快速幂和扩展欧几里得,学了下BSGS求离散对数。没什么好讲的。

Code

#include <bits/stdc++.h>
#define LL long long
using namespace std ;
LL Qpow ( LL a, LL b, LL modd ) {
    LL rec = 1 ;
    for ( a %= modd; b ; b >>= 1, (a *= a) %= modd )
        if (b&1) (rec *= a) %= modd ;
    return rec ;
}
LL exgcd ( LL a, LL b, LL &x, LL &y ) {
    if (b == 0) {
        x = 1, y = 0 ;
        return a ;
    }
    LL d = exgcd(b, a%b, y, x) ;
    y -= a/b*x ;
    return d ;
}
void solve1() {
    LL a, b, c ;
    scanf ( "%lld%lld%lld", &a, &b, &c ) ;
    printf ( "%lld\n", Qpow(a, b, c) ) ;
}
void solve2() {
    LL a, b, c, d, x, y ;
    scanf ( "%lld%lld%lld", &a, &c, &b ) ;
    d = exgcd(a, b, x, y) ;
    if (c%d != 0) {
        puts("Orz, I cannot find x!") ;
        return ;
    }
    x = (x+b)%b ;
    c /= d ;
    x *= c ;
    printf ( "%lld\n", x%b ) ;
}
map <LL, LL> g ;
void solve3() {
    g.clear() ;
    LL a, b, c, rec, i, m ;
    scanf ( "%lld%lld%lld", &a, &b, &c ) ;
    if (a%c == 0) goto end ;
    m = ceil(sqrt(c)) ;
    rec = b%c ;
    for ( i = 0 ; i <= m ; i ++, (rec *= a) %= c )
        if (!g[rec]) g[rec] = i ;
    for ( i = 1 ; i <= m ; i ++ ) {
        rec = Qpow(a, i*m, c) ;
        if (g.count(rec)) {
            rec = i*m - g[rec] ;
            printf ( "%lld\n", (rec%c+c)%c ) ;
            return ;
        }
    }
    end:puts("Orz, I cannot find x!") ;
}
int main() {
    int _, type ;
    scanf ( "%d%d", &_, &type ) ;
    while (_--) {
        if (type == 1) solve1() ;
        else if (type == 2) solve2() ;
        else solve3() ;
    }
    return 0 ;
}

[SDOI2013]随机数生成器

Description

题面占地方。

Solution

v 次之后到达。发现展开之后是个多项式。

xav1+bi=0v2ai=t

后一项用等比数列化简,第一项再通分后合并次数为 v1 的项。求逆元后转化为离散对数。

Code

#include <bits/stdc++.h>
#define LL long long
using namespace std ;
LL modd ;
LL Qpow ( LL a, LL b ) {
    LL rec = 1 ;
    for ( a %= modd ; b ; b >>= 1, (a *= a) %= modd )
        if (b&1) (rec *= a) %= modd ;
    return rec ;
}
map <LL, LL> Map ;
LL BSGS ( LL a, LL b ) {
    if (b == 1) return 1 ;
    LL i, t, m = ceil(sqrt(modd)), rec = b ;
    Map.clear() ;
    for ( i = 1 ; i <= m ; i ++ ) {
        (rec *= a) %= modd ;
        if (!Map.count(rec)) Map[rec] = i ;
    }
    t = Qpow(a, m) ;
    rec = 1 ;
    for ( i = 1 ; i <= m ; i ++ ) {
        (rec *= t) %= modd ;
        if (Map.count(rec)) return i*m - Map[rec] + 1 ;
    }
    return -1 ;
}
int main() {
    LL _, a, b, x, t ;
    cin >> _ ;
    while (_--) {
        cin >> modd >> a >> b >> x >> t ;
        if (x == t) {
            puts("1") ;
            continue ;
        }
        if (a == 0) {
            if (b == t) puts("2") ;
            else puts("-1") ;
            continue ;
        }
        if (a == 1 && b == 0) {
            puts("-1") ;
            continue ;
        }
        if (a == 1) {
            cout << ((t-x+modd)%modd)*Qpow(b, modd-2)%modd+1 << endl ;
            continue ;
        }
        t = (a*t%modd - t + modd + b)%modd ;
        (t *= Qpow(a*x%modd - x + b + modd, modd-2)) %= modd ;
        cout << BSGS(a, t) << endl ;
    }
    return 0 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值