UVA1489/POJ3986/HDU3693 Math teacher's homework

我的blog

\(POJ\)题目链接:POJ3986 Math teacher's homework

\(HDU\)题目链接:HDU3693 Math teacher's homework
\[preface\]

\(UVA\)评测了我半个小时,害的我猛然发现\(HDU\)\(POJ\)上有这道题就过了

被强制要求写题解\(QaQ\)

\[description\]

给出两个整数\(K\)\(n\) 和一个整数序列\(M_1,M_2,M_3....M_n\)

求满足\(X_1 \otimes X2 \otimes X3...\otimes Xn=K\)\(0\leq X_i\leq M_i(i=1..n)\)的解的个数。
\[solution\]

这题真的不好想

\(K\)移到左边得到:

\[X_1 \otimes X2 \otimes X3...\otimes Xn \otimes K=0\]

显然我们就是求与\(K\)异或等于\(0\)的方案数

先将每个\(M_i+1\),题目就转化为:

求满足\(X_1 \otimes X2 \otimes X3...\otimes Xn \otimes K=0\)\(0\leq X_i< M_i(i=1..n)\)的解的个数。

先把这道题简单化:

有一个n个数的数列\(k\),对于任意的\(1\leq i\leq n\)满足\(ki\in\{0, 1\}\)。如果\(k_1\otimes k_2\otimes k_3...\otimes k_n=u\),且\(u\in\{0, 1\}\),求满足条件的k数列的数目。

这个的答案显然是\(2^{n - 1}\)。我们先随便取\(n - 1\)个数,最后一个数显然有且只有一种取法使得条件满足。

现在我们继续增加一个条件。如果对于一个整数\(0\leq p\leq n\)\(k\)数列的前\(p\)项都是确定的。这时的答案是多少?

这就不用说了。如果\(p\neq n\),显然是\(2^{n - p - 1}\);如果\(p=n\),就得看条件满足与否了。

由以上的两个分析我们可以想到这个题的一个初步做法。

(懒人盗图)

浅蓝色的部分表示和\(m_i\)的这一位一样。绿色部分表示\(m_i\)的这一位是\(1\),而这里是\(0\)的一位,橘黄色的部分表示不可以被随便确定的位子

解释一下:

蓝色部分之所以可以随意使用是因为前面决策已经设计到这里且自己做出的决策也大于这一位(这就是我们\(+1\)的原因),显然根据我们刚刚给出的结论,设浅蓝色个数位\(cnt\),是前面的数的方案数\(\times 2^{cnt}\)

而橘黄色的部分之所以不能变动是因为刚扩展的位数还不清楚接下来有没有下一个数满足有这一位数,其实我们在下一个数处理就行,不影响答案。

浅绿色部分很明显是扩展到的位数

非正解(正解是这个的优化)

\(dp[pos][val][len]\)表示当前决策到第\(pos\)个数,决策的异或值为\(val\),最低位到\(len\)(同样也可以理解为\(0\)\(len-1\)是蓝色部分)

\(res\)\(M_{pos}\)的前缀(这里的前缀的意思是指二进制下从高位到低位的前若干个数组合成的数,但是\(res\)不能等于\(M_{pos}\))

显然根据之前给出的结论

可以得出以下转移

\(j\)\(res\)中二进制下的\(1\)所在的最低位在\(M_{pos}\)中出现的位置的下一个\(1\)出现的位置
(绕口)

\(dp[pos][val][len]=\sum (dp[pos+1][val\otimes res][max(len,j)]\times 2^j)\)

(显然这么算j从\(0\),不然按照之前得出的结论,得\(-1\))

解释一下这个\(dp\):

  • 转移到\(pos+1\)这是数位\(dp\)固有的转移
  • 我们假设\(M_{pos}\)部分中的\(res\)已经被确定了,显然我们必须传到下面一位是\(val\otimes res\)
  • 显然最低肯操控的位数在绿色哪位,也就是\(max(len,j)\)
  • 根据我们之前得出的结论,我们需要让数相乘,显然,下一个数的贡献已经被算出来,根据之前问题所得出来的结论显然是\(\times 2^j\)

显然数组开不下\(QwQ\)

正解

正解是上一个非正解的\(dp\)优化

根据打表可以发现,在\(pos\)\(len\)确定时,\(val\)除了第\(len\)位都是可以确定的

证明

根据\(len\)的转移和之前j的定义,我们可以得出\(len\)是所有决策前缀的最低位的在原数所出现的位置下一个\(1\)出现的位置的最大值,显然每个数在算上各自j那一位都有至少匹配到这里的前缀。而不包括这一位因为各自的\(j\)显然都\(\leq len\),所以最高位到\(len+1\)位是肯定确定的我们只需要判断第\(len\)位的异或值即可

数组终于能装的下了,时间复杂度肯定过了,是
\[O(31^2\times 2n)\]

\[code\]

#include <cstdio>
#include <cstring>
#include <algorithm>
#define re register
#define ll long long
#define mod 1000000003
using namespace std;
template<typename T>
inline void read(T&x)
{
    x=0;
    char s=(char)getchar();
    bool f=false;
    while(!(s>='0'&&s<='9'))
    {
        if(s=='-')
            f=true;
        s=(char)getchar();
    }
    while(s>='0'&&s<='9')
    {
        x=(x<<1)+(x<<3)+s-'0';
        s=(char)getchar();
    }
    if(f)
        x=(~x)+1;
}
ll dp[55][35][2],power[35];
unsigned int a[55];
int n,k;
inline ll dfs(int pos,int val,int len)
{
    val&=(~((1<<len)-1));//把0~len-1位的数清零
    if(pos==n+1)
        return !val;
    bool flag=(val>>len)&1;
    ll &ans=dp[pos][len][flag];
    if(ans!=-1)
        return ans;
    ans=0ll;
    int res=0;
    for(re int i=31; i>=0; --i)
        if((a[pos]>>i)&1)
        {
            (ans+=dfs(pos+1,val^res,max(len,i))*power[min(len,i)]%mod)%=mod;
            res|=(1<<i);
//          printf("%d ",res);
        }
//  printf("dp[%d][%d][%d][%d]=%lld\n",pos,val,len,ans);
//  putchar('\n');
    return ans;
}
inline void work()
{
    for(re int i=1; i<=n; ++i)
    {
        read(a[i]);
        ++a[i];
    }
    memset(dp,-1,sizeof(dp));
    printf("%lld\n",dfs(1,k,0));
}
int main()
{
    power[0]=1;
    for(re int i=1; i<=31; ++i)
        power[i]=(power[i-1]<<1)%mod;
//  for(re int i=0; i^31; ++i)
//      printf("%lld\n",power[i]);
    while(read(n),read(k),n||k)
        work();
}

猜你喜欢

转载自www.cnblogs.com/wangjunrui/p/12293442.html