Showing posts with label dp. Show all posts
Showing posts with label dp. Show all posts

Monday, December 07, 2009

another dp

TheSwap

framework for a dp solution


// memoization unit
int memo[][];

int main() {

// reset memo
memset(memo, 0, sizeof(memo));

// calling subroutine to iteratively solve the problem
return doit();
}


int doit(int n, int k ) {
//if already visited state, return memo[][];
if (memo[][] != 0) return memo[][];

// if it is the stopping condition, return memo[] = ? //
// e.g., k = 0 return n????

// initialize return value
int ret = 0; // -1 sometimes

for(all the possible variables) {
for(for the possible variables) {

// pre-processing to get n_1

// enter subproblem
// comparison_funct, e.g., max, min, ++
// note k-1 below, this identifies the subproblem
ret = comparison_function(ret, doit(n_1, k-1) ) ;

// post-processing to get back n
// we might need some processing to retrieve n in the original problem
n = f(n_1);
}
}

// this state is visited, set it in the memo
return ret = memo[][];

}




int doit(vector &v, int k) {
int rv = get_v(v);

// visited
if(memo[rv][k] != 0) return memo[rv][k];

if(k == 0) return memo[rv][k] = rv;

int a = -1;
for(int i = 0; i < v.size(); ++i) {
for(int j = i+1; j < v.size(); ++j) {

// excluded cases: highest digit does not swap with '0'
if (j == v.size()-1 && v[i] == 0) continue;

// pre-processing
int tmp = v[i];
v[i] = v[j], v[j] = tmp;
// enter subproblem
a = max(a, doit(v, k-1));

// post-processing
v[j] = v[i], v[i] = tmp;
}
}

return memo[rv][k] = a;
}


For another problem in NumbersAndMaches.

const int dis[10][7] =
{
{1, 1, 1, 0, 1, 1, 1},
{0, 0, 1, 0, 0, 1, 1},
{1, 0, 1, 1, 1, 0, 1},
{1, 0, 1, 1, 0, 1, 1},
{0, 1, 1, 1, 0, 1, 0},
{1, 1, 0, 1, 0, 1, 1},
{1, 1, 0, 1, 1, 1, 1},
{1, 0, 1, 0, 0, 1, 0},
{1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 0, 1, 1},
};


long long dp[20][127][127];

int _inc[10][10], _dec[10][10];

int k;
long long solve(long long N, int idx, int added, int removed) {

if (dp[idx][added][removed] != -1)
return dp[idx][added][removed];

if(N == 0) {
if (added == removed && added <= k) {
return 1LL;
}
return 0LL;
}

int i = N % 10;
long long ret = 0;
for(int j = 0; j <= 9; ++j) {
ret += solve(N/10, idx+1, added + _inc[i][j], removed + _dec[i][j]);
}
return dp[idx][added][removed] = ret;
}

long long NumbersAndMatches::differentNumbers(long long N, int K)
{
for (int i = 0; i < 10; i ++)
for (int j = 0; j < 10; j ++)
{
_inc[i][j] = _dec[i][j] = 0;
for (int k = 0; k < 7; k ++)
{
if (dis[i][k] < dis[j][k])
_inc[i][j] ++;
if (dis[i][k] > dis[j][k])
_dec[i][j] ++;
}
}
k = K;
memset(dp, -1, sizeof(dp));
return solve(N, 0, 0, 0);
}

Saturday, November 28, 2009

bitset and memoization

SRM 448 DIVII LEVEL 3

http://forums.topcoder.com/?module=Thread&threadID=651040&start=0

Let D(n,m) = number of ways to arrange the cards given that we have used "m" cards (m = bitmask representation) and the last card that was chosen was card n.

Sunday, October 19, 2008

Maximum interval sum

Or maximum contiguous subvector of the input.

Do a linear sweep from left to right, accumulate the sum. Start new interval whenever you encounter partial sum < 0.

/* 1-d linear alg O(n) */
inline int mis1d(int line[], int size) {
int maxv = -INF;
int p = 0;
for(int i = 0; i < size; ++i) {
p += line[i];
if(p < 0) p = 0;
if(p > maxv) maxv = p;
}
return maxv;
}
In a 2D problem, we are expected to find a maximum sum subrectangle in a mxn matrix. 


  • We first calculate the accumulative sum in the dimension of length m (smaller dim).

  • We then use the above technique in the dimension of length n.

  • The total running time is O(m^2 n). (for nxn matrix, that is O(n^3) )
UVa problem: 108, 836

#include <iostream>
using namespace std;

#define INF 999999999
int M[101][101];
int partialSum[101][101];
int line[101];

/* 1-d linear alg O(n) */
inline int mis1d(int line[], int size) {
int maxv = -INF;
int p = 0;
for(int i = 0; i < size; ++i) {
p += line[i];
if(p < 0) p = 0;
if(p > maxv) maxv = p;
}
return maxv;
}

/* 2-D O(n^3) */
int mis2d(int size) {
int maxv = -INF;
for(int j = 0; j < size; ++j) {
partialSum[0][j] = M[0][j];
for (int i = 1; i < size; i++)
partialSum[i][j] = partialSum[i-1][j] + M[i][j];
}
for(int i = 0; i < size; ++i) { // use from the i-th row
for(int j = i; j < size; ++j) { // to the j-th row
if(i == j)
memcpy(line, M[i], size*sizeof(int));
else {
// generate the data i to j-th row
for(int k = 0; k < size; ++k)
line[k] = partialSum[j][k] - partialSum[i][k];
}
int v = mis1d(line, size);
if(v > maxv) maxv = v;
}
}
return maxv;
}

int main() {
int n;
while(scanf("%d ", &n) == 1) {
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j)
scanf("%d", &M[i][j]);
printf("%d\n", mis2d(n));
}
}
Related problems:


  • find the subvector with the sum closest to zeros


    • calc the accumulative sum so that cum[i] = x[0] + ... + x[i]

    • The subvector x[l ... u] sums to zeros if cum[l-1] = cum[u]

    • the subvector with the sum closest to zeros is found by locating the two closest elements in cum

    • which can be done in O(nlogn) by sorting

  • find the subvector with the sum closest to a given real number t.


    • a simple extension from the above case

    • The subvector x[l ... u] sums to t if cum[l-1] + t = cum[u] and l-1<u

    • first nlogn sorting

    • then for each element, do a (log n) binary search for closest to (cum[i]+t)
 

Sunday, September 21, 2008

Number Split

topCoder: NumberSplit

By Editorial:

The first thing to notice here is that the numbers in every step become smaller. Let's take an example and say we have a five digit number of the form abcde (a, b, c, d and e represent the decimal digits of the number), which we split to produce the successor: ab * c * de. Here it is always c < 10 and de < 100, so for the successor we have: ab * c * de < ab * 10 * 100 = ab000 ≤ abcde. Similar with any other numbers and splittings.

Now we need a way to compute all possible successors, given a given number. For this we can use the following recursive pseudo-code:

generateSuccessors(int multiplier, int n) {
add (multiplier * n) to the set of successors
for (i = 10; i <= n; i *= 10) {
generateSuccessors(multiplier * (n / i), n % i);
}
}


We initialize the set of successors to an empty set, and call generateSuccessors(1, n) (where n is the number, for which we want to find the successors). Finally, we remove n from the generated set (since this is not a successor of n itself, we need to split the given number to at least two parts for a successor to be valid).


To compute the longest possible sequence, starting with the given start, generate all successors of start as described above, and for each n in the successor set compute recursively longestSequence(n). The return value is the maximum of all computed values + 1 (if start is a single digit number, the successors set will be empty, and we return 1). Note that this would not work if loops in the sequence were possible, but since each successor is smaller then the original number, this can not happen. In order to avoid a timeout, we need to memorize in a buffer all values already computed.


Alternatively, we can use dynamic programming, by initializing longest[i] = 1 for all single digit numbers i, and computing longest[i] from i = 10 up to i = start by adding 1 to the maximum of longest[j] for all j in the successor set of i. This works, since all successors of i are smaller than i. With this solution, we compute the longest sequence for more numbers than actually needed (many numbers between 1 and start can not be reached from start) but with the low constraints this solution was within the time limit.


my dp code with memoization (note: topCoder does not have itoa)


void itoa(int n, char* buff) {
sprintf(buff, "%d", n);
}
// memoization array
int a[999999];
int solve(string s) {
int ss = atoi(s.c_str());
if(a[ss] != -1) return a[ss];
char buff[10];
if(s.length() == 1) return 1;
int res = 0;
for(int i = 1; i < s.length(); ++i) {
// single split
int left = atoi(s.substr(0,i).c_str());
int right = atoi(s.substr(i).c_str());
itoa(left*right, buff);
checkmax(res, 1+solve(string(buff)));
string rights = s.substr(i);
if(rights.length() <= 1) continue;

// this part does double split
for(int j = 1; j < rights.length(); ++j) {
int lleft = atoi(rights.substr(0,j).c_str());
int lright = atoi(rights.substr(j).c_str());
itoa(left * lleft * lright, buff);
checkmax(res, 1+solve(string(buff)));
}
}
return a[ss] = res;
}

int NumberSplit::longestSequence(int start) {
char buff[10];
itoa(start,buff);
memset(a, -1, sizeof(a));
string s(buff);
return solve(buff);
}

Floyd-Warshall: All-Pairs Shortest Path

By wiki, Floyd-Warshall is a graph analysis algorithm for finding shortest paths in a weighted, directed graph. A single execution of the algorithm will find the shortest paths between all pairs of vertices. The Floyd–Warshall algorithm is an example of dynamic programming.

 1 /* Assume a function edgeCost(i,j) which returns the cost of the edge from i to j
2 (infinity if there is none).
3 Also assume that n is the number of vertices and edgeCost(i,i)=0
4 */
5
6 int path[][];
7 /* A 2-dimensional matrix. At each step in the algorithm, path[i][j] is the shortest path
8 from i to j using intermediate values in (1..k-1). Each path[i][j] is initialized to
9 edgeCost(i,j).
10 */
11
12 procedure FloydWarshall ()
13 for k: = 0 to n1
14 for each (i,j) in (0..n − 1)
15 path[i][j] = min ( path[i][j], path[i][k]+path[k][j] );

For numerically meaningful output, Floyd-Warshall assumes that there are no negative cycles (in fact, between any pair of vertices which form part of a negative cycle, the shortest path is not well-defined because the path can be infinitely small). Nevertheless, if there are negative cycles, Floyd–Warshall can be used to detect them. A negative cycle can be detected if the path matrix contains a negative number along the diagonal. If path[i][i] is negative for some vertex i, then this vertex belongs to at least one negative cycle.

Applications:


  • Shortest paths in directed graphs (Floyd's algorithm).
  • Transitive closure of directed graphs (Warshall's algorithm). In Warshall's original formulation of the algorithm, the graph is unweighted and represented by a Boolean adjacency matrix. Then the addition operation is replaced by logical conjunction (AND) and the minimum operation by logical disjunction (OR).
  • Optimal routing. In this application one is interested in finding the path with the maximum flow between two vertices. This means that, rather than taking minima as in the pseudocode above, one instead takes maxima. The edge weights represent fixed constraints on flow. Path weights represent bottlenecks; so the addition operation above is replaced by the minimum operation.
  • Testing whether an undirected graph is bipartite
  • Minmax / Maxmin Distance

    Shortest Path Problems:

    int Floyd_Warshall (int n) {
    for(int k = 0; k < n; ++k)
    for(int i = 0; i < n; ++i)
    for(int j = 0; j < n; ++ j)
    G[i][j] = min(G[i][j], G[i][k] + G[k][j]);
    }


    Transitive Closure/Hull

    for(int k = 0; k < N; ++k)
    for(int i = 0; i < N; ++i)
    for(int j = 0; j < N; ++j)
    G[i][j] = G[i][j] || (G[i][k] && G[k][j]);


    Minmax/ Maxmin distance

    void minmax(int n) {


    for(int k = 0; k < n; ++k)
    for(int i = 0; i < n; ++i)
    for(int j = 0; j < n; ++j)
    G[i][j] = min(G[i][j], max(G[i][k], G[k][j]));
    }



      void maxmin(int n) {
      for(int k = 0; k < n; ++k)
      for(int i = 0; i < n; ++i)
      for(int j = 0; j < n; ++j)
      G[i][j] = max(G[i][j], min(G[i][k], G[k][j]));
      }


    • UVa 534: Frogger

    • UVa 10048: Audiophobia

    • UVa 544: Heavy Cargo

    • UVa 10099: The Tourist Guide

  • Subset Sum Problem

    The problem is this: given a set of integers, does the sum of some non-empty subset equal exactly zero?The problem is NP-Complete. wiki

    An equivalent problem is this: given a set of integers and an integer s, does any non-empty subset sum to s? Subset sum can also be thought of as a special case of the knapsack problem. One interesting special case of subset sum is the partition problem, in which s is half of the sum of all elements in the set.

    topCoder: PayBill Editorial

    There are two basic approaches to this problem. The first, which inevitably fails due to timeout on larger test cases, is to try obtain the sum by either including or excluding the first element, and then calling itself recursively with the remainder of the set. Unfortunately, with the maximum 50 people, this is over 1 quadrillion operations to perform.

    Instead, a more clever, dynamic programming approach needs to be used. Since each item can only be up to 10,000, we know that the sum cannot be more than 500,000. So, we simply need a boolean array of 500,000 elements, where the i-th element represents whether or not we can reach the total i with some subset of the original values. In code it looks something like this:

    boolean[] canTotal = new boolean[500001];
    canTotal[0] = true;
    for (int i = 0; i < meals.length; i++)
    for (int j = totalMoney; j >= meals[i]; j--)
    if (canTotal[j - meals[i]]) canTotal[j] = true;

    topCoder: LoadBalancing Editorial


    int minTime(vector<int> chunkSizes)
    {
    vector<int> dp(204801, 0);
    dp[0] = 1;
    int total = 0;
    for (int i=0; i<(int)chunkSizes.size(); i++)
    {
    total += chunkSizes[i]/1024;
    for (int j=204800; j>=0; j--)
    if (dp[j] == 1)
    dp[j+chunkSizes[i]/1024] = 1;
    }
    for (int i=(total+1)/2; true; i++)
    if (dp[i] == 1)
    return 1024 * i;
    }

    UVa 562: Dividing Coins



    跟上面一题如出一辙,就是平分问题。这里用了bitvector

    const int MAX = 50000;
    const unsigned int N = 1563;
    unsigned int a[N+1];

    int coin[101];
    int main() {
    int ndata;
    scanf("%d ", &ndata);
    while(ndata--) {
    int n;
    scanf("%d ", &n);

    for(int i = 0; i < n; ++i) {
    scanf("%d ", &coin[i]);
    }

    int total = std::accumulate(coin, coin+n, 0);

    memset(a, 0, sizeof(int)*(total>>SHIFT));
    SET(a, 0);
    for(int i = 0; i < n; ++i) {
    for(int j = total; j >= 0; --j)
    if(TEST(a, j))
    SET(a, j+coin[i]);
    }

    for(int i = (total+1)/2; true; ++i) {
    if(TEST(a, i)) {
    printf("%d\n", abs(2*i-total));
    break;
    }
    }
    }
    }

    UVa 11517 Exact Change



    这道要多记一个coin的数目,不能用bitvector了

            a[0] = 1;
    for(int i = 0; i < n; ++i) {
    for(int j = max-1; j >= 0; --j)
    if(a[j]) {
    int t = j+ bill[i];
    if(t > max) continue;
    if(a[t] != 0)
    a[t] = a[t] < a[j]+1 ? a[t]: a[j]+1;
    else
    a[t] = a[j]+1;
    }
    }
    for(int i = price; i <= MAX; ++i) {
    if(a[i]) {
    printf("%d %d\n", i, a[i]-1);
    break;
    }
    }

    Saturday, September 20, 2008

    memo[i][j] = min ( memo[i][k] + memo[k+1][j] )

    用类似这一recurrence的题非常多,在这汇总一下

      topCoder: QuickSums

      INPUT: i, j, sum
      for all i <= k <= j take out substring(i,k) from sum try d[k+1][j] = solve(substring(k+1,j), sum - substring(i,k)); end for d[i][j] = min (1+d[k+1][j]) k=i...j

      边缘情况注意一下,如果d[i][j]整体已经等于sum,直接返回,不用1+res

      #define INF 999999999
      int memo[12][12][102];
      void checkmin(int &a, int b) {
      if(b < a) a = b;
      }
      int solve(string numbers, int i, int j, int s) {
      if(memo[i][j][s] >= 0) return memo[i][j][s];
      if(i == j) {
      if (numbers[i]-'0' == s) return memo[i][j][s] = 0;
      else return memo[i][j][s] = INF;
      }
      // the whole word match!
      if(atoi(numbers.substr(i, j-i+1).c_str()) == s) return memo[i][j][s] = 0;
      int res = INF;
      for(int k = i; k < j; ++k) {
      int left = atoi(numbers.substr(i,k-i+1).c_str());
      if(left <= s){
      //cout << numbers.substr(k+1, j-k+1) << "->" << s-left << endl;
      int sol = 1+solve(numbers, k+1, j, s-left);
      //cout << numbers.substr(k+1, j-k+1) << " (" << sol <<")" << endl;
      checkmin(res, sol);
      }
      }
      return memo[i][j][s] = res;
      }

      int QuickSums::minSums(string numbers, int sum) {
      memset(memo, -1, sizeof(memo));
      int res = solve(numbers, 0, numbers.length()-1, sum);
      if(res == INF) return -1;
      return res;
      }


      topCoder: ShortPalindromes


      “ shortest(base)
      if base is already a palindrome then
      return base
      if base has the form A...A then
      return A + shortest(...) + A
      if base has the form A...B then
      return min(A + shortest(...B) + A,
      B + shortest(A...) + B)

      string memo[26][26];
      string solve(string base, int i, int j) {
      //cout << base.substr(i,j-i+1) << endl;
      if(i>j) return string("");
      if(i == j) return memo[i][j]=base[i];
      if(memo[i][j] != "") return memo[i][j];

      string res;
      if(base[i] == base[j]) {
      res = base[i] + solve(base, i+1, j-1)+base[i];
      return memo[i][j]=res;
      }

      string pre = base[j] + solve(base, i, j-1) + base[j];
      string suf = base[i] + solve(base, i+1, j) + base[i];

      if(pre.length() != suf.length()) {
      if(pre.length() < suf.length()) res = pre;
      else res = suf;
      }
      else {
      if(pre < suf) res = pre;
      else res = suf;
      }
      return memo[i][j]=res;
      }

      string ShortPalindromes::shortest(string base) {

      int L = base.length();
      for(int i = 0; i < L; ++i)
      for(int j = 0; j < L; ++j)
      memo[i][j]="";
      return solve(base, 0, base.length()-1);
      }

      topCoder: TreePlanting


      long long memo[62][62][62];
      int N;
      long long solve(long long status, int i, int j, int f) {

      if(memo[i][j][f] != -1LL) return memo[i][j][f];

      if(f == 0 || (i == j && f == 1)) return memo[i][j][f] = 1LL;
      if(i > j) return memo[i][j][f] = 0LL;
      if(i == j && f > 1) return memo[i][j][f] = 0LL;

      long long res = 0;
      for(int k = i; k <= j; ++k) if(status & 1LL<<k) {

      res += solve(status ^ (1LL<<k), k+2, j, f-1);
      }
      return memo[i][j][f] = res;
      }

      long long TreePlanting::countArrangements(int total, int fancy) {
      memset(memo, -1, sizeof(memo));
      N = total;
      long long status = (1LL<<total)-1;
      return solve(status, 0, total-1, fancy);
      }
      Editorial solution:

      Here, we need to get a little creative to come up with a workable solution. Consider placing f fancy trees along a total of n locations, so that no two are adjacent. Call our function, C, the number of ways to do this. At our first location, we can either plant a fancy tree, or not plant a fancy tree. If we plant a fancy tree, then we can't plant a fancy tree in the second spot, and hence will have n-2 locations in which to plant the remaining f-1 fancy trees. If we don't plant a fancy tree in the first spot, then we have n-1 locations in which to place all f fancy trees. This gives us our recursive formula, C(n, f) = C(n - 2, f - 1) + C(n - 1, f). Our starting values are of course, C(1, 1) = 1, and C(0, 0) = 1.

      However, with the problem constraints as they are, a simple recursive function by itself is not sufficient. We either need to implement memoization, whereby we store the values of previous recursive calls, to avoid recalculating them repeatedly, or we can use dynamic programming, as shown here:

      public long countArrangements(int total, int fancy) {
      long[][] count = new long[total + 1][fancy + 1];
      count[0][0] = 1;
      count[1][1] = 1;
      for (int i = 0; i < = total; i++)
      for (int j = 0; j < = fancy; j++) {
      if (i > 0)
      count[i][j] += count[i - 1][j];
      if (i > 1 && j > 0)
      count[i][j] += count[i - 2][j - 1];
      };
      return count[total][fancy];
      }

      topCoder: SentenceDecomposition

      int diff(string& a, string& b) {
      int cost = 0;
      for(int i = 0;i < a.length(); ++i)
      if(a[i]!=b[i]) cost ++;
      return cost;
      }
      bool match(string& aa, string &bb) {
      sort(ALL(aa));
      return aa==bb;
      }
      int memo[52];
      //dict is a copy of validWords
      //sort_dict is a copy of dict, but each row is sorted
      vector<string> dict, sort_dict;
      #define INF 999999999
      int solve(int i, string &s) {
      //cout << s.substr(i) << endl;
      if(memo[i] != -1) return memo[i];

      if(i == s.length()) return memo[i] = 0;
      if(i > s.length()) return memo[i]=INF;

      bool found = false;
      int mincost = INF;
      for(int k = 0; k < dict.size(); ++k) {
      int len = dict[k].length();
      if(!match(s.substr(i, len), sort_dict[k])) continue;
      int ret = 0;
      ret = solve(i+len, s);
      if(ret == INF) continue;
      else {
      found = true;
      ret += diff(s.substr(i, len), dict[k]);
      }
      mincost = min(mincost, ret);
      }

      if(found) return memo[i] = mincost;
      return memo[i] = INF;
      }

      int SentenceDecomposition::decompose(string sentence, vector <string> validWords) {
      sort_dict = dict = validWords;
      for(int i = 0; i < sort_dict.size(); ++i)
      sort(ALL(sort_dict[i]));

      memset(memo, -1, sizeof(memo));

      int res = solve(0, sentence);
      if(res == INF) return -1;
      return res;
      }

      Friday, September 19, 2008

      Longest Increasing/Decreasing Subsequence

      A lot of related problems:


      two basic dp algorithms

      e.g.,
      sequence: 1,6,2,3,5,4,7
      algorithm 1: O(N^2)
      a 1 2 3 4 5 6 7
      ---------------------------
      1 6 2 3 5 4 7
      ---------------------------
      0| 1 1 1 1 1 1 1
      1| (1) 2 2 2 2 2 2
      2| 1 (2) 2 2 2 2 2
      3| 1 2 (2) 3 3 3 3
      4| 1 2 2 (3) 4 4 4
      5| 1 2 2 3 (4) 4 5
      6| 1 2 2 3 4 (4) 5 <-

      algorithm 2: O(NlogN)
      a 1 2 3 4 5 6 7
      ---------------------------
      1 6 2 3 5 4 7
      ---------------------------
      0| 1
      1| 1 6
      2| 1 2
      3| 1 2 3
      4| 1 2 3 5
      5| 1 2 3 4
      6| 1 2 3 4 7 <-
      topCoder: thePriceIsRight 
      // longest increasing sequence (O(N^2)) 
      vector<int> lis (vector<int> prices) {
      vector<int> len(prices.size(), 1); //length of the longest inc sequence up to i
      vector<int> ways(prices.size(), 1); //how many ways of the lcs up to i

      for(int i = 0; i < prices.size()-1; ++i) {
      for(int j = i+1; j < prices.size(); ++j)
      if(prices[j] > prices[i]) {
      if(len[j] < len[i] + 1) {
      len[j] = len[i] + 1;
      ways[j] = ways[i];
      }
      else if(len[j] == len[i]+1)
      ways[j]+= ways[i];
      }
      }
      int maxv = *max_element(len.begin(), len.end());
      int nways = 0;
      for(int i = 0; i < len.size(); ++i)
      if(len[i] == maxv) nways += ways[i];
      vector<int> res;
      res.push_back(maxv), res.push_back(nways);
      return res;
      }

      topCoder: Books
      // binary search 
      template<typename T1, typename T2> int bsearch(T1 &A, T2 target) {
      int u = 0;
      int v = A.size()-1;
      while(u < v) {
      int c = (u + v) / 2;
      if (target > A[c]) u=c+1;
      else v=c;
      }
      return u;
      }

      // longest non-decreasing sequence
      template<typename T> int lis (vector<T> &seq) {
      if(seq.empty()) return 0;
      vector<T> A;
      int n = seq.size();
      A.push_back(seq[0]);

      int u, v;
      for(int i = 1; i < n; ++i) {
      if(seq[i] >= A.back()) {
      A.push_back(seq[i]);
      continue;
      }
      int u = bsearch(A, seq[i]);
      //important !!! when compute non-increasing or non-decreasing seq
      if(A[u] == seq[i]) A[u+1] = seq[i];
      else A[u] = seq[i];
      }
      return A.size();
      }

      int Books::sortMoves(vector <string> titles) {
      return books.size()-lis(titles);
      }

      WordParts

      TopCoder: WordParts –> Editorial

      • prefix, suffix
      • memoization

      d[i][j] = min(d[i][k] + d[k+1][j])
      k --- substring(i,k) exists in the prefix/suffix array
      set<string> dict;
      #define INF 999999999
      int memo[51];

      void checkmin(int &a, int b) {
      if (b < a) a = b;
      }
      int solve(string &compound, int cpos) {
      //cout << compound.substr(cpos) << " " << cpos << endl;
      if(memo[cpos] >= 0) return memo[cpos];
      if(cpos == compound.length()) return memo[cpos] = 0;

      int res = INF;
      int maxlen = compound.length() - cpos;

      for(int i = 1; i <= maxlen; ++i) {
      string s = compound.substr(cpos, i);
      if(dict.count(s)) {
      checkmin(res, 1+solve(compound, cpos+i));
      }
      }
      return memo[cpos] = res;
      }

      int WordParts::partCount(string original, string compound) {
      dict.clear(); // don’t forget to reset dict
      // add the whole word
      dict.insert(original);
      // add all prefix
      for(int i = 1; i <= original.length()-1; ++i)
      dict.insert(original.substr(0,i));
      // add all suffix
      for(int i = 1; i <= original.length()-1; ++i)
      dict.insert(original.substr(i));

      memset(memo, -1, sizeof(memo));
      int res = solve(compound, 0);
      if(res == INF) return -1;
      return res;
      }

      dancing couples

      topCoder: DancingCouples editorial
      "Each subproblem is clearly defined by three variables: the set of unmatched boys, the set of unmatched girls, and the number of couples we need to make."
      d[i][j][k]  ----- the total ways to make k couples 
      | | |
      | | i couples to make
      | available girl status (bitmask)
      the i-th boy to match

      d[i][j][k] = the ways to make k couples in which we do not use the i-th boy +
      the ways to make k couples in which we use the i-th boy
      = d[i-1][j][k] + sum (d[i-1][j^(1 << k)][k-1])
      {k|A[k][j]='Y'}
      对比上一贴:

      cnt [v][s] = the ways in which z6 = 0 + the ways in which z6 > 0
      = cnt[v-1][s] + cnt[v][s-v]
      The code:
      int dp[12][1025][12];
      int solve(int nboy, int available_girl, int K) {
      if(nboy < K || count_bit(available_girl) < K) return 0;
      if(K == 0) return 1;
      if(dp[nboy][available_girl][K] >= 0) return dp[nboy][available_girl][K];

      int res = solve(nboy-1, available_girl, K);

      for(int i = 0; i < cd[0].size(); ++i) {
      if(cd[nboy-1][i] == 'Y' && available_girl & (1<<i))
      res += solve(nboy-1, available_girl ^ (1<<i), K-1);
      }
      return dp[nboy][available_girl][K] = res;
      }

      knapsack problem

      0/1 Knapsack Problem

      n items (U = {u1, u2, … , u_n} ) need to be packed in a knapsack of size C. Each item has value v_i and size s_i.We want to find a subset of U such that

      sum_ vi is maximized subject to the constraint

      sum_si <= C

      let V[i][j] denote the value obtained by filling a knapsack of size j with items taken from the first i items in an optimal way.
      • V[i][j] = 0 (if i = 0 or j = 0)
      • V[i][j] = V[i-1][j] ( if j < si )
      • V[i][j] = max ( V[i-1][j], V[i-1][j-si] + vi ) ( if i > 0 and j >= si )

      相关的dp题
      SuperSale:: 标准knapsack, 每人来一轮knapsack
      #include <iostream>
      #include <vector>
      #include <algorithm>

      using namespace std;

      int C[1001][31];

      int Vi[1001], Wi[1001];
      int MW[31];

      void knapsack(int N, int MaxW) {
          for(int i = 0; i < N; ++i)
              C[i][0] = 0;
          for(int w = 0; w <= MaxW; ++w)
              C[0][w] = 0;
          for(int i = 1; i <= N; ++i) {
              for( int w = 1; w <= MaxW; ++w) {
                  if(Wi[i] > w)
                      C[i][w] = C[i-1][w];
                  else
                      C[i][w] = max(C[i-1][w], C[i-1][w-Wi[i]] + Vi[i]);
              }
          }
      }

      int main() {

          int T; cin >> T;

          for(int t = 0; t < T; ++t) {
              int N; cin >> N;
              for(int i = 1; i <= N; ++i) {
                  cin >> Vi[i] >> Wi[i];
              }

              int G; cin >> G;
              for(int i = 0; i < G; ++i)
                  cin >> MW[i];
              int total = 0;
              int maxw = *max_element(MW, MW + G);
              knapsack(N, maxw);
              for(int i = 0; i < G; ++i)
                  total += C[N][MW[i]];
              cout << total << endl;
          }

          return 0;
      }


      topcoder editorial 对第一题有很详细的讲解,中心意思是简化问题先,将原问题转化为一个更易表达和编程的形式。最后是这个形式:
      0 ≤ z1, ..., z6
      z1 + 2z2 + ... + 6z6 ≤ 6M-21

      "We can now count the number of valid sequences as follows. There are two types of valid sequences: Those where z6>0 and those where z6=0. In the first case, we can subtract 1 from z6, and get the same problem with the right side of the inequality smaller by 6. In the second case, we get a similar problem with only 5 variables.

      This can be formulated as a recurrence relation. Let cnt[v][s] be the number of ways in which we can set variables z1 to zv so that the total (z1 + ... + vzv) does not exceed s. Then we have: cnt[v][s] = cnt[v-1][s] + cnt[v][s-v]. "

      第二个题即:
      0≤z1≤a1,..., 0≤z6≤a6
      z1 + 2z2 + ... + 6z6 = (a1 + a2 + ... + a6)/2
      如果没有a1,...,a6的限制,跟题1几乎一样了,只要把"+"改成"||"就行了。
      cnt[v][s] = cnt[v-1][s] || cnt[v][s-v].

      但跟题1的一点不同是z1,...,z6有上限,所以dp过程中我们记录了zv已经用了几次。而且这题只要知道能不能"="(平分)即可,我们用0表示否,1表示可以(zv一次没用),2(zv用了一次),..., i(zv用了i-1次了),...
              int maxsum = 0;
      for(int v = 1; v <= 6; ++v) {
      maxsum += v * a[v];
      if (maxsum > sum) maxsum = sum;
      cnt[v][0] = 1; // zero can be partitioned
      for(int s = 0; s <= maxsum; ++s) {
      cnt[v][s] = cnt[v-1][s] > 0;
      if(cnt[v][s] == 1) continue;
      if(s >= v && cnt[v][s-v]) {
      if(cnt[v][s-v] - 1 < a[v]) cnt[v][s] = cnt[v][s-v]+1;
      }
      }
      }

      下面完整的code也用了题1的技巧,用v%2省了2/3的memory,速度也由63ms降为1ms以下。因为dp每次只用上一轮的结果,e.g.,算cnt[5][s]的时候只用cnt[4][s]。
      #include <iostream>
      #define N 20000

      int cnt[2][6*N/2+1];

      int main() {
      int a[7] = {0};
      int t = 1;
      while(scanf("%d %d %d %d %d %d",
      &a[1],&a[2],&a[3],&a[4],&a[5],&a[6])!= EOF &&
      (a[1] || a[2] || a[3] || a[4] || a[5] || a[6])) {

      printf("Collection #%d:\n", t++);
      int sum = 0;
      for(int i = 1; i <= 6; ++i)
      sum += i * a[i];

      // odd sum
      if(sum & 1) {
      printf("Can't be divided.\n");
      printf("\n");
      continue;
      }
      sum = sum >> 1; // divided by 2
      memset(cnt, 0, sizeof(cnt));

      cnt[0][0] = 1;

      int maxsum = 0;
      for(int v = 1; v <= 6; ++v) {
      maxsum += v * a[v];
      if (maxsum > sum) maxsum = sum;
      cnt[0][0] = 1; // zero can be partitioned
      for(int s = 0; s <= maxsum; ++s) {
      cnt[v%2][s] = cnt[1-v%2][s] > 0;
      if(cnt[v%2][s] == 1) continue;
      if(s >= v && cnt[v%2][s-v]) {
      if(cnt[v%2][s-v] - 1 < a[v]) cnt[v%2][s] = cnt[v%2][s-v]+1;
      }
      }
      }

      if(cnt[0][sum]) printf("Can be divided.\n");
      else printf("Can't be divided.\n");
      printf("\n");
      }
      return 0;
      }
      来一个例子:

      -----------------------
      z1 z2 z3 z4 z5 z6
      -----------------------
      1 1 2 1 1 1
      -----------------------

      sum = 24, target = sum/2 = 12
      -------------------------------------
      s |[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12][13]
      ---------+----------------------------------------------------------
      cnt[1][s]| 1 2 0
      --------------------------------------------------------------------
      cnt[2][s]| 1 1 2 2 0
      --------------------------------------------------------------------
      cnt[3][2]| 1 1 1 1 2 2 2 3 3 3 0
      --------------------------------------------------------------------
      cnt[4][2]| 1 1 1 1 1 1 1 1 1 1 2 2 2 0
      --------------------------------------------------------------------
      cnt[5][2]| ......
      --------------------------------------------------------------------


      又碰到一道类似的,也放在这了
      topCoder: windowWasher
      0≤z1,z2,...,zn
      z1 + z2 + ... + zn = width of the wall (每种worker的人数之和等于wall width)
      minimize max(T1 x z1, T2 x z2, ..., Tn x Zn) (最小化总时间,大家可以并行干活)
      dp recurrence:
      cnt[v][s] = min(cnt[v-1][s], max(cnt[v-1][s-n], T[v]*n))
      n=1..s
      = min(max(cnt[v-1][s-n], T[v]*n))
      n=0..s
      启用第v个worker时,两种情况:
      1. 不用这个worker,只用前v-1个worker
      2. 用这个worker 1次,2次, ... s次
      只比前两题多一层循环,同样可以用% 2的方法,这里没用了。
      int cnt[51][1001];

      int WindowWasher::fastest(int width, int height, vector <int> washTimes) {
        int N = washTimes.size();
        vector<int> T(N+1);
        for(int i = 1; i <= N; ++i)
          T[i] = height * washTimes[i-1];
        memset(cnt, -1, sizeof(cnt));

        // use only the first worker
        for(int s = 0; s <= width; ++s)
          cnt[1][s] = s*T[1];

        // start from only one worker, add worker one by one
        for(int v = 2; v <= N; ++v) {
          cnt[v][0] = 0; // no work! no time needed!
          for(int s = 1; s <= width; ++s) {
            cnt[v][s] = cnt[v-1][s]; // do not use worker v, use v-1 worker only
            // worker v works on 1,2,...,s columns
            for(int n = 1; n <= s; ++n)
              checkmin(cnt[v][s], max(cnt[v-1][s-n], T[v]*n));
          }
        }
        return cnt[N][width];
      }

      Thursday, September 18, 2008

      Matrix Chain Multiplication

      经典教科书题
      N个矩阵,N+1个维数

      Matrix: 0 ...... N-1
      -----------+---------------------------------------------------------
      Matrix dim:| (M0 x M1) x (M1 x M2) x (M2 x M3) x ... x (M_N-1 x M_N)
      -----------+---------------------------------------------------------
      Matrix | 0 1 2 N-1
      ---------------------------------------------------------------------

      The i-th matrix's dimension: M_(i-1) x M_i
      第i到j个矩阵相乘的代价

      d[i][i] = 0
      d[i][j] = min(d[i][j], d[i][k]+d[k+1][j]+ M_(i-1)*M_(k)*M_(j))
      int C[N+1][N+1];
      int K[N+1][N+1];
      void mcm(vector<int> &M, int n) {
      for(int i = 1; i <= n; ++i)
      C[i][i] = 0;
      for(int d = 2; d <= n; ++d) {
      for(int i = 1; i <= n-d+1; ++i) {
      int j = i + d - 1;
      C[i][j] = INF;
      for(int k = i; k <= j-1; ++k) {
      int tmp = C[i][k] + C[k+1][j] + M[i-1] * M[k] * M[j];
      if( tmp < C[i][j]) {
      K[i][j] = k;
      C[i][j] = tmp;
      }
      }
      }
      }
      }
      C[1][N]中存着从1到N连乘最小的代价,从K[][]可以得到路径。
      void print_opt(int i, int j) {
      if(i == j)
      cout << "A" << i;
      else {
      cout << "(";
      print_opt(i, K[i][j]);
      cout << " x ";
      print_opt(K[i][j]+1, j);
      cout << ")";
      }
      }
      调用时传入mcm()一个长度为N+1的数组,和N(不是N+1!)
      vector<int> M(n+1);
      mcm(M, n);
      下面是memoization的版本,编程简单
      int lookupMcm(vector<int> &M, int i, int j) {
      if(C[i][j] != INF) return C[i][j];
      if(i == j)
      return C[i][j] = 0;
      for(int k = i; k < j; ++k) {
      int q = lookupMcm(M, i, k) + lookupMcm(M, k+1, j) + M[i-1] * M[k] * M[j];
      if( C[i][j] > q)
      C[i][j] = q;
      }
      return C[i][j];
      }

      调用前将C[][]初始化为INF
      for(int i = 1; i <= n; ++i)
      for(int j = 1; j <= n; ++j)
      C[i][j] = INF;

      printf("%d\n", lookupMcm(M, 1, n));
      Similarly, this technique can be used to solove the following problem: (in today's interview (oct 21) )

      find the length of the longest regular brackets sequence that is a subsequence of s

      主要思想就是从中间出发,往两边发展,

      如果已和dp[i][j]则考虑 dp[i-1][j+1]

      if  s[i-1] match s[j+1], then dp[i-1][j+1] = dp[i][j]

      else  dp[i-1][j+1] = max (dp[i-1][k] + dp[k+1][j+1])

      #include <cstdio>
      #include <cstring>

      const int MAX = 101;

      // dynamic programming 2d array
      int dp[MAX][MAX];

      // return true if find matching brackets
      inline bool match(char a, char b) {
      if(a == '(' && b == ')') return true;
      if(a == '[' && b == ']') return true;
      return false;
      }

      int main() {
        char buff[MAX];

      // terminate until "end"
      while (gets(buff) && buff[0] != 'e') {
      int n = strlen(buff);
      memset(dp, 0, sizeof(dp));
      for (int i = 0; i < n; ++ i)
      if (match(buff[i],buff[i+1]))
      dp[i][i+1] = 2;

      for (int k = 2; k < n; ++ k) {
      for (int i = 0; i < n; ++ i) {
      if (k + i < n) {
      if (match(buff[i], buff[i+k]))
      dp[i][i+k] = dp[i+1][i+k-1] + 2;
      for (int j = i; j < i + k; ++ j) {
      if (dp[i][j] + dp[j+1][i+k] > dp[i][i+k])
      dp[i][i+k] = dp[j+1][i+k] + dp[i][j];
      }
      }
      }
      }
      printf("%d\n", dp[0][n-1]);
      }
      return 0;
      }

      Wednesday, September 17, 2008

      Josephus problem

      N人围成一圈,每轮报到k倍数的离开,从下一个人开始重新从1开始报数. 求解最后一个剩下的人最初是第几个。
      问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。

      我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
      k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
      并且从k开始报0。

      现在我们把他们的编号做一下转换:
      k --> 0
      k+1 --> 1
      k+2 --> 2
      ...
      ...
      k-2 --> n-2
      k-1 --> n-1

      变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x‘=(x+k)%n

      如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:

      令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]

      递推公式
      f[1]=0;
      f[i]=(f[i-1]+m)%i; (i>1)

      有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1

      由于是逐级递推,不需要保存每个f[i],程序也是异常简单:

      DP solution: O(N)
      d[1] = 0
      d[i] = ( d[i-1] + k ) % i
      不光最后一个剩下的符合这个规律,倒数第j个离开的也是这样

      d[i,j] = ( d[i-1, j] + k ) % i

       K = 3, $->  second-last; * -> last
      ---------------------------------
      0 1 2
      $ *

      0 1 2 3
      * $

      0 1 2 3 4
      $ *

      0 1 2 3 4 5
      * $

       

      int d[151];

      // standard joseph problem solution
      // person id is 0-based
      int joseph(int n, int m) {
      d[1] = 0;
      for(int i = 2; i <= n; ++i)
      d[i] = (d[i-1] + m)% i;
      return d[n];
      }

      // another joseph problem extension
      // always kill the 1st guy, then every m-th guy
      // idea: after the 1st guy is killed,
      // the problem (n,m) is changed to a (n-1, m)
      // standard joseph problem
      // the solution's id + 1 is the old problem's id
      int joseph1(int n, int m) {
      int last = joseph(n-1, m);
      return last + 1;
      }

      // the solve subrutine is used to find the exact m
      // to make the 2nd person the last guy to kill
      // in extension joseph1
      int solve(int n) {
      int m = 2;
      while(true) {
      // 0-based index
      int last = joseph1(n, m);
      if (last == 1)
      break;
      ++ m;
      }
      return m;
      }

      Problem:




      • UVa 130 Roman Roulette

      • UVa 133 The Dole Queue



        • just simulate the process

        • using array to keep the status (i tried to use bit vector, but it seems no good for this problem)
                  int live = n;  // live person #
          int p1 = 1, p2 = n; // current position
          while(live > 0) {
          int cnt = 0;
          while(true) { //skip k persons
          if(a[p1]) ++cnt;
          if(cnt == k) break;
          reg(--p2,n)
          }
          cnt = 0;
          while(true) { // skip m persons
          if(a[p2]) ++cnt;
          if(cnt == m) break;
          reg(--p2,n)
          }

          if(p1 == p2) {
          printf("%3d", a[p1]);
          a[p1] = 0; live--;
          } else {
          printf("%3d%3d", a[p1],a[p2]);
          a[p1]=a[p2]=0;
          live -= 2;
          }
          reg(++p1,n);
          reg(--p2,n);

          if(live) printf(",");
          }


      • UVa 305 Joseph



        • We have to check :

        • a1 = (m-1) % n > k

        • a2 = (a1 + m-1) % (n-1) > k

        • a3 = (a2 + m-1) % (n-2) > k

        • ...

        • ak = (a(k-1) + m-1) % (n-k+1) > k
          int gen_data(int k) {
          int n = k << 1; // total number n = 2k
          int m = k+1; // starting to check at k+1
          bool found = false;
          while(!found){
          int ap = 0; // previous (a_i-1)
          int an; // current (a_i)
          for(int i = 0; i < k; ++i) {
          an = (ap+m-1) % (n-i);
          ap = an;
          if(an < k)
          break;
          if(i == k-1 && an >= k)
          found = true;
          }
          if(!found) ++m;
          }
          return m;
          }


      • UVa 402 M*A*S*H

      Monday, September 15, 2008

      Two types of couting change problem (DP)

      1. to minimize the number of coins

      d[i] = min(d[i], d[i - denomination[j]] + 1)
      j
      e.g., coins = {1, 5, 10, 25}, 其实算到100以内足够了,100以上的(n > 100) 用
      (n / 100) x 4 + d[n % 100] 就行了。(整dollar直接4个quarter, 零头查100以内的表)如果还要输出各种coin都用了多少,用个d[i][D] 二维表,第二维直接存各种coin用的数. 上面的例子,

      [0] [1] [2] [3] [4]
      0(min num) 1c 5c 10c 25c
      -------+---------+---+----+----+---|
      |d[1] | 1 | 1| 0| 0| 0|
      |d[2] | 2 | 2| 0| 0| 0|
      ....
      |d[5] | 1 | 0| 1| 0| 0|
      |d[6] | 2 | 1| 1| 0| 0|
      ....
      |d[100]| 4 | 0| 0| 0| 4|


      2. to output the maximum number of ways to count the changes
      Uva judge:

      "The number of ways to change amount A using N kinds of coins equals to:
      • The number of ways to change amount A using all but the first kind of coins, plus
      • The number of ways to change amount A-D using all N kinds of coins, where D is the denomination of the first kind of coin." --- Art of programming contest SE for uva