treasure
 
% treasure.pi (in Picat)
% by N.F. Zhou, March 25, 2015
% https://code.google.com/codejam/contest/2270488/dashboard#s=p3&a=3 
% Qualification Round 2013
% Problem D. Treasure
%
% to use: picat treasure < input_file > output_file
%
% Keys held in hand and keys contained in each chest are represented as 
% ordered lists of pairs, where in each pair the first element is a type 
% number and the second is a quantity.

import planner.

main =>
    C = read_int(),
    foreach (Case_num in 1..C)
        K = read_int(), N = read_int(),
        KeyList = [read_int() : _ in 1..K].sort(),
        TypeList = KeyList.sort_remove_dups(),
        TypeQtPairs = [(Type,Qt) : Type in TypeList, 
                                   Qt = sum([1 : Type in KeyList])],
        read_chests(Chests,N),
        initialize_table,
        once do_case(Case_num, TypeQtPairs, Chests)
    end.

read_chests(Chests,0) => Chests = [].
read_chests(Chests,I) => 
    Ti = read_int(),
    Ki = read_int(),
    KeyList = [read_int() : _ in 1..Ki].sort(),
    TypeList = KeyList.sort_remove_dups(),
    TypeQtPairs = [(Type,Qt) : Type in TypeList, 
                               Qt = sum([1 : Type in KeyList])],
    Chests = [$chest(Ti,TypeQtPairs)|Chests1],
    read_chests(Chests1,I-1).

do_case(Case_num,Keys,Chests),
    plan_unbounded({Keys,Chests},Plan)
=>
    chest_to_index(Plan,Chests,[],Is),
    printf("Case #%w: ", Case_num),
    foreach (I in Is)
        printf("%w ", I)
    end,
    nl.
do_case(Case_num,_Keys,_Chests) =>
    printf("Case #%w: IMPOSSIBLE\n", Case_num).

chest_to_index([],_Chests,Is0,Is) => Is = reverse(Is0).
chest_to_index([Chest|Plan],Chests,Is0,Is) => 
    nth(I,Chests,Chest),
    not member(I,Is0),
    chest_to_index(Plan,Chests,[I|Is0],Is).

final({_,[]}) => true.    % done when all chests have been opened

action({Keys,Chests},NewS,Action,Cost) =>
    Action = Chest, Cost = 1,
    select(Chest,Chests,ChestsRest),      % select a chest to open
    Chest = $chest(KeyType,ChestKeys),
    select((KeyType,Qt),Keys,KeysRest),   % select a key to open the chest
    NewQt = Qt-1,
    (NewQt == 0 ->
        KeysRest1 = KeysRest
    ;
        KeysRest1 = insert_ordered(KeysRest,(KeyType,NewQt))
    ),
    add_keys(KeysRest1,ChestKeys,NewKeys),
    possible(NewKeys,ChestsRest),
    NewS = {NewKeys,ChestsRest}.

%% merge two sets (ordered lists)
add_keys(Keys1,[],NewKeys) => NewKeys = Keys1.
add_keys([],Keys2,NewKeys) => NewKeys = Keys2.
add_keys([(Type,Qt1)|Keys1R],[(Type,Qt2)|Keys2R],NewKeys) =>
    NewKeys = [(Type,Qt1+Qt2)|NewKeysR],
    add_keys(Keys1R,Keys2R,NewKeysR).
add_keys([Key1@(Type1,_)|Keys1R],Keys2@[(Type2,_)|_],NewKeys),Type1 < Type2 =>
    NewKeys = [Key1|NewKeysR],    
    add_keys(Keys1R,Keys2,NewKeysR).
add_keys(Keys1,[Key2|Keys2R],NewKeys) =>
    NewKeys = [Key2|NewKeysR],    
    add_keys(Keys1,Keys2R,NewKeysR).

% For each key type, the available quantity should not be less than 
% the needed quantity.It's possible to open a chest if there is a key 
% in hand that can open it or a key is inside another chest and that 
% chest is possible to be opened.
table
possible(_,[]) => true.
possible([],Chests) => fail.
possible(Keys,Chests) =>
    count_keys(Chests,NeededKeys),
    foreach((NeededType,NeededQt) in NeededKeys)
        sum([Qt : $chest(_,ChestKeys) in Chests, (NeededType,Qt) in ChestKeys]) + 
        sum([Qt : (NeededType,Qt) in Keys]) >= NeededQt
    end,
    foreach ($chest(KeyType,_) in Chests)
        possible(Keys,KeyType,Chests)
    end.

table 
count_keys(Chests,NeededKeys) =>
    Keys = [Key : $chest(Key,_) in Chests],
    KeyTypes = sort_remove_dups(Keys),
    NeededKeys = [(KeyType,Qt) : KeyType in KeyTypes, 
                                 Qt = sum([1 : KeyType in Keys])].

table
possible(Keys,KeyType,_Chests),
    member((KeyType,_),Keys)
=> 
    true.
possible(Keys,KeyType,Chests) =>
    member(Chest1,Chests),
    Chest1 = $chest(KeyType1,ChestKeys),
    once member((KeyType,_),ChestKeys),
    possible(Keys,KeyType1,Chests).