Buy Low, Buy Lower[USACO]
这道题的第一问比较简单,以a[i]结尾的最长减数列的长度f(i) = max{f(j)| j < i, a[j] > a[i]} + 1。
第二问,可以先考虑简单一点的,不考虑重复的话,以a[i]结尾的最长减数列的个数 g(i) = ∑{g(j)| j < i, a[j] > a[i], f(j) = f(i) - 1},我们要做的只是从中去除重复的情况,考虑一个例子:
.......64(1)......64(2)....a[i](小于64)....
64(1) 之前长度为L的减数列,将其与64(2)组合肯定还是一个减数列,而且64(2)为结尾的减数列最长长度肯定是 不小于L的,也就是说,此时计算g(i)时,就不需要64(1)不需要参与∑运算。怎么表示这种情况呢:
为了避免每次都扫描重复情况,只扫描一次,生成arrNextPos[],arrNextPos[j]记录着下一个a[j]的出现位置,如果这个位置小于i,则不考虑这个元素.
另外:
1.USACO好像没有_itoa,_atoi 这种东西,怎么整都是compile error。于一,第一次使用stringstream,感觉very good。
2.开始用的int,test7还是8来着 出错了,必须用Big Number。
3.用USACO的思想,2^31是个10位数,所以我把string 分割成8位一组来计算加法。需要注意添0,以及最后去掉头部多余的0。比如计算过程中的 100000001,我们将开头的1进位,后边取余时得到的1在搞成字符串的时候要把7个0加上。
这么计算加法的话,应该是比逐位计算快很多的~
/* ID: zhangyc1 LANG: C++ TASK: buylow */ #include <fstream> #include <iostream> #include <string> #include <sstream> #include <map> #include <iomanip> using namespace std; stringstream os; class LongInt { public: LongInt(){strNum = "0";} LongInt(int i){os << i;os >> strNum;os.clear();} LongInt& operator=(int i){os << i;os >> strNum;os.clear();return *this;} LongInt& operator=(LongInt& other){strNum=other.strNum; return *this;} LongInt& operator+=(LongInt& other); private: string strNum; friend ostream& operator<<(ostream& out, LongInt& longInt){out << longInt.strNum; return out;} }; ofstream fileout("buylow.out"); const int MAX_SIZE = 5001; // arrLen[i]:以a[i]结尾的最长减数列的长度; arrCount[i]:以a[i]结尾的最长减数列的个数 int N, arrNum[MAX_SIZE], arrLen[MAX_SIZE]; LongInt arrCount[MAX_SIZE]; // arrRepeated[i]:从a[i + 1]到a[N]是否已经出现过a[i] int arrNextPos[MAX_SIZE]; LongInt& LongInt::operator+=(LongInt& other) { // 2147483647 = 2^31 - 1。每次计算8位 string strRs = "", strTemp; int nNum1, nNum2, nCarry = 0; int nLen1 = this->strNum.length(); int nLen2 = other.strNum.length(); while (nLen1 || nLen2 || nCarry) { if (nCarry == 0) { if (nLen1 <= 0) // 将剩余的字符串塞到strRs头中 { string strLeft = other.strNum.substr(0, nLen2); strRs.insert(0, strLeft); break; } if (nLen2 <= 0) // 将剩余的字符串塞到strRs头中 { string strLeft = this->strNum.substr(0, nLen1); strRs.insert(0, strLeft); break; } } nNum1 = nNum2 = 0; int nLen = 0; if (nLen1 > 0) { if (nLen1 > 8) nLen = 8; else nLen = nLen1; nLen1 = nLen1 - nLen; os << strNum.substr(nLen1, nLen); os >> nNum1; os.clear(); } if (nLen2 > 0) { if (nLen2 > 8) nLen = 8; else nLen = nLen2; nLen2 = nLen2 - nLen; os << other.strNum.substr(nLen2, nLen); os >> nNum2; os.clear(); } int rs = nNum1 + nNum2 + nCarry; nCarry = rs / 100000000; rs %= 100000000; os << setfill('0') << setw(8) << rs; os >> strTemp; os.clear(); strRs.insert(0, strTemp); } int nPos = 0; while (strRs[nPos] == '0' && nPos < strRs.length()) { nPos++; } if (nPos == strRs.length()) { nPos = strRs.length() - 1; } this->strNum = strRs.substr(nPos); return *this; } void prepairData() { ifstream filein("buylow.in"); filein >> N; for (int i = 0; i < N; i++) { filein >> arrNum[i]; } arrLen[0] = 1; arrCount[0] = 1; arrNum[N] = 0; // 记录出现过的数 map<int, int> mapAppear; map<int, int>::const_iterator mapIt; for (int i = N; i >= 0; i--) { mapIt = mapAppear.find(arrNum[i]); if (mapIt == mapAppear.end()) { arrNextPos[i] = MAX_SIZE; } else { arrNextPos[i] = mapIt->second; } mapAppear[arrNum[i]] = i; } filein.close(); } void process() { // 计算最长长度 for (int i = 1; i <= N; i++) { int nMax = 0; for (int j = 0; j < i; j++) { if (arrNum[i] < arrNum[j] && nMax < arrLen[j]) { nMax = arrLen[j]; } } arrLen[i] = nMax + 1; } // 计算以各元素结尾的最长减数列的个数 for (int i = 1; i <= N; i++) { if (arrLen[i] == 1) { arrCount[i] = 1; continue; } LongInt nSum = 0; for (int j = 0; j < i; j++) { if (arrLen[i] == arrLen[j] + 1 && arrNum[j] > arrNum[i] && arrNextPos[j] > i) { nSum += arrCount[j]; } } arrCount[i] = nSum; } fileout << arrLen[N] - 1 << " " << arrCount[N] << endl; } int main(){ prepairData(); process(); fileout.close(); return 0; }