dp训练第5题 xjtuoj old58 10倍GG dp-位置dp

题意

给一个全为正整数的N*N矩阵,要从左上角走到右下角,每次只能向右或向下,问路径上所有数字乘积末尾的零最少有多少个.

思维过程

我一度以为这道题不具有最优子结构性质啊喂,还有后效性啊喂
思考了一个小时然后看了syl题解啊喂
一会建立一个题集合,就叫莫名其妙的卡想法.

首先,如果这道题是dp的话,显然是一个二维的位置dp,只要求i,j到n,n的信息即可.
显然末尾0的个数是由2因子和5因子的个数决定的,为min(n2,n5)

我卡的地方,也就是思维的关键难点在于:一个节点到底该保存什么信息?(状态表示)
保存末尾0最小值的话,随意构造一组数据就能卡掉.
因为前方的数据是未知的,换句话说,保存最小值是有后效性的.
如果对于每个n2的值,都保存一个最小的n5的值,是可以走通的,但是复杂度会超(n2最高100000,一共相当于n^3还多)
但是又不能轻易判定两个n2,n5数对的大小关系来决定存哪个,比如(1,7)和(2,6),需要哪个作为当前节点的信息完全取决于之前的节点,同样有后效性,不能覆盖.

解法

以上的思考过程中我忽略了一个性质:如果获得了某个节点的最小n2和最小n5值(记为m2和m5),注意不用在同一路径上
那么最小末尾0的个数就是min(m2,m5).
因为走m2的路径,n5一定大于m5.同理走m5的路径,n2一定大于m2.
所以状态信息的表示分成了dp2和dp5,即一个节点的m2和m5.求解答案时,取min即可.
先写代码,再总结.

状态表示:dp2[i][j],dp5[i][j]分别表示点i,j到点n,n的最少2因子个数和最少5因子个数.
边界条件:dp[n][n]=save[n][n]
状态转移:dp[i][j]=min(dp[i+1][j],dp[i][j+1])+save[i][j] 注意边界的情况.
if(j==n) dp[i][j]=dp[i+1][j]+save[i][j]
if(i==n) dp[i][j]=dp[i][j+1]+save[i][j]

/* LittleFall : Hello! */
#include 
using namespace std;
inline int read();
inline void write(int x);
const int M = 1024;
int save[M][M][2],dp[M][M][2]; //0为n2,1为n5

int main(void)
{
    #ifdef _LITTLEFALL_
    freopen("in.txt","r",stdin);
    #endif
    //std::cin.sync_with_stdio(false); 

    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(save,0,sizeof(save));
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            long long tmp;
            scanf("%lld",&tmp);
            while(!(tmp&1))
            {
                save[i][j][0]++;
                tmp>>=1;
            }
            while(tmp%5==0)
            {
                save[i][j][1]++;
                tmp/=5;
            }
        }
        memset(dp,1,sizeof(dp));
        dp[n][n][0]=dp[n][n][1]=0;
        for(int i=n;i;i--)
        for(int j=n;j;j--)
        {
            if(i0]=min(dp[i][j][0],dp[i+1][j][0]);
                dp[i][j][1]=min(dp[i][j][1],dp[i+1][j][1]);
            }
            if(j0]=min(dp[i][j][0],dp[i][j+1][0]);
                dp[i][j][1]=min(dp[i][j][1],dp[i][j+1][1]);
            }
            dp[i][j][0]+=save[i][j][0];
            dp[i][j][1]+=save[i][j][1];
        }
        printf("%d\n",min(dp[1][1][0],dp[1][1][1]) );
    }

    return 0;
}


inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

先回去,要锁门了.

总结:节点的状态表示不一定要是结果.甚至可以同时进行多个dp,最后合并成结果.

你可能感兴趣的:(题解)