问题
分析
这题看上去很复杂,但紫书上的思路很清晰,按照上面的思路写就比较简单了
首先是问题,这个问题是bit传输中会有[1-d+1]的延时,扰乱顺序,其中0,1数量不变,所以可以规定所有的0看作按照原顺序收到的,把所有的1看作按照原顺序收到的,这规定就把问题变为了两串数字0,1交替插值,0,1的序号之差要在d之内,所以我们可以用贪心法求解最大和最小值
假设原来1-n比特数字,传输后变为[1,n+d]这么长,但这个区间内实际有数字到达的时间不超过n,我们可以规定去除多余的延时,不影响结果,然后由于同一时间点到达的数字可以按照任意顺序排列,所以可以将其中的一些数字到达的时间合并,如紫书上的图9-25,总结出来的规律就是任意时刻k中已经被接收的位最右边那位一定没有延迟
下面是状态和状态转移,规定状态d(i,j)是前面i个0和j个1组合成的整数个数,下面就只有两种转移方式,一种是接着加0,转移到dp(i+1,j),另一种是接着加1,转移到dp(i,j+1),有点类似于归并排序是合并两个数组
添加时需要判断是否满足条件,就是两个数字间(i+1和j 或者 i和j+1 两种组合方式)的发射时间间隔小于d
总的时间复杂度
注意
要用unsigned long long存储状态
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;
const int maxn=70;
unsigned long long ans=0,n,d,k,maxv,minv,dp[maxn][maxn]; //long long会溢出
int kase=0,digit[maxn],digit0[maxn],digit1[maxn],n0,n1;
void dec2bin(unsigned long long t){
for(int i=0;i<n;++i){
digit[n-i-1]=t%2;
t=t/2;
}
}
void countDigit(){
for(int i=0;i<n;++i){
if(digit[i]==0){
digit0[n0++]=i;
}else{
digit1[n1++]=i;
}
}
}
void greedy(){
maxv=minv=0;
int i=0,j=0;
//反向计算二进制指数值的技巧
while(i<n0 || j<n1){
if(i<n0 && (j==n1 || digit1[j]+d>=digit0[i])){
++i;
minv=2*minv;
}
else {
++j;
minv=2*minv+1;
}
}
i=j=0;
while(i<n0 || j<n1){
if(j<n1 && (i==n0 || digit0[i]+d>=digit1[j])){
++j;
maxv=2*maxv+1;
}
else {
++i;
maxv=2*maxv;
}
}
}
int main(void){
while(cin>>n && n){
cin>>d>>k;
n0=n1=0;
// memset(digit,-1,sizeof(digit));
dec2bin(k);
countDigit();
memset(dp,0,sizeof(dp));
dp[0][0]=1; //dp[0,0]代表空的序列
//dp[i,j] 代表i个0和j个1,所以对应数组中序号要减少
for(int i=0;i<=n0;++i){
for(int j=0;j<=n1;++j){
if(i<n0 && (j==n1 || digit1[j]+d>=digit0[i])) dp[i+1][j]+=dp[i][j]; //加入0
if(j<n1 && (i==n0 || digit0[i]+d>=digit1[j])) dp[i][j+1]+=dp[i][j]; //加入1
}
}
ans=dp[n0][n1];
greedy();
printf("Case %d:",++kase);
cout<<" "<<ans<<" "<<minv<<" "<<maxv<<endl;
}
}