USACO Stringsobits, 还是得搬出动态规划来

本文通过三次代码迭代,最终采用动态规划方法高效解决了寻找特定条件下的第pos个二进制串的问题。该问题要求二进制串长度为N,包含不多于L个1。文章详细记录了解题过程,从最初的暴力搜索到使用动态规划减少无效计算。

这题证明俺现在真是老了,退步了,这是第一份代码:

  1. #include <fstream>
  2. using namespace std;
  3.  
  4. ifstream fin("kimbits.in");
  5. ofstream fout("kimbits.out");
  6.  
  7. unsigned int N, L, pos;
  8.  
  9. int main()
  10. {
  11.     fin >> N >> L >> pos;
  12.  
  13.     unsigned int num = 0, count = 0, max = (1<<N)-1, bit1, tmp;
  14.     while(count < pos)
  15.     {
  16.         if(num > max) break;
  17.         bit1 = 0, tmp = num;
  18.         for(; tmp > 0; bit1++)
  19.             tmp &= (tmp - 1);
  20.         if(bit1 <= L) ++count;
  21.         ++num;
  22.     }
  23.  
  24.     num -- ;
  25.     for (int i = N-1; i >=0; --i)
  26.         fout << ((num >>i) & 0x1u);
  27.     fout << endl;
  28.     return 0;
  29. }

其实还是挺简洁的哈,就是会超时,因为基本思路是暴力搜索。


这个是很郁闷的,看数据,确实太大了。仔细端详一番,觉得有DP的感觉,例如,对于(二进制)二位数:10 11,要得到三位数,实际上就是移位和移位+1,+1的操作中可以判断+1后得到的数字中的1是不是超过了给定的L,不过,首先还是先崩溃了一下:

  1. #include <fstream>
  2. #include <deque>
  3. #include <vector>
  4. using namespace std;
  5.  
  6. ifstream fin("kimbits.in");
  7. ofstream fout("kimbits.out");
  8.  
  9. typedef unsigned int UINT;
  10. UINT N, L, pos;
  11. vector<deque<UINT> > dp;
  12.  
  13. inline int printBinary(UINT num)
  14. {
  15.     for (int i = N-1; i >=0; --i)
  16.         fout << ((num >>i) & 0x1u);
  17.     fout << endl;
  18.     return 0;
  19. }
  20.  
  21. int main()
  22. {
  23.     fin >> N >> L >> pos;
  24.  
  25.     if(pos <= 2) {
  26.         printBinary(pos-1);
  27.         return 0;
  28.     }
  29.  
  30.     deque<UINT> d, dPre;
  31.     d.push_back(0);
  32.     for(UINT i = 0; i <= N; ++i)
  33.         dp.push_back(d);
  34.  
  35.     dp[0][0] = 1,  dp[0].push_back(1);
  36.     UINT count = 2, uTmp;
  37.  
  38.     for(UINT i = 0; i < N; ++i)
  39.     {
  40.         for(UINT k = 0; k <= i; ++k)
  41.         {
  42.             d.clear();
  43.             for(UINT j = 1; j <= dp[k][0]; ++j)
  44.             {
  45.                 uTmp = (dp[k][j] << 1);
  46.                 if(++count == pos) return printBinary(uTmp);
  47.  
  48.                 if(k+2 <= L) // k从0开始的
  49.                 {
  50.                     uTmp = (dp[k][j]<<1)+1;
  51.                     if(++count == pos) return printBinary(uTmp);
  52.                     d.push_back(uTmp);
  53.                 }
  54.  
  55.                 dp[k][j] <<= 1;
  56.             }
  57.             if(== i)
  58.             {
  59.                 dp[k+1].insert(++dp[k+1].begin(), d.begin(), d.end());
  60.                 dp[k+1][0] += d.size();
  61.             }
  62.             if(> 0)
  63.             {
  64.                 dp[k].insert(++dp[k].begin(), dPre.begin(), dPre.end());
  65.                 dp[k][0] += dPre.size();
  66.             }
  67.             dPre = d;
  68.         }
  69.     }
  70.  
  71.     return 0;
  72. }

时间还不知道,空间先扛不住了:


看Test 6和7,估计这个办法也不咋滴,为什么呢,究其原因还是无效计算过多。下面是AC的代码,主要算法思想写在注释里了,这道题本来很简单的,却搞了我这么久,受打击了,呜呜....

  1. #include <fstream>
  2. using namespace std;
  3. typedef unsigned int UINT;
  4. ifstream fin("kimbits.in");
  5. ofstream fout("kimbits.out");
  6.  
  7. UINT N, L, pos;
  8. UINT dp[33][33]; // dp(i,j)表示长度为i,1的个数不超过j的串有多少
  9. int res[33]={0};
  10.  
  11. int main()
  12. {
  13.     fin>> N >> L >> pos;
  14.     for(UINT j = 0; j <= L; j++)
  15.         dp[0][j] = 1;
  16.    
  17.     // 方程:f[j,k]=f[j-1,k]+f[j-1,k-1]; 分别表示在当前位加上0和加上1时的两种状况
  18.     // 边界:f[j,0]=1, f[0,j]=1, f[j,k](k>j)=f[j,j]
  19.     // 这样我们得到了所有的f[j,k] 需要做的就是据此构造出所求字符串
  20.     for(UINT i = 0; i <= N; i++)
  21.     {
  22.         for(UINT j = 0; j <=L; j++)
  23.         {
  24.             if(== 0) dp[i][j] = 1;
  25.             else if(<= i) dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
  26.             else if(> i) dp[i][j] = dp[i][i];
  27.         }
  28.     }
  29.    
  30.     // 构造思路如下:
  31.     // 设所求串为S,假设S的位中最高位的1在K位
  32.     // 那么必然满足:
  33.     // F[K-1,L]<pos and F[K,L]>=pos
  34.     // 这样的K是唯一的, 所以S的第一个1在从右至左第K位
  35.     // 因为有F[K-1,L]个串第K位上为0,所以所求的第I个数的后K位就应该是:
  36.     // 满足"位数为K且串中1不超过L-1个"这个条件的第 pos - F[K,L] 个数 => 递归的过程
  37.     while(pos > 1)
  38.     {
  39.         for(int i = N-1; i>=0; i--)
  40.         {
  41.             if(dp[i][L] < pos)
  42.             {
  43.                 res[N-i] = 1;
  44.                 pos -= dp[i][L];
  45.                 break;
  46.             }
  47.         }
  48.         L--;
  49.     }
  50.     UINT i = 1;
  51.     while(res[i] == 0 && 33 - i <= N) i++;
  52.     if(== N+1) i = 1;
  53.     for(; i <= N; i++)
  54.         fout << res[i];
  55.     fout << endl;
  56.     return 0;
  57. }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值