8.31校内测试找规律二分DP背包+spfa

Posted wans-caesar-02111007

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了8.31校内测试找规律二分DP背包+spfa相关的知识,希望对你有一定的参考价值。

技术分享图片

打表出奇迹!表打出来发现了神奇的规律:

1 1 2 2 3 4 4 4 5 6 6 7 8 8 8 8 9 10 10 11 12 12 12 13 14 14 15 16 16 16 16 16...

嗯嗯嗯?没有规律?我们把每个数出现的次数列出来:

2 2 1 3 1 2 1 4 1 2 1 3 1 2 1 5

然后惊觉表中的数字是连续的,每个都至少有一个,而分解因数中有只要2的幂,有一个就会加一,比如8是2、4、8的倍数,在1的基础上会多三个。(1和2除外)

所以对于一个位置$x$,我们可以二分出它的值前面那个值,check的时候计算二分的值总共占了多少位置即可。而求和时又发现,基数是一段连续的等差数列,之后每次都是累加2的幂的等差数列。把前面的值计算完后,把$x$代表的值的个数加起来即可。这样就可以用前缀和相减得出答案了。

注意取模的位置,好事多模!

#include<iostream>
#include<cstdio>
#define LL long long
#define mod 1000000007
using namespace std;

LL vi = 500000004;

LL count ( LL mid ) {
    LL k = 1, num = 0;
    while ( k <= mid ) {
        num += mid / k;
        if ( num > 1e18 ) break;
        k *= 2;
    }
    return num + 1;//2个1 
}

LL work ( LL x ) {
    if ( x == 0 ) return 0;
    if ( x == 1 ) return 1;
    if ( x == 2 ) return 2;
    LL l = 1, r = x, pos, sum;
    while ( l <= r ) {
        LL mid = ( l + r ) >> 1;
        LL tmp = count ( mid );
        if ( tmp < x ) l = mid + 1, pos = mid, sum = tmp;
        else r = mid - 1;
    }
    LL ans = 1, k = 1;
    while ( k <= pos ) {
        ans = ( ans + ( k + pos / k * k % mod ) % mod * ( pos / k % mod ) % mod * vi % mod ) % mod;
        k *= 2;
    }
    LL res = ( ( ( x - sum ) % mod ) + mod ) % mod;
    ans = ( ans + res * ( pos + 1 ) % mod ) % mod;
    return ans;
}

int main ( ) {
    freopen ( "me.in", "r", stdin );
    freopen ( "me.out", "w", stdout );
    LL L, R;
    cin >> L >> R;
    cout << ( ( work ( R ) - work ( L - 1 ) ) % mod + mod ) % mod << endl;
    return 0;
}

技术分享图片

这个模数真是...看到觉得是$FFT$凉了啊...

结果就是个二维DP!而且题目上$x$给的虽然是10000,但是$n$是80,实际的$x$想上300都难!

定义$dp[i][k]$表示当前从前往后填到了$i$,我们只当当前区间长度是$i$。$k$表示当前贡献。枚举填$i$的位置$j$由于$1-j-1$已经统计出方案数,而相对大小是不会因为新加的数改变的,前面填的方案数就是$C(i-1,j-1)$。所以枚举前后段的贡献,用乘法原理更新答案即可。

#include<iostream>
#include<cstdio>
#define LL long long
#define MOD 998244353
using namespace std;

LL dp[85][10005], M[85], C[85][85];

int n, x;

void Work ( ) {
    for ( int i = 0; i <= n; i ++ )
        for ( int j = 0; j <= i; j ++ )
            if ( i == j || j == 0 || i == 0 ) C[i][j] = 1;
            else C[i][j] = ( C[i-1][j-1] + C[i-1][j] ) % MOD;
    dp[0][0] = dp[1][1] = 1;
    M[1] = 1;
    for ( int i = 2; i <= n; i ++ )//从小到大插入数 
        for ( int j = 1; j <= i; j ++ ) {//枚举插入位置 (有相对大小即可,不需要准确位置,当前区间长度为i 
            int r = min ( j, i - j + 1 ), tmp = C[i-1][j-1];//r是当前插入的数提供的贡献 
            M[i] = max ( M[i], M[j-1] + M[i-j] + r );//i个数放入达到的最大贡献 
            for ( int k1 = j-1; k1 <= M[j-1]; k1 ++ )
                for ( int k2 = i-j; k2 <= M[i-j]; k2 ++ )
                    dp[i][r+k1+k2] = ( dp[i][r+k1+k2] + dp[j-1][k1] * dp[i-j][k2] % MOD * tmp % MOD ) % MOD;
        }
    if ( n > x || x > M[n] ) printf ( "0" );  
    else printf ( "%I64d", dp[n][x] );
}

int main ( ) {
    freopen ( "good.in", "r", stdin );
    freopen ( "good.out", "w", stdout );
    scanf ( "%d%d", &n, &x );
    Work ( );
    return 0;
}

技术分享图片

题目改成了可不可以达到,输出1和-1。

很容易想到和背包有关。可是$x$的数据范围??

但是看到$A$的范围??可以想到恰好装满$x$实际上可以转换为用$A_2-A_n$装满$x%A_1$(装的过程也对$A_1$取模),所以空间一下子就压到了$1e5$叻!

可是还有一种情况:比如$A$是4、6、8,$x$是2,按照刚才的算法这样也是算可以,把6单独拿出来%4就是2。这意味着我们必须要判断背包过程中达到$x%A_1$可行的最小方案必须要小于等于$x$。所以可以跑$Spfa$边更新可行边记录最小方案。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define RG register
#define LL long long 
using namespace std;

int n, Q;
LL dis[100005];
int vis[100005], a[100005];
queue < LL > q;
void Spfa ( ) {
    memset ( dis, -1, sizeof ( dis ) );
    dis[0] = 0; q.push ( 0 ); vis[0] = 1;
    while ( !q.empty ( ) ) {
        LL x = q.front ( ); q.pop ( ); vis[x] = 0;
        for ( RG int i = 2; i <= n; i ++ ) {
            LL v = ( x + a[i] ) % a[1];
            if ( dis[v] < 0 || dis[v] > dis[x] + a[i] ) {
                dis[v] = dis[x] + a[i];
                if ( !vis[v] ) {
                    vis[v] = 1;
                    q.push ( v );
                }
            }
        }
    }
}

int main ( ) {
    freopen ( "hungry.in", "r", stdin );
    freopen ( "hungry.out", "w", stdout );
    scanf ( "%d%d", &n, &Q );
    for ( RG int i = 1; i <= n; i ++ )    scanf ( "%d", &a[i] );
    Spfa ( );
    while ( Q -- ) {
        LL x;
        scanf ( "%I64d", &x );
        if ( dis[x % a[1]] < 0 || dis[x % a[1]] > x ) printf ( "-1
" );
        else printf ( "1
" );
    }
    return 0;
}

 

以上是关于8.31校内测试找规律二分DP背包+spfa的主要内容,如果未能解决你的问题,请参考以下文章

繁繁的数字 背包DP

Codeforces 830A. Office Keys (背包dp+贪心) / (二分+贪心)

知识点汇总

POJ 2229 sumset ( 完全背包 || 规律递推DP )

比赛题解 更新ING

10.22校内测试二分二分图很像莫队的乱搞/树状数组