码迷,mamicode.com
首页 > 其他好文 > 详细

CF1197div2

时间:2019-10-11 20:11:30      阅读:124      评论:0      收藏:0      [点我收藏+]

标签:nod   博弈   最大   dev   line   namespace   草稿   mod   编译   

\(A,B,C\)都没印象了...所以就只放我写过题解的D,E了...

如果有时间会去补一下F之类的,不过我不会博弈论啊QAQ
`
CF1197D 【Yet Another Subarray Problem】

早上打算做一做的时候挂在自己的草稿纸的潦草字迹上了,挂了1个小时(虽然编译器也有一点锅,果然学校的Dev还是有点小撮,调一年)

我们仔细康康原来的式子:

\[\sum_{i=l}^ra[i] - k*\left\lceil\dfrac{r - l+1}{m}\right\rceil\]

尝试用一下前缀和,记\(x = l -1\)

则原式即:

\[sum[r] -sum[x]-k*\left\lceil\dfrac{r-x}{m}\right\rceil\]

考虑对\(r,x\)分别讨论,将其改写成这种形式:

\[r = \left\lfloor\frac{r}{m}\right\rfloor*m + r\%m\]

\[x = \left\lfloor\frac{x}{m}\right\rfloor*m + x\%m\]

则原式即:

\[sum[r]-sum[x]-k*\left\lceil\dfrac{\left\lfloor\frac{r}{m}\right\rfloor*m + r\%m-(\left\lfloor\frac{x}{m}\right\rfloor*m + x\%m)}{m}\right\rceil\]

也就是:

\[sum[r]-sum[x]-k*\left\lceil\ \left\lfloor\frac{r}{m}\right\rfloor - \left\lfloor\frac{x}{m}\right\rfloor+ \frac{( r \% m - x \% m )}{m}\right\rceil\]

我们发现前两个貌似都是整数,他们不会影响上取整的结果,所以我们可以将他们提出来,得到:

\[sum[r]-k*\left\lfloor\frac{r}{m}\right\rfloor-(sum[x]-k*\left\lfloor\frac{x}{m}\right\rfloor)-k*\left\lceil\dfrac{r\% m-x\%m}{m}\right\rceil\]

如果我们记\(D(x) = sum[x] - k*\left\lfloor\dfrac{x}{m}\right\rfloor\)

那么原式即:

\[D(r) -D(x)-k*\left\lceil\dfrac{r\% m-x\%m}{m}\right\rceil\]

我们发现后面那一坨貌似和其他值均无关系,它只和\(r\%m\)\(x\%m\)有关

至此,我们已经将原式化简的差不多了。


(休息休息)


我们考虑求解对于每个\(i\)以其为结尾的最大权值。

不难发现,对于此\(i\),我们需要求解的式子中\(D(i)\)是固定的,\(i\%m\)也是固定的,所以我们可以考虑枚举一个\(x\%m\),并统计对于\(\%m\)相同的\(x\)中维护一个最小\(D(x)\),就可以保证权值最小了,复杂度\(O(nm)\)

详见代码:

#include<bits/stdc++.h>
using namespace std ;
#define int long long
int read() {
    char cc = getchar() ; int cn = 0, flus = 1 ;
    while( cc > '9' || cc < '0' ) { if( cc == '-' ) flus = -flus; cc = getchar() ; }
    while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ; 
    return cn * flus ;
}
const int inf = 1234567890000 ;
const int N = 3e5 + 5 ; 
const int M = 15 ; 
int a[N], D[N], n, m, k, Mi[M], Ans ; 
signed main() {
    n = read(), m = read(), k = read() ;
    for( int i = 1; i <= n; ++ i ) 
    a[i] = read(), a[i] = a[i - 1] + a[i], D[i] = a[i] - k * ( i / m ) ; 
    for( int i = 0; i < m; ++ i ) Mi[i] = inf ;
    Mi[0] = 0 ; int f = 0;
    for( int i = 1; i <= n; ++ i ) {
        f = - inf ; 
        for( int j = 0; j < m; ++ j ) {
            int fl = ceil( 1.0 * ( ( i % m ) - j ) / m ) ; 
            f = max( f, D[i] - Mi[j] - k * fl ) ; 
        }
        Mi[i % m] = min( Mi[i % m], D[i] ) ;
        Ans = max( Ans, f ) ;
    }
    printf("%lld\n", Ans ) ;
    return 0 ;
}

然而实际上这个复杂度可以更优,不过貌似出题人没有给非常大的\(m\)而已\(.......\)

这里讲一下本蒟蒻\(yy\)出来的做法吧(虽然听说有\(O(n)\)做法)

我们注意到\(x\%m<m\),所以\(1 \ge\left\lceil\dfrac{r\% m-x\%m}{m}\right\rceil \ge 0\)

如果我们设\(i\%m = t~(m> t\ge 0)\)

那么使得\(\left\lceil\dfrac{t-y}{m}\right\rceil = 0\)\(y\)是可以直接求出来的。

\(y\ge t\)

对于其余情况,\(\left\lceil\dfrac{t-y}{m}\right\rceil = 1\)

接下来有一个更加优秀的性质:

注意到我们求解每个以\(i\)为结尾的最优答案的时候,其\(i\)连续,换而言之\(i\%m\)的值连续(\(0,1,2...m-1\)

到达\(m-1\)后变成\(0\)

我们考虑按照\(\left\lceil\dfrac{i\%m-x\%m}{m}\right\rceil\)是否等于\(1\)将可转移的\(D(x)\)分成两段。

对于两部分各开一个线段树,维护一下最小值即可。

左边的线段树表示\(\left\lceil\dfrac{i\%m-x\%m}{m}\right\rceil=0\)\(D(x)\),右边则是\(\left\lceil\dfrac{i\%m-x\%m}{m}\right\rceil=1\)\(D(x)\)

那么两边分别取出最小值然后判断一下转移给\(i\)即可。

然后\(i\)右移一位,我们发现它在\(\%m\)的意义下增大了\(1\)

那么它对应的就是在\(\%m\)意义下使得那个式子\(=1\)\(D(x)\)多了一个。

所以我们将使得那个式子\(=1\)的部分中将\(i\%m\)删掉即可,并加入右边。

到达\(m-1\)的时候特判即可。

即每移动一位需要删除/加入一个点,然后还要做一遍查询,所以复杂度\(O(n \log m)\)

至于到了\(m-1\)后,再\(+1\)则发现\(0/1\)的线段树恰好交换

复杂度\(O(n\log m)\)

CF1197E 【Culture Code】

可能还比较套路???

\(First:\)

我们先按照\(out\)值排序

然后考虑记\(dp[i]\)\(dp\)到第\(i\)位的最小值,\(g[i]\)表示得到第\(i\)位的最小值的方案数

那么就有:\(dp[i] = \min_{j=1}^{i-1}(dp[j] + in[i] -out[j] ~(out[j]\le in[i]))\)

方案数可以顺便统计(两个dp值相等就合并)

这样做是\(O(n^2)\)


考虑优化:

我们注意到转移的条件是\(1\le out[x] \le in[i]\)

即要从\([1,~in[i]]\)这个区间内转移过来

其次是转移方程为\(dp[j]-out[j]+in[i]\)

最后哪一项只和\(i\)有关,而前面又之和\(j\)有关。

所以我们不难想到对\(out\)值建一棵线段树,我们每求出一个\(dp\)都丢到对应的\(out\)这个下标去。(把$dp[x] - out[x] $作为一个整体丢进去)

然后每次做\(dp\)就从\([1,~in[i]]\)中取出最小值转移就可以了。

方案数其实就是这个最小值出现的次数

我们用线段树实现就可以了,每次合并两个儿子区间内信息即可:


左儿子小取左儿子,右儿子小取右儿子,否则相等,方案等于两边的和。

于是就只需要兹娃单点改和区间问了。。。

详见代码:

注意要离散化。。。以及取膜

#include<bits/stdc++.h>
using namespace std;
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i )
#define re register
#define ls(x) x * 2
#define rs(x) x * 2 + 1
#define inf 12345678900
#define int long long
int read() {
    char cc = getchar(); int cn = 0, flus = 1;
    while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
    while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
    return cn * flus;
}
const int mod = 1e9 + 7 ;
const int N = 2e5 + 5 ; 
int n, dp[N], g[N], Mi, Mx, cnt ; 
struct node {
    int in, out, in2, out2 ; 
} a[N];
struct Node {
    int val, sum ; 
};
struct S {
    int val, id, ip ;
} s[N * 2] ;
struct Tree {
    int mi, sum ; 
} tr[N * 5];
bool cmp( node x, node y ) {
    return x.out < y.out ;
}
bool cmp2( S x, S y ) {
    return x.val < y.val ; 
}
void build( int x, int l, int r ) {
    tr[x].mi = inf ; 
    if( l == r ) return ;
    int mid = ( l + r ) >> 1 ;
    build( ls(x), l, mid ), build( rs(x), mid + 1, r ) ;
}
Node up( Node lx, Node rx ) {
    if( lx.val < rx.val ) return lx ;
    if( rx.val < lx.val ) return rx ;
    return (Node){ lx.val, ( lx.sum + rx.sum ) % mod } ;
}
Node query( int x, int l, int r, int ql, int qr ) {
    if( ql <= l && r <= qr ) return (Node){ tr[x].mi, tr[x].sum } ;
    int mid = ( l + r ) >> 1 ;
    if( mid >= qr ) return query( ls(x), l, mid, ql, qr ) ;
    if( mid < ql ) return query( rs(x), mid + 1, r, ql, qr ) ;
    return up( query( ls(x), l, mid, ql, qr ), query( rs(x), mid + 1, r, ql, qr ) ) ;
}
void update( int x, int l, int r, int wh, int val, int sum ) {
    if( l == r ) {
        if( tr[x].mi == val ) tr[x].sum += sum, tr[x].sum %= mod ; 
        if( tr[x].mi > val ) tr[x].mi = val, tr[x].sum = sum ;
        return ;
    }
    int mid = ( l + r ) >> 1 ; 
    if( mid >= wh ) update( ls(x), l, mid, wh, val, sum ) ;
    else update( rs(x), mid + 1, r, wh, val, sum ) ;
    tr[x] = tr[ls(x)] ;
    if( tr[x].mi == tr[rs(x)].mi ) tr[x].sum += tr[rs(x)].sum ; 
    if( tr[rs(x)].mi < tr[x].mi ) tr[x] = tr[rs(x)] ;
    tr[x].sum %= mod ;
}
signed main()
{
    n = read() ; 
    rep( i, 1, n ) a[i].out = read(), a[i].in = read(), Mx = max( Mx, a[i].in ), 
    s[++ cnt].id = i, s[cnt].ip = -1, s[cnt].val = a[i].in, 
    s[++ cnt].id = i, s[cnt].ip = 1,  s[cnt].val = a[i].out ; 
    sort( s + 1, s + cnt + 1, cmp2 ) ; int num = 0 ;
    rep( i, 1, cnt ) {
        if( s[i].val != s[i - 1].val ) ++ num ;
        if( s[i].ip == 1 ) a[s[i].id].out2 = num ;
        else a[s[i].id].in2 = num ; 
    }
    sort( a + 1, a + n + 1, cmp ) ; 
    Mi = inf ; build( 1, 1, num ) ;
    rep( i, 1, n ) {
        dp[i] = a[i].in, g[i] = 1 ; 
        Node x = query( 1, 1, num, 1, a[i].in2 ) ;
        if( dp[i] == x.val + a[i].in ) g[i] += x.sum, g[i] %= mod ;
        if( dp[i] > x.val + a[i].in ) dp[i] = x.val + a[i].in, g[i] = x.sum ;
        if( a[i].out > Mx ) Mi = min( Mi, dp[i] ) ; 
        update( 1, 1, num, a[i].out2, dp[i] - a[i].out, g[i] ) ;
    }
    int Ans = 0 ;
    rep( i, 1, n ) {
        if( a[i].out > Mx && Mi == dp[i] ) Ans += g[i], Ans %= mod ; 
    }
    printf("%lld\n", Ans % mod ) ;
    return 0;
}

CF1197div2

标签:nod   博弈   最大   dev   line   namespace   草稿   mod   编译   

原文地址:https://www.cnblogs.com/Soulist/p/11656247.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!