【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\n" );
        else printf ( "1\n" );
    }
    return 0;
}

原文地址:https://www.cnblogs.com/wans-caesar-02111007/p/9567408.html

时间: 2024-12-10 13:45:41

【8.31校内测试】【找规律二分】【DP】【背包+spfa】的相关文章

找规律/数位DP HDOJ 4722 Good Numbers

题目传送门 1 /* 2 找规律/数位DP:我做的时候差一点做出来了,只是不知道最后的 is_one () 3 http://www.cnblogs.com/crazyapple/p/3315436.html 4 数位DP:http://blog.csdn.net/cyendra/article/details/11606209 5 */ 6 #include <cstdio> 7 #include <iostream> 8 #include <algorithm> 9

2017年icpc西安网络赛 Maximum Flow (找规律+数位dp)

题目 https://nanti.jisuanke.com/t/17118 题意 有n个点0,1,2...n-1,对于一个点对(i,j)满足i<j,那么连一条边,边权为i xor j,求0到n-1的最大流,结果取模,n<=1e18 分析 可以写个最大流对数据找规律,但没找出来…… 然后只能取分析了,首先最大流等价于最小割 明确一定,0->n-1这个要先割掉 然后我们贪心,希望有一些点割掉与0相连的边,一些点割掉与n-1相连的边 我们去观察每个点与0相连和与n-1相连的两条边权值,容易发现

POWEROJ 1168-A F(x)(找规律&amp;二分查找)

题目链接:1168-A 题意 Time Limit: 1000 MS Memory Limit: 65536 KB Description 小明有一个不降序列(f(1),f(2),f(3),--),f(k)代表在这个序列中大小是k的有f(k)个.我们规定f(n)的前12项如下图. n 1 2 3 4 5 6 7 8 9 10 11 12 f(n) 1 2 2 3 3 4 4 4 5 5 5 6 现在给你一个n,你知道f(n)是多少吗? Input 多组测试数据 每组一个n(1<=n<=2000

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

Solution 谁能想到这道题卡读入??还卡了70pts??? 二分+$n^2$check就行了 Code #include<bits/stdc++.h> using namespace std; int n, m; int sum[2005][2005]; void read(int &x) { x = 0; char ch = getchar(); while(ch > '9' || ch < '0') ch = getchar(); while(ch >= '

ZOJ 3768Continuous Login(找规律然后二分)

Continuous Login Time Limit: 2 Seconds      Memory Limit: 131072 KB      Special Judge Pierre is recently obsessed with an online game. To encourage users to log in, this game will give users a continuous login reward. The mechanism of continuous log

HDU - 4722 Good Numbers 【找规律 or 数位dp模板】

If we sum up every digit of a number and the result can be exactly divided by 10, we say this number is a good number. You are required to count the number of good numbers in the range from A to B, inclusive. InputThe first line has a number T (T <=

【55测试】【字符串】【栈】【找规律】

集训第二天,额,考崩了. 第一题 hao 大意:(这个名字就不要在意了,其实是祖玛游戏) 模拟祖玛游戏的模式,给一个'A'~'Z'的字符串,然后有t个插入操作为 “ 添加后的在原字符串的位置 x  插入元素 c ”,字符串中有超过或等于 3 个相同的字符,则被消除,输出每次操作后剩余的字符串,如果为空,则输出“-”. 样例: ACCBA                     输出:  ABCCBA 5   AABCCBA 1 B AABBCCBA 0 A - 2 B A 4 C 0 A 解:

hdu 2147 kiki&#39;s game(DP(SG)打表找规律)

题意: n*m的棋盘,一枚硬币右上角,每人每次可将硬币移向三个方向之一(一格单位):左边,下边,左下边. 无法移动硬币的人负. 给出n和m,问,先手胜还是后手胜. 数据范围: n, m (0<n,m<=2000) 思路: dp[i][j]=0,说明从(i,j)这个点到时左下角先手败.dp[i][j]=1则先手胜. 然后记忆搜.但是记忆搜会超时. 搜完把整张表打出来,发现规律了,,,,然后,,,代码剩几行了. 代码: ///打表观察 /* int f[2005][2005]; int go(in

【推导】【找规律】【二分】hdu6154 CaoHaha&#39;s staff

题意:网格图.给你一个格点多边形的面积,问你最少用多少条边(可以是单位线段或单位对角线),围出这么大的图形. 如果我们得到了用n条边围出的图形的最大面积f(n),那么二分一下就是答案. n为偶数时,显然要尽量用斜边去拼矩形,于是f(i)=i*i/4-1 (i mod 4 == 2),f(i)=i*i/4-1(i mod 4 == 0). 当n为奇数时,尽量用i-1情况下的最长边向外扩张一个单位,于是f(i)=f(i-1)+[(i+1)/4]*2-1(i mod 2 == 1),方括号表示下取整.