ENVPILE - Editorial

PROBLEM LINK:

Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4

Author: raysh07 and everule1
Tester: sky_nik
Editorialist: iceknight1093

DIFFICULTY:

TBD

PREREQUISITES:

Graph traversal

PROBLEM:

You have two piles of stones, one initially empty and one with a stone of weight W.
You also have infinite stones of N types, the i-th type having value A_i.

Do the following:

  • Choose an index i, and add a stone of weight A_i to the lighter pile.
    After this, it should now be the heavier pile.

Find a sequence of at most 7500 moves that uses every type of stone at least once.
N\leq 500 and A_i, W \leq 5000.

EXPLANATION:

As a first step, let’s simplify the situation a bit.
Rather than dealing with two piles of stones, let’s define d = |W_1 - W_2| to be the difference between the weights of the piles, and work with only this difference.

Our operation then changes as follows:

  • Choose an index i such that A_i \gt d, and replace d with A_i - d.

The A_i\gt d condition comes from the fact that we need to make the difference ‘negative’ to transfer which of the piles is the heavier one, and of course the new difference is |d - A_i| = A_i - d.

Since we’re forced to choose an A_i that’s larger than d in every step, in some sense our limiting quantity is A_1, the lightest stone type.
In particular, if we ever want to use A_1, we need to end up with d \lt A_1; otherwise it’s impossible.

On the other hand, observe that if we do end up with d\lt A_1, there’s a trivial strategy that uses every stone: use A_1, then A_2, then A_3, and so on till A_N.
After all, if 0 \lt d \lt A_i, then 0 \lt A_i - d \lt A_i, and we’re given that A_i \lt A_{i+1} so we can definitely operate with A_{i+1} the next move.
In other words, as long as we’re able to get down to d \lt A_1, we can use N more moves and ensure that every type is used.


All that remains is to figure out whether we can reach d\lt A_i (and a sequence of moves to reach there).
For that, we use the fact that constraints are small: N\leq 500 and A_i \leq 5000.

Consider a graph on A_N vertices (representing the differences 1, 2, 3, \ldots, A_N, where for each x and index i such that A_i \gt x, there’s an edge between x and A_i - x.
This graph encapsulates all the possible moves that can be made, and has N\cdot \max(A) edges at most, which is 500\cdot 5000 = 2.5\cdot 10^6, not too large.

We start out with a difference of W, so simply perform a search (using BFS/DFS or a DSU) in the connected component of W to find whether it includes a value smaller than A_1.
If it does, find a path to it from W (which uses at most \max(A) \leq 5000 moves), and once you’re there use N more moves as noted earlier to ensure that every type gets used at least once.
If every value in the component of W is \geq A_1, no solution exists.

TIME COMPLEXITY:

\mathcal{O}(N\cdot \max(A)) per testcase.

CODE:

Author's code (C++)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)1e18
#define f first
#define s second

mt19937_64 RNG(chrono::steady_clock::now().time_since_epoch().count());

void Solve() 
{
    int n, w; cin >> n >> w;

    vector <int> a(n);
    for (auto &x : a) cin >> x;

    int max = a[n - 1] + 1;
    vector <bool> vis(max + 1, false);
    vector <int> par(max + 1, -1);
    vis[w] = true;
    par[w] = -2;

    queue <int> q;
    q.push(w);

    while (!q.empty()){
    	int u = q.front(); q.pop();

    	for (int i = 0; i < n; i++){
    		if (a[i] > u && !vis[a[i] - u]){
    			vis[a[i] - u] = true;
    			q.push(a[i] - u);
    			par[a[i] - u] = i;
    		}
    	}
    }

    for (int i = 0; i < a[0]; i++){
    	if (vis[i]){
    		vector <int> ord;
    		while (par[i] != -2){
    			ord.push_back(par[i] + 1);
    			i = a[par[i]] - i;
    		}

    		reverse(ord.begin(), ord.end());
    		
    		for (int i = 1; i <= n; i++){
    			ord.push_back(i);
    		}
    // 		cout << 1 << "\n";
    // 		return;
    		cout << ord.size() << "\n";
    		for (auto x : ord){
    			cout << x << " ";
    		}
    		cout << "\n";
    		return;
    	}
    }

    cout << -1 << "\n";
}

int32_t main() 
{
    auto begin = std::chrono::high_resolution_clock::now();
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int t = 1;
    // freopen("in",  "r", stdin);
    // freopen("out", "w", stdout);
    
    cin >> t;
    for(int i = 1; i <= t; i++) 
    {
        //cout << "Case #" << i << ": ";
        Solve();
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
    cerr << "Time measured: " << elapsed.count() * 1e-9 << " seconds.\n"; 
    return 0;
}
Tester's code (C++)
#include <bits/stdc++.h>
using namespace std;

int main() {
  cin.tie(0)->sync_with_stdio(0);

  int t; cin >> t; while (t--) {
    int n, w;
    cin >> n >> w;
    vector<int> a(n);
    for (auto& ai : a) {
      cin >> ai;
    }

    int mn = w;
    vector<int> argmin, path;
    set<int> seen;

    auto dfs = [&](auto&& self, int w) -> void {
      seen.insert(w);
      if (mn > w) {
        mn = w;
        argmin.clear();
        copy(path.cbegin(), path.cend(), back_inserter(argmin));
      }

      for (int i = 0; i < n; ++i) {
        if (a[i] > w && !seen.count(a[i] - w)) {
          path.push_back(i);
          self(self, a[i] - w);
          path.pop_back();
        }
      }
    };

    dfs(dfs, w);

    if (mn >= a[0]) {
      cout << -1 << '\n';
    } else {
      cout << argmin.size() + 2 * n << '\n';
      for (const auto i : argmin) {
        cout << i + 1 << ' ';
      }
      for (int i = 1; i <= n; ++i) {
        cout << i << ' ' << i << ' ';
      }
      cout << '\n';
    }
  }
}
Editorialist's code (Python)
for _ in range(int(input())):
    n, w = map(int, input().split())
    a = list(map(int, input().split()))
    par = [0]*5005
    par[w] = n+1
    que = [w]
    for x in que:
        for i in reversed(range(n)):
            if a[i] <= x: break
            if par[a[i]-x] == 0:
                par[a[i]-x] = i+1
                que.append(a[i]-x)
    mn = min([i for i in range(5005) if par[i] > 0])
    
    if mn >= a[0]:
        print(-1)
        continue
    path = []
    x = mn
    while x != w:
        path.append(par[x])
        x = a[par[x]-1] - x
    print(len(path) + n)
    path = path[::-1]
    for i in range(n): path.append(i+1)
    print(*path)

I used debug tool in codechef, it showed me that it fails in testcase:
image
But isnt my output right here…

Initially weights: [0 , 1] w1 < w2
After first operation: [9 , 1] w2 > w1

These feature does not work where there can be multiple answers

1 Like

ok