Showing posts with label vkcup. Show all posts
Showing posts with label vkcup. Show all posts

Sunday, March 25, 2012

Codeforces VK Cup round 2: (ouch)

This was a very discouraging round to me. Well, I see a lot of problems out of my league. But for each of them, there are tons of people that solved it. That said, I have suspicions about a couple of them.

Problem A (link)

A quick dp problem to boot. I guess. There are a couple of complications. The length 5000, shall make it tight to stay under the 256 MB limit. This is a dp that although it may be straightforward to see that it just needs dp, you need to know how to guard a couple of details, one is the memory limit (At most you can do a [2][5000][5000] int array). The other is taking care of a couple of details...

Eventually, after trying some stuff that made me redo the problem, I decided it is best to move the dp inside another loop. Let us iterate for the position of the last character of the substring. Then solve the subproblem: How many pairs of substring of s, subsequence of t are equal such that the substring's last character is at the given position?

Then we should avoid counting empty strings. Simply add a variable to the state (nonempty) if we want to count empty strings or not. (We would like to count empty strings in a sub problem if we already found a pair of characters that match).

Thus we have the recurrence f(nonempty, a, b) which should return the number of pairs such that:
- The last character of the substring is at position (a-1).
- The last character of the subsequence is at a position less than b.
- If nonempty is 1, count the empty substrings/sequences that match.

The logic works as follows. We know that the substring must begin at (a-1), so s[a-1] must be matched to a character from t. From t, we can remove any number of characters. So, if (s[a-1]==t[b-1]) we have the option to match them. If we do, then we
have to add to the result f(1, a-1, b-1) , because that is the result of subsequences and substrings that we can append to s[a-1] and t[b-1] to get a result.

We can also move to f(nonempty, a, b-1) , in other words, just ignore the character at position t[b-1], until we find a match for s[a].

string s, t; 

const int MOD = 1000000007;
int dp[2][5001][5001];

#define SUBMOD(x) if (x>=MOD) { x-=MOD;}

int rec(int nonempty, int a, int b)
{
int& res = dp[nonempty][a][b];
if (res == -1) {
if ( (a==0) || (b==0) ) {
res = nonempty;
} else {
res = 0;
if (s[a-1] == t[b-1]) {
res += rec(1, a-1, b-1);
SUBMOD(res);
}
res += rec(nonempty, a, b-1);
SUBMOD(res);
}
//cout << sstart<<", "<<a<<", "<<b<<" = "<<res<<endl;
}
return res;
}


int solve()
{
memset(dp,-1,sizeof(dp));
int res = 0;
for (int i=1; i<=s.length(); i++) {
res += rec(0, i, t.length() );
SUBMOD(res);
}
return res;
}




Problem B (link)
I think the high level idea of my approach is correct, but details need to be polished. I submitted almost knowing that it was unlikely I would pass. When you want to minimize the maximum time to assign to some variables. It is almost always an indication to use binary search for the time. There is an issue in this case, and it is that times are real values, so your binary search would need many iterations to be precise.

I cut down some iterations by noticing that the height h is irrelevant as we do not really need to output the time. Its overall effect on the time is always a factor of h, since that factor is constant we can just switch to minimizing the worst (i/v_j). This is helpful because it reduces the maximum time from 10^9 to 10^5.

For a given time, is it possible to solve the problem in a time less than or equal to it? Once the time is fixed, you can find, for each lemming, the maximum position it can be assigned to. Then for each position from greater to lower, you can just assign the lemming with the maximum weight available to that position. (Making sure not to pick a lemming heavier than the one in the above position). This greedy strategy will always work correctly. The problem is to implement it in a way that is quick. Whether it is the constant factor or the logarithmic factor I used to do things like find the maximum weight, it seems it is too slow.

Problem C (link)
I didn't do much, except note that d=(l/(v1+v2))*v1 is the length of the interval of positions in which a chocolate can be picked. After that you need to do some data structure magic to get all the probabilities in O(n*log(n)) or something like that.

Problem D (link)
I tried many things without much success. At first I thought of simple ideas like always distributing each prime number with an exponent >= 3 evenly between the side lengths and then deciding on exponent%3. But examples like V=8*7*7, break such idea.


Outcome
Failed B, my A submission was very slow. I hope I at least earn some rating.

Sunday, March 11, 2012

Codeforces: VK Cup round 1: Problem C: Abracadabra

This problem haunted me all day and I kept solving it inside my head without really wanting to. At the end, the solution I thought of all day was sort of incomplete, but a little dirty trick can make it pass. Later I saw some other codes and finally noticed the hard reality...

The divide and conquer
First of all, let us say that we care about the string present after a number of steps equal to step (30 for the string we want to solve). We can tell the size of the string (t = 2^step - 1). We can know the position of them middle character m = t/2 + 1. Please note that due to how the string is constructed, the middle character is unique - this character does not appear in any other position of the string at this number of steps.

In fact, that's really all we need to know how to use divide and conquer in this problem. Let us say that the result greatest common substring contains this middle character, then it is forcefully the overlap between the two intervals. If the best substring is not the overlap between the two intervals, then it won't contain the middle character.

In fact, we can split each of the intervals into two maximal parts (possibly empty) that do not contain the middle character. And each of these four parts will match an interval in the string for step # (step - 1). (The positions of the intervals that appear after the middle, shall get the middle position subtracted).

The result is thus one of five possibilities: It can be the overlap of the two intervals. Or it can be the largest substring (same sub-problem) between one of the four possible pairs between strings that come from the first interval and strings that come from the second.

This solution as is has a lot of branching. Worst case may call the divide and conquer recurrence 4^30 times.

Dirty tricks
First of all, we will handle trivial cases such as when one of the intervals is empty. The result is obviously always 0 in those cases.

But there are ways to optimize that original 4^30 idea. What I did was to make sure that it is 2^30, avoiding to branch the recurrence more than two times per instance, this requires some tricks like not doing the split when both intervals begin at the first position or end at the last (It can be proven the overlap is the best result in this case). Even at 2^30, it is too slow, the second dirty trick was to do an array mem[80][80][80][80], and if both extremes are less than 80, memoize the results of the recurrence. This will make the number of calls from 2^30 to 2^30 / (80^4) + 80^4. This is enough to solve the problem.

The better solution
A smarter solution involves noticing, what if one interval is completely inside the other? In this case, the overlap is 100% surely the best answer (And it is actually equal to the size of the smallest string). It is easy to know that this will give a correct answer. It is slightly more complicated to notice that this little optimization will make the algorithm run very fast. In fact, the maximum number of branches is 2, so the result is O(step). Really.

As a slight proof, let us think of a pair of intervals such that one is not a subset of the other.

1) If neither interval touches the middle letter, the only pair that is not trivial (does not have an empty string) will be one that matches the two intervals again but in the previous step. No branching.
2) If both intervals touch the middle letter, there will be branching. Yes. But 2 of the pairs of intervals generated will be cases in which one interval is a subset of the other. So the case will branch 2 times, and both times the case will an interval that begins at position 1 with another interval that ends at position t.
3) Handling Case 2 will either result in a sub-problem in which one interval is a subset or the other, or in Case 2 again. Try it.

int solve(int l1, int r1, int l2, int r2, int step=30) 
{
if (l1 > r1) {
return 0;
}
if (l2 > r2) {
return 0;
}
//overlap
int ovl = max(l1, l2);
int ovr = min(r1, r2);
int overlap = ( (ovr >= ovl)? (ovr - ovl + 1) : 0 );

int t = (1<<step) - 1;

int m = t/2 + 1; //the middle

// should we continue in depth? (Is the result not necessarily the overlap?)
if ( (l1 <= l2 && r1 >= r2) || (l2 <= l1 && r2 >= r1)) { //one is a subset of the other
return overlap;
}
// split each interval in two halves that don't touch the middle.
int l[4] = { min(l1, m) , max(m+1, l1) - m, min(l2, m) , max(m+1, l2) - m};
int r[4] = { min(r1, m-1), max(m, r1) - m, min(r2, m-1), max(m, r2) - m};
int res = overlap;
// try each pair of interval.
for (int i=0; i<2; i++) {
for (int j=2; j<4; j++) {
res = max(res, solve(l[i],r[i], l[j], r[j], step-1) );
}
}
return res;

}

Codeforces: VK Cup round 1 (update 1)

After learning that people that are not in the cup can participate in a rated version of the contest. I really went through a lot of inconvenience to be able to participate in this round.

After I solved problem B (albeit with an overflow bug), I was not able to submit. Some time later codeforces announced the unofficial match won't be rated because unofficials weren't able to submit. At then I stopped trying. Around some time later I check back codeforces and it seems that submission is possible again. So I tried to submit B, failed, fixed and then suddenly I had only less than an hour left... I wish things went differently, I got problem D solved, kind of.

Problem B linky
Only Karts that have stools will be discounted. Now note that once you put a stool in the kart, then you would like to avoid to place any element with smaller cost. Placing elements with larger cost will not change the discount (but it might make you miss the opportunity to receive a discount on that same item in another kart). So, once you place a stool, you really should avoid placing any other item altogether. In cases where there are more karts than stools, there will be plenty of karts to place the elements you do not want to place in karts that already have stools. In the other case, where the number of stools is less than or equal to the number of karts, then there is no need to place those unwanted elements in more than one kart with a stool. Doing this will make you unable to get the discount for that stool, and in fact will make you get a discount for the least expensive item overall.

The best discount you can get will get from stools. If there are less discountable karts than stools, it is better to pick the largest stools to place into each of those karts. Thus the logic goes as follows:

- If there are less stools than karts: Pick a kart for each stools. Distribute the remaining items in the stool-less karts (make sure to place at least one item in each, but the actual distribution does not matter).
- Else, you will need to forcefully place the minimum price kart and a stool in one kart. Place the best (k-1) stools in the other karts and any other element in the kart that has the least expensive element.

Problem A linky
At first I thought you would need fancy std::sets, but since x and y are always the same for all soldiers, it gets a little simple.

Assume that you have the soldiers and vests sorted non-decreasingly.

Now, let us say you are picking the i-th soldier in such order. Since we have already made decisions for all the soldiers with a smaller a_i, then we can get rid of all vests that are smaller than a_i - x. The smallest vest greater than or equal to a_i-x will remain, note that if this vest is less than or equal to a_i+y, you can match it to the soldier, and in fact, you must match it to the soldier. Else it will be impossible to match the vest, but it might be possible to match it with a soldier with a higher preference.

Problem C linky
Seemed interesting, but submission counts suggested D was easier, and they both have the same score, so I skipped to D.

Problem D linky

Since it is a tree, it is quite dp-friendly. Assume we have
decided an arbitrary root for the tree, and such each node is either the root or has a parent and all the other adjacent nodes are children. Let us say we constructed a table dp[x][k], which returns the number of (grand) children of x at a minimum distance equal to k. Once the tree gets an arbitrary root, it is easy to calculate this table: dp[x][0] is 1 for all nodes (the node itself is at 0 units of distance), then it is sum(dp[y][k-1]) for all y that are children of x.

Let us count all the pairs (x,y) that are K edges away and x is a (Grand) parent of y. This is precisely equal to dp[x][K].

But there is a catch, sometimes the parent node x may be just part of the K-size path between two nodes that are children of it (Do not worry about parents of x, you will consider their involvement when they themselves become x).

Well, not so hard, let starting from dp[y][a] for all children y of x, you can easily count the number of paths of length a+1 that start at x and go through the y child. You can merge all such the paths of different sizes a and b such that a+b = k. This requires some trick to make sure the algorithm is not very slow (Let sum[a] be the total sum of paths of length a, sum[a] - dp[y][a] will give the number of paths that do not go through y).

This takes you to a O(n * k) algorithm. Note that the number of edges is O(n), (in the second part whilst doing all the merges, you visit all edges O(k) times).

Edit: here is the code for problem D. I mostly finished it during the match but had the typical bug of increasing the wrong counter inside a for. Then it turns out that when practice room becomes active I actually time out in the largest case. It turns out that the constraints were too large and you couldn't use memoization in the dp part. That's kind of lame.

//================================ 
// Program:
//
int n, k;
int u[50000]; //note that I use 0-based indices instead of 1-based
int v[50000]; //because 1-indexing is crud.

int degree[50000];
vector<int> G[50000];

int parent[50000];

void dfs(int x, int p)
{
if (parent[x] == -2) {
parent[x] = p;
for (int i=0; i<degree[x]; i++) {
dfs( G[x][i] , x );
}
}
}

// dp[j][x] how many grand children does x have away of j distance units?
int dp[501][50000];

long long solve()
{
fill(degree, degree+n, 0);
for (int i=0; i<(n-1); i++) {
degree[ u[i] ]++;
degree[ v[i] ]++;
}
for (int i=0; i<n; i++) {
G[i].resize(degree[i]);
degree[i] = 0;
}
for (int i=0; i<(n-1); i++) {
G[u[i]][degree[ u[i] ]++] = v[i];
G[v[i]][degree[ v[i] ]++] = u[i];
}
fill(parent, parent+ n, -2);
dfs(0, -1);

for (int j=0; j<=k; j++) {
for (int x=0; x<n; x++) {
if (j==0) {
//Base case, when k = 0, the only "child" at a distance 0 is
// the node x itself.
dp[j][x] = 1;
} else {
dp[j][x] = 0;
// Else the sum of dp[j-1][y] for every children y of x.
for (int i=0; i<degree[x]; i++) {
int y = G[x][i];
if (y != parent[x]) {
dp[j][x] += dp[j-1][y];
}
}
}
}
}

long long res = 0;
for (int i=0; i<n; i++) {
res += dp[k][i]; //Paths that start at node #i


// Paths that go through node #i, without using the edge connecting it
// to its parent.
long long sum[k+1];
fill(sum, sum+k+1, 0);
int active = 0;
for (int j=0; j<degree[i]; j++) {
if (G[i][j] != parent[i]) {
active++;
for (int p = 0; p <= k; p++) {
sum[p] += dp[p][ G[i][j] ];
}
}
}
if (active > 0) {
long long tem = 0;
for (int j=0; j<degree[i]; j++) {
if (G[i][j] != parent[i]) {
active++;
for (int p = 1; p <= k-1; p++) {
//a: number of paths of length p that go from node i
//and use the edge towards child j.
//b: number of paths of length k-p that go from node i
//and don't use the edge that towards child j.
long long a = dp[p-1][ G[i][j] ];
long long b = sum[k - p - 1] - dp[k - p - 1][ G[i][j] ];
tem += a*b;
}
}
}
res += tem / 2;
}
}

return res;

}