info

ICPCのdpcmデコード問題解いた

http://rose.u-aizu.ac.jp/onlinejudge/ProblemSet/description.jsp?id=2199

問題解いた物を改造してコードを読みやすくして、標準エラーに最小二乗和、標準出力にデコードしたバイナリを吐くようにした
ここのファイルをデコードするとノイズの後、ゆっくりの声がきこえる
http://acm-icpc.aitea.net/index.php?2010%2FPractice%2F%CC%CF%B5%BC%B9%F1%C6%E2%CD%BD%C1%AA%2F%CC%E4%C2%EA%CA%B8%A4%C8%A5%C7%A1%BC%A5%BF%A5%BB%A5%C3%A5%C8

音量注意
./a.out < C1 > decodedata
cat decodedata > /dev/dsp

dpcm.h

class Decodedpcm
{
private:
  std::vector <std::vector<int> > cache;
  std::vector<int> sample;
  std::vector<int> codebook;
  int cook(int, int); //実際にメモ化再帰を行う所                                                                           

public:
  Decodedpcm(std::vector<int>,std::vector<int>); //暗号データとコードブックのvectorを引数にとる
  virtual ~Decodedpcm(){}
  int saisyojijyo(); //最小二乗和を返すためのエントリーポイント
  void printdecodedata(); //標準出力にバイナリでデコードデータを出す
};

dpcm.cc

#include <iostream>
#include <vector>
#include <map>
#include <climits>
#include "dpcm.h"

using namespace std;
template<class T> inline T sqr(T x) {return x*x;}

//初期化リスト、二次元vectorをすべて-1で初期化して空の値を表現する、入力データの数ぶんだけ列を初期化する。
Decodedpcm::Decodedpcm(vector<int> insample, vector<int> incodebook)
  :cache(int(insample.size()) + 1, vector<int>(256, -1)),sample(insample),codebook(incodebook)
{}

//メモ化再帰、見ためをシンプルにするために128をコードブックぶんだけ足したものをエントリーポイントからもらわなければいけない->yの値
int Decodedpcm::cook(int sample_index, int y)
{
  //-1だったら空、キャッシュされてるならばキャッシュされてる値を返す
  //インデックスの値がvectorの範囲を超えていたらゼロを返す
  if(cache[sample_index][y] >= 0)
    {
      return cache[sample_index][y];
    }
  else if(sample_index == int(sample.size()))
    {
      return 0;
    }
  else
    {
      //その木で一番小さい葉までのコストの道を選ぶ
      int mincost = INT_MAX;
      for(vector<int>::iterator it = codebook.begin(); it != codebook.end(); ++it)
	{
	  int next_y = y + (*it);
	  if(next_y < 0){next_y = 0;}
	  else if(next_y > 255){next_y = 255;}

	  int cost = sqr<int>(sample.at(sample_index) - y);
	  int cooked = cost + cook((sample_index + 1), next_y);
	  //帰ってきた葉までのコストを更新する、小さいならば更新する
	  mincost = min(mincost, cooked);
	}
      //キャッシュして、一番小さいコストを返す
      return cache[sample_index][y] = mincost;
    }
}

//最小二乗和を返す、エントリーポイント、インデックス0の一番小さい二乗和を返す、コードブックの範囲のみで判断する
int Decodedpcm::saisyojijyo()
{
  int tmpmin = INT_MAX;
  for(vector<int>::iterator it = codebook.begin(); it != codebook.end(); ++it)
    {
      int next_y = 128 + (*it);
      if(next_y < 0){next_y = 0;}
      else if(next_y > 255){next_y = 255;}

      tmpmin = min(tmpmin, cook(0, next_y));
    }
  return tmpmin;
}
//http://ja.wikipedia.org/wiki/Sun%E3%82%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%82%AA%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB
//デコードしたデータを標準出力(.auファイル)に出す、変形したエントリーポイント、違うのはサンプルの長さぶんだけ探索するのと、小さい二乗和を見つけた時、出力データも保持する所
void Decodedpcm::printdecodedata()
{
  unsigned char y = 128;

  for(int i = 0; i < int(sample.size()); ++i)
    {
      int samplemin = INT_MAX;
      int tmpy = 0;
      for(vector<int>::iterator it = codebook.begin(); it != codebook.end(); ++it)
	{
	  int x = y + (*it);
	  if(x < 0){x = 0;}
	  else if(x > 255){x = 255;}

	  if(samplemin > cook(i,x))
	    {
	      samplemin = cook(i,x);
	      tmpy = x;
	    }
	}
      y = tmpy;
      cout << y;
    }
}


//メインループを使う
//処理と出力をきりわける
//リソースの確保、リリース
int main()
{
  int codebooklen,samplelen;

  unsigned char header[4] = {0x2e,0x73,0x6e,0x64};//文字データで.snd
  unsigned char offset[4] = {0,0,0,24}; //ヘッダのサイズ
  unsigned char datasize[4] = {255,255,255,255}; //データサイズ、わからなかったら0xffffffでいいらしい
  unsigned char encoding[4] = {0,0,0,2};//リニアPCM形式
  unsigned char samplerate[4] = {0,0,31,64};//8khz
  unsigned char channel[4] = {0,0,0,1};//モノラルかステレオか何か
  
  for(int i = 0; i < 4; ++i) cout << header[i];
  for(int i = 0; i < 4; ++i) cout << offset[i];
  for(int i = 0; i < 4; ++i) cout << datasize[i];
  for(int i = 0; i < 4; ++i) cout << encoding[i];
  for(int i = 0; i < 4; ++i) cout << samplerate[i];
  for(int i = 0; i < 4; ++i) cout << channel[i];

  while(cin >> samplelen >> codebooklen)
    {
      if((samplelen == 0) && (codebooklen == 0)){break;}

      vector<int>sample, codebook;
      for(int i = 0; i < codebooklen; ++i)
	{
	  int x;
	  cin >> x;
	  codebook.push_back(x);
	}
  
      for(int i = 0; i < samplelen; ++i)
	{
	  int x;
	  cin >> x;
	  sample.push_back(x);
	}

      Decodedpcm *my_decoder = new Decodedpcm(sample, codebook);

      cerr << my_decoder->saisyojijyo() << endl;
      my_decoder->printdecodedata();
      delete my_decoder;
    }
  return 0;
}
//http://rose.u-aizu.ac.jp/onlinejudge/ProblemSet/description.jsp?id=2199
//128からコードブックの値を選んで足し引きする
//足し引きするたび圧縮データ列の一つとひきざんして二乗する
//足し引きした値(最初なら128を)もちこしてまたコードブックを選んで次のサンプルと二乗する
//二乗した値を足し合わせて最小となるコードブックをえらんで、二乗した値を出力する