CF1567C——两种解法:隔离的思想状压dp;并拓展到一般情况

Posted hans774882968

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CF1567C——两种解法:隔离的思想状压dp;并拓展到一般情况相关的知识,希望对你有一定的参考价值。

题意:定义了另一种加法,把加法进位的位置从i+1变为i+2。求满足x+y=n(x,y)个数,x和y是正整数,1000组数据,n<=1e9

一道让人思维开阔的好题。

法一:隔离的思想。既然进位位置是i+2,那么i、i+2、i+4……和i+1、i+3、……是互不干扰的。于是我们惊奇地发现,按位置下标i模2的剩余类内部,进行的是正常的加法。因此我们按位置下标的奇偶,分出两个数,按乘法原理组合出方案即可。比如n=114514,分出141和154,其中一个方案是071+070=141,004+150=154,则007014+017500=114514。高位补0是为了一致性,奇数下标的数字用粗体标出。

但这样算出的方案是含有(n,0)和(0,n)这两种非法方案的,因此还要减2。

拓展:若进位位置改成i+x,则按位置下标模x的剩余类来分出x个整数,进行乘法原理即可~

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 3e5 + 5;

int n;

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

int main(int argc, char** argv) {
    int T;read(T);
    while(T--){
        read(n);
        vector<int> v1,v2;
        for(int i = 0;n;++i,n /= 10){
            if(i&1) v2.push_back(n%10);
            else v1.push_back(n%10);
        }
        reverse(v1.begin(),v1.end());
        reverse(v2.begin(),v2.end());
        int n1 = 0,n2 = 0;
        for(int v: v1) n1 = 10*n1+v;
        for(int v: v2) n2 = 10*n2+v;
        printf("%d\\n",(n1+1)*(n2+1)-2);
    }
    return 0;
}

法二:状压dp。官方题解讲得很晦涩,但它的思想很简单。还是拿n=114514举例。首先,我们假设已经求出了11451的答案,然后看能不能转移,因此状态需要记录i,表示当前数字是从位置i到最高位(下标为len-1)的。但这不够,因为114514可能向11451的十位进位,所以状态需要记录数字的十位(下标为1)是否有来自低位的进位。因为114514的十位就是11451的个位,即11451的个位的进位情况,必须和114514的十位的进位情况保持同步,所以状态还需要记录数字的个位(下标为0)是否有进位。

总结一下,我们引入了dp[i][j],而进位情况同步的要求引入了状态kjk分别表示当前数字的十位和个位是否有来自低位的进位。

为了方便,我们先允许非负整数,再减去(n,0)和(0,n)这两种非法方案。所以答案是dp[0][0][0]-2

边界:dp[len-1][0][k]=d[dl-1]+k,既然只有1位,那么十位必须是不进位才有方案。

转移:分11451的十位有和没有进位来讨论。(d[i]+1-k)*dp[i+1][0][j] + (9+k-d[i])*dp[i+1][1][j]

拓展:若进位位置改成i+x,则就是一个纯正的状压dp。个位以外的所有进位情况都是保持同步。具体参照”拓展“的代码(对拍已通过,可以放心食用~)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 3e5 + 5;

int n,dp[13][2][2];

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

int main(int argc, char** argv) {
    int T;read(T);
    while(T--){
        read(n);
        memset(dp,0,sizeof dp);
        vector<int> d;
        for(;n;n /= 10) d.push_back(n%10);
        int dl = d.size();
        dp[dl-1][0][0] = d[dl-1]+1;
        dp[dl-1][0][1] = d[dl-1];
        dwn(i,dl-2,0){
            rep(j,0,1){
                rep(k,0,1){
                    dp[i][j][k] = (d[i]+1-k)*dp[i+1][0][j] + (9+k-d[i])*dp[i+1][1][j];
                }
            }
        }
        // rep(i,0,dl-1) rep(j,0,1) rep(k,0,1) cout<<dp[i][j][k]<<" \\n"[j&&k];//
        printf("%d\\n",dp[0][0][0]-2);
    }
    return 0;
}
拓展
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 3e5 + 5;

int n,dp[13][32];

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

int solve1(int n,int pos){
    vector<int> v[pos];
    for(int i = 0;n;++i,n /= 10){
        v[i%pos].push_back(n%10);
    }
    re_(i,0,pos) reverse(v[i].begin(),v[i].end());
    int ans = 1;
    re_(i,0,pos){
        int x = 0;
        for(int va: v[i]) x = 10*x+va;
        ans *= x+1;
    }
    return ans-2;
}

int solve2(int n,int pos){
    memset(dp,0,sizeof dp);
    vector<int> d;
    for(;n;n /= 10) d.push_back(n%10);
    int dl = d.size();
    dp[dl-1][0] = d[dl-1]+1;
    dp[dl-1][1] = d[dl-1];
    dwn(i,dl-2,0){
        re_(j,0,1<<pos){
            int k = j&1;
            dp[i][j] = (d[i]+1-k)*dp[i+1][j>>1] + (9+k-d[i])*dp[i+1][(1<<(pos-1))|(j>>1)];
        }
    }
    // rep(i,0,dl-1) re_(j,0,1<<pos) cout<<dp[i][j]<<" \\n"[j+1==(1<<pos)];//
    return dp[0][0]-2;
}

int main(int argc, char** argv) {
    int T;read(T);
    while(T--){
        read(n);
        rep(i,2,5){
            int a1 = solve1(n,i),a2 = solve2(n,i);
            printf("%d %d\\n",a1,a2);
            assert(a1 == a2);
        }
    }
    return 0;
}

以上是关于CF1567C——两种解法:隔离的思想状压dp;并拓展到一般情况的主要内容,如果未能解决你的问题,请参考以下文章

CodeForces - 1567C Carrying Conundrum(思维/状压)

CodeForces - 1567C Carrying Conundrum(思维/状压)

CF 40E[Number Table]

CF 1042B. Vitamins(状压dp)

CF16E Fish(状压+期望dp)

CF11D A Simple Task(状压DP)