Problem:
Transparent Mahjong
F: 透明な麻雀牌
あなたは，闇に舞い降りた天災と噂されるギャンブラーの鷹巣さんと麻雀で対局することになった．
鷹巣さんは天災と噂されるだけあって，鷹巣牌という奇怪な麻雀牌を用いた対局を提案してきた．
麻雀とは，プレイヤーが互いに手牌という複数枚の牌を持ち合い，自分の手牌を相手よりもはやく，アガリの形にすることで勝利が確定するゲームである．
麻雀の牌の表側には，その牌が何であるかが描かれており，牌の裏側には，何も描かれていない．
よって，あなたが麻雀で対局するときは，あなたの手牌の表側をあなたの側に，裏側を対局者の側に向けるとよい．
こうすることで，あなたの手牌が何であるかは，あなたのみが知ることができる．
麻雀の醍醐味は，プレイヤーが互の手配を読み合い，それを楽しむことにある．
しかしながら，鷹巣牌は透明な麻雀牌である．
鷹巣牌は透明であるため，プレイヤーは互いに相手の手牌を知ることができる．
これでは，手配の読み合いを楽しむことができない．
「もはや，これは麻雀ではない．」と，鷹巣牌を用いることに納得できないあなたは，鷹巣さんとの紆余曲折を経た協議の結果，鷹巣牌と普通の麻雀牌を混ぜて対局することになった．
手始めに，あなたは，鷹巣さんの手牌のアガリ牌が何であるかを予想することにした．
鷹巣さんの手牌は，鷹巣牌と普通の牌から構成される．
問題の簡単のため，鷹巣牌と普通の牌は，1から12の12種類各4枚からなるものとする．
3
n
+2枚の牌からなる手牌について，次の3つの条件が満たされるとき，その手牌はアガリである．
同じ数字を組み合わせた牌の組が1つ存在する
残りの3
n
枚の牌は，3個の数字の組合せの
n
グループになる
それぞれのグループは，同じ数字を3つ組み合わせたものか，3つの連続する数字を組み合わせたものである
例えば，
1 1 1 3 3 3 5 5 5 7 7 7 8 9
のような14枚の牌は，全て鷹巣牌で構成されている．
この場合，次のように2つの牌の組合せ1つと4つのグループを作ることができ，アガリである．
(1 1 1) (3 3 3) (5 5 5) (7 7) (7 8 9)
次に，普通の牌と鷹巣牌が混ざった手牌の例を挙げる．
1 1 1 3 3 3 5 5 5 7 7 7 * *
のような14枚の牌は，12枚の鷹巣牌と2枚の普通の牌(*)で構成されている．
この場合，次のように2つの普通の牌が8と9であるかもしれないので，2つの牌の組合せ1つと4つのグループを作ることができ，アガリであるかもしれない．
(1 1 1) (3 3 3) (5 5 5) (7 7) (7 [8] [9])
同じ例について，次のように2つの普通の牌が8と8であるかもしれないので，以下のような形のアガリであるかもしれない．
(1 1 1) (3 3 3) (5 5 5) (7 7 7) ([8] [8])
しかし，7という同じ牌は4枚までしか使えないので，以下のような形のアガリであることはない．
(1 1 1) (3 3 3) (5 5 5) (7 7 7) ([7] [7])
鷹巣さんの3
n
+1枚の手牌が入力として与えられたときに，
鷹巣さんの手牌に1枚の牌を加えて，3
n
+2枚の手牌を作ることを考える．
このときに，3
n
+2枚の手牌がアガリになり得るような加える1枚の牌(アガリ牌)を全て求めよ．
Input
入力は，次の形式で与えられる．
n
牌
1
牌
2
...
牌
3n+1
入力データは次の条件を満たす
n
は，0 <=
n
<= 15を満たす．
各牌は，鷹巣牌か普通の牌である．鷹巣牌の場合は，1以上12以下の整数で表され，普通の牌の場合は，文字*で表される．
Output
アガリ牌の一覧を昇順に出力せよ．アガリ牌を1つ出力する度に改行を出力せよ．
あがり牌がない場合は，-1を1行に表示せよ．
Sample Input 1
4
1 1 1 3 3 3 5 5 5 7 7 7 9
Sample Output 1
8
9
Sample Input 2
4
1 2 2 3 3 4 5 6 7 8 8 8 9
Sample Output 2
1
4
7
9
10
Sample Input 3
4
1 1 1 4 4 4 7 7 7 8 8 9 *
Sample Output 3
6
7
8
9
10
11
Sample Input 4
4
1 * * * * * * * * * * * *
Sample Output 4
1
2
3
4
5
6
7
8
9
10
11
12
Sample Input 5
1
3 * 1 4
Sample Output 5
1
2
4
5

Submitted solutions:

code_snippet: // AOJ #2422 Transparent Mahjong
// 2025.4.2

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int n;
vector<int> orig(13);
unordered_map<string,bool> memo;

string enc(const vector<int>& f, int w, const vector<int>& a) {
    string s = to_string(w);
    for (int i = 1; i <= 12; i++) s += "," + to_string(f[i]) + "," + to_string(a[i]);
    return s;
}

bool dfs(vector<int>& f, int w, vector<int>& a) {
    int tot = w;
    for (int i = 1; i <= 12; i++) tot += f[i];
    if(tot == 0) return true;
    string key = enc(f,w,a);
    if(memo.count(key)) return memo[key];
    bool flag = false;
    bool all0 = true;
    for (int i = 1; i <= 12; i++) if(f[i] > 0){ all0 = false; break; }

    if(all0){
        if(w % 3 != 0) return memo[key] = false;
        int k = w / 3;
        int cnt = 0;
        for (int i = 1; i <= 12; i++) if(4 - orig[i] - a[i] >= 3) cnt++;
        return memo[key] = (cnt >= k);
    }

    int idx = 1;
    while(idx <= 12 && f[idx] == 0) idx++;
    if(idx > 12) return memo[key] = false;
    int need = max(0, 3 - f[idx]);
    if(need <= w && a[idx] + need <= 4 - orig[idx]) {
        int backup = f[idx];
        int used = min(f[idx], 3);
        f[idx] -= used;
        a[idx] += need;
        if(dfs(f, w - need, a)) { flag = true; }
        a[idx] -= need;
        f[idx] = backup;
        if(flag){ memo[key] = true; return true; }
    }
    if(idx <= 10) {
        int needTotal = 0;
        vector<int> needArr(13,0);
        bool poss = true;
        for (int j = idx; j <= idx+2; j++){
            if(f[j] >= 1) needArr[j] = 0;
            else          needArr[j] = 1;
            if(a[j] + needArr[j] > 4 - orig[j]) poss = false;
            needTotal += needArr[j];
        }
        if(poss && needTotal <= w) {
            vector<int> back(13);
            for (int j = idx; j <= idx+2; j++){
                back[j] = f[j];
                if(f[j] >= 1) f[j]--;
                a[j] += needArr[j];
            }
            if(dfs(f, w - needTotal, a)) { flag = true; }
            for (int j = idx; j <= idx+2; j++){
                a[j] -= needArr[j];
                f[j] = back[j];
            }
            if(flag){ memo[key] = true; return true; }
        }
    }
    memo[key] = flag;
    return flag;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n;
    int total = 3*n + 1;
    vector<string> in(total);
    vector<int> cnt(13, 0);
    int wc = 0;
    for (int i = 0; i < total; i++){
        cin >> in[i];
        if(in[i]=="*") wc++;
        else cnt[stoi(in[i])]++;
    }

    vector<int> ans;
    for (int x = 1; x <= 12; x++){
        if(cnt[x] == 4) continue;
        vector<int> c = cnt;
        c[x]++;
        int tot2 = wc;
        for (int i = 1; i <= 12; i++) tot2 += c[i];
        if(tot2 != 3*n + 2) continue;
        bool ok = false;
        for (int p = 1; p <= 12; p++){
            int need = max(0, 2 - c[p]);
            if(need > wc) continue;
            if(c[p] + need > 4) continue;
            vector<int> f(13,0), a(13,0);
            for (int i = 1; i <= 12; i++) f[i] = c[i];
            int use = min(f[p], 2);
            f[p] -= use;
            a[p] = need;
            int wleft = wc - need;
            orig = f;
            memo.clear();
            if(dfs(f, wleft, a)){ ok = true; break; }
        }
        if(ok) ans.push_back(x);
    }
    if(ans.empty()) cout << -1 << endl;
    else {
        sort(ans.begin(), ans.end());
        for (auto v: ans) cout << v << endl;
    }
    return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 3576
final_rank: 4

code_snippet: #include<cstdio>
#include<cstring>
#include<algorithm>

#define rep(i,n) for(int i=0;i<(n);i++)

using namespace std;

int n,star;
int cnt[12]; // 数字 i が書かれた牌はあと i 個
int rem[12]; // * が i に成れるのは rem[i] 個まで

bool dfs(int i){
	if(i==12) return star==0;

	if(cnt[i]==0){
		if(dfs(i+1)) return true;
	}

	// i, i, i
	if(cnt[i]+min(star,rem[i])>=3){
		int tmp=min(cnt[i],3);
		cnt[i]-=tmp;
		star-=3-tmp;
		rem[i]-=3-tmp;
		bool res=dfs(i);
		cnt[i]+=tmp;
		star+=3-tmp;
		rem[i]+=3-tmp;
		if(res) return true;
	}

	// i, i+1, i+2
	if(i<=9){
		bool b=true; // i, i+1, i+2 の割り当てが可能かどうか
		int minus=0;
		rep(j,3){
			if(cnt[i+j]==0 && min(star-minus,rem[i+j])==0) b=false;
			if(cnt[i+j]==0) minus++;
		}
		if(b){
			bool used[3]={};
			rep(j,3){
				if(cnt[i+j]>0) cnt[i+j]--, used[j]=true;
				else           star--, rem[i+j]--;
			}
			bool res=dfs(i);
			rep(j,3){
				if(used[j]) cnt[i+j]++;
				else        star++, rem[i+j]++;
			}
			if(res) return true;
		}
	}

	return false;
}

int main(){
	scanf("%d",&n);
	int hai[47],star=0;
	rep(i,3*n+1){
		char s[4]; scanf("%s",s);
		if(s[0]=='*'){
			hai[i]=-1;
			star++;
		}
		else{
			hai[i]=atoi(s)-1;
			cnt[hai[i]]++;
		}
	}

	bool none=true;
	rep(a,12) if(cnt[a]<4) { // 3*n+2 枚目の牌
		hai[3*n+1]=a;
		cnt[a]++;

		rep(i,12) rem[i]=4-cnt[i];

		bool ok=false;
		rep(b,12) if(cnt[b]+star>=2) { // 最後の二つの牌
			int tmp1[47],tmp2[12],tmp3[12];
			memcpy(tmp1,hai,sizeof hai);
			memcpy(tmp2,cnt,sizeof cnt);
			memcpy(tmp3,rem,sizeof rem);

			::star=star;
			int m=3*n+2,two=2;
			rep(i,m) if(two>0 && hai[i]== b) swap(hai[i],hai[m-1]), i--, m--, two--, cnt[b]--;
			rep(i,m) if(two>0 && hai[i]==-1) swap(hai[i],hai[m-1]), i--, m--, two--, rem[b]--, ::star--;
			ok=dfs(0);

			memcpy(hai,tmp1,sizeof hai);
			memcpy(cnt,tmp2,sizeof cnt);
			memcpy(rem,tmp3,sizeof rem);

			if(ok) break;
		}

		if(ok) printf("%d\n",a+1), none=false;

		cnt[a]--;
	}
	if(none) puts("-1");

	return 0;
}
accuracy: 1
time_ms: 60
memory_kb: 1028
final_rank: 1

code_snippet: #include<cstdio>
#include<cassert>
#include<cstring>
#include<algorithm>

#define rep(i,n) for(int i=0;i<(n);i++)

using namespace std;

int n,star;
int cnt[12]; // 数字 i が書かれた牌はあと i 個
int rem[12]; // * が i に成れるのは rem[i] 個まで

bool dfs(int i){
	if(i==12) return star==0;

	if(cnt[i]==0){
		if(dfs(i+1)) return true;
	}

	// i, i, i
	if(cnt[i]+min(star,rem[i])>=3){
		int tmp=min(cnt[i],3);
		cnt[i]-=tmp;
		star-=3-tmp;
		rem[i]-=3-tmp;
		assert(cnt[i]>=0);
		assert(star>=0);
		assert(rem[i]>=0);
		bool res=dfs(i);
		cnt[i]+=tmp;
		star+=3-tmp;
		rem[i]+=3-tmp;
		if(res) return true;
	}

	// i, i+1, i+2
	if(i<=9){
		bool b=true; // i, i+1, i+2 の割り当てが可能かどうか
		int minus=0;
		rep(j,3){
			if(cnt[i+j]==0 && min(star-minus,rem[i+j])==0) b=false;
			if(cnt[i+j]==0) minus++;
		}
		if(b){
			bool used[3]={};
			rep(j,3){
				if(cnt[i+j]>0) cnt[i+j]--, used[j]=true;
				else           star--, rem[i+j]--;
				assert(cnt[i+j]>=0);
				assert(star>=0);
				assert(rem[i+j]>=0);
			}
			bool res=dfs(i);
			rep(j,3){
				if(used[j]) cnt[i+j]++;
				else        star++, rem[i+j]++;
			}
			if(res) return true;
		}
	}

	return false;
}

int main(){
	scanf("%d",&n);
	int hai[47],star=0;
	rep(i,3*n+1){
		char s[4]; scanf("%s",s);
		if(s[0]=='*'){
			hai[i]=-1;
			star++;
		}
		else{
			hai[i]=atoi(s)-1;
			cnt[hai[i]]++;
		}
	}

	bool none=true;
	rep(a,12) if(cnt[a]<4) { // 3*n+2 枚目の牌
		hai[3*n+1]=a;
		cnt[a]++;

		rep(i,12) rem[i]=4-cnt[i];

		bool ok=false;
		rep(b,12) if(cnt[b]+star>=2) { // 最後の二つの牌
			int tmp1[47],tmp2[12],tmp3[12];
			memcpy(tmp1,hai,sizeof hai);
			memcpy(tmp2,cnt,sizeof cnt);
			memcpy(tmp3,rem,sizeof rem);

			::star=star;
			int m=3*n+2,two=2;
			rep(i,m) if(two>0 && hai[i]== b) swap(hai[i],hai[m-1]), i--, m--, two--, cnt[b]--;
			rep(i,m) if(two>0 && hai[i]==-1) swap(hai[i],hai[m-1]), i--, m--, two--, rem[b]--, ::star--;
			assert(two==0);
			ok=dfs(0);

			memcpy(hai,tmp1,sizeof hai);
			memcpy(cnt,tmp2,sizeof cnt);
			memcpy(rem,tmp3,sizeof rem);

			if(ok) break;
		}

		if(ok) printf("%d\n",a+1), none=false;

		cnt[a]--;
	}
	if(none) puts("-1");

	return 0;
}
accuracy: 1
time_ms: 60
memory_kb: 1028
final_rank: 1

code_snippet: #include <cstdio>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <bitset>
#include <numeric>
#include <climits>
#include <cfloat>
using namespace std;

vector<int> a(12, 4);
vector<int> washizu(12, 0);
vector<bool> ret(12, false);

void solve(int i, int n)
{
    if(n == 0){
        vector<int> select(12, 0);
        for(int j=0; j<12; ++j)
            select[j] = 4 - a[j];

        for(int j=0; j<12; ++j){ // 対子
            if(a[j] < 2)
                continue;

            select[j] += 2;

            bool ok = true;
            for(int k=0; k<12; ++k){
                if(select[k] < washizu[k])
                    ok = false;
            }

            if(ok){
                for(int k=0; k<12; ++k){
                    if(select[k] > washizu[k])
                        ret[k] = true;
                }
            }

            select[j] -= 2;
        }
        return;
    }

    if(i < 12){ // 刻子
        if(a[i] >= 3){
            a[i] -= 3;
            solve(i+1, n-1);
            a[i] += 3;
        }
        solve(i+1, n);
    }else if(i < 22){ // 順子
        int j = i - 12;
        if(a[j] > 0 && a[j+1] > 0 && a[j+2] > 0){
            -- a[j];
            -- a[j+1];
            -- a[j+2];
            solve(i, n-1);
            ++ a[j+2];
            ++ a[j+1];
            ++ a[j];
        }
        solve(i+1, n);
    }
}

int main()
{
    int n;
    cin >> n;

    for(int i=0; i<3*n+1; ++i){
        string s;
        cin >> s;
        if(s != "*"){
            int x = atoi(s.c_str());
            -- x;
            ++ washizu[x];
        }
    }

    solve(0, n);

    bool ng = true;
    for(int i=0; i<12; ++i){
        if(ret[i]){
            cout << (i + 1) << endl;
            ng = false;
        }
    }
    if(ng)
        cout << -1 << endl;

    return 0;
}
accuracy: 1
time_ms: 110
memory_kb: 1220
final_rank: 3

code_snippet: #include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>

using namespace std;

#define FOR(i,k,n) for(int i=(k); i<(int)n; ++i)
#define REP(i,n) FOR(i,0,n)
#define FORIT(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();++i)

template<class T> void debug(T begin, T end){ for(T i = begin; i != end; ++i) cout<<*i<<" "; cout<<endl; }

typedef long long ll;
const int INF = 100000000;
const double EPS = 1e-8;
const int MOD = 1000000007;
int n, m;
int cnt[13];
int cn[13]; //tmpcnt
int rest[13]; //rest of use
int usenum; //use number
int S; // bit
typedef pair<int, int> P;
typedef pair<P, int> PP;
set<PP> memo;
bool dfs(int idx){
  if(memo.count(make_pair(make_pair(idx, usenum), cn[12]))) return false;
  memo.insert(make_pair(make_pair(idx, usenum), cn[12]));
  if(usenum >= n) return true;
  if(idx > 11) return false;
  if(S & (1 << usenum)){
    //111
    for(int i = 0; i <= 3; i++){
      if(cn[idx] < i) break;
      if(rest[idx] < 3 - i || cn[12] < 3 - i) continue;
      usenum ++;
      cn[idx] -= i;
      rest[idx] -= 3 - i;
      cn[12] -= 3 - i;
      if(dfs(idx)) return true;
      usenum --;
      cn[idx] += i;
      rest[idx] += 3 - i;
      cn[12] += 3 - i;
    }
    if(cn[idx] > 0) return false;
    return dfs(idx + 1);
  }else{
    //123
    if(idx + 2 > 11) return false;
    for(int S2 = 0; S2 < 1<<3; S2++){
      bool ok = true;
      if(__builtin_popcount(S2) > cn[12]) ok = false;
      for(int i = 0; i <= 2; i++){
        if(S2 & (1 << i)){
          if(rest[idx+i] == 0) ok = false;
        }else{
          // not use
          if(cn[idx+i] == 0) ok = false;
        }
      }
      if(!ok) continue;
      usenum ++;
      for(int i = 0; i <= 2; i++){
        if(S2 & (1 << i)){
          rest[idx+i] -= 1;
          cn[12] -= 1;
        }else{
          // not use
          cn[idx+i] -= 1;
        }
      }
      assert(cn[12] >= 0);
      if(dfs(idx)) return true;
      usenum --;
      for(int i = 0; i <= 2; i++){
        if(S2 & (1 << i)){
          rest[idx+i] += 1;
          cn[12] += 1;
        }else{
          // not use
          cn[idx+i] += 1;
        }
      }
    }
    if(cn[idx] > 0) return false;
    return dfs(idx + 1);
  }
  return false;
}
bool ok(){
  /*
  for(int i = 0; i < 13; i++){
    printf("%d:%d\n", i, cnt[i]);
  }
  */
  for(int two = 0; two < 12; two++){
    REP(i, 13)cn[i] = cnt[i];
    REP(i, 12)rest[i] = 4 - cnt[i];
    if(cn[two] + cn[12] >= 2){
      int r = min(cn[two], 2);
      if(rest[two] >= 2 - r){
        cn[two] -= r;
        cn[12] -= 2 - r;
        rest[two] -= 2 - r;
      }
    }
    else continue;
    /*
    for(int i = 0; i < 13; i++){
      printf("%d:%d\n", i, cn[i]);
    }
    */
    REP(tS, 1<<n){
      usenum = 0;
      S = tS;
      memo.clear();
      if(dfs(0)) return true;
    }
  }
  return false;
}

int main(){
  while(cin>>n){
    memset(cnt, 0, sizeof(cnt));
    m = 3 * n + 2;
    REP(i, m - 1){
      string s; cin>>s;
      if(s == "*"){
        cnt[12]++;
        continue;
      }
      stringstream ss(s);
      int n;
      ss>>n;
      cnt[n-1]++;
    }
    int asss = 0;
    REP(i, 12){
      assert(cnt[i] <= 4);
      if(cnt[i] == 4) continue;
      cnt[i]++;
      if(ok()) {
        cout<<i+1<<endl;
        asss++;
      }
      cnt[i]--;
    }
    if(asss == 0) cout<<-1<<endl;
  }
  return 0;
}
accuracy: 1
time_ms: 140
memory_kb: 1248
final_rank: 5