XXI Open Cup, Grand Prix of Samara

链接

A B C D E F G H I J K L M N
$\color{red}{\texttt{-}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{red}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{green}{\texttt{+}}$ $\color{gray}{\texttt{+}}$ $\color{green}{\texttt{-}}$ $\color{green}{\texttt{+}}$

B. Fakes and Shidget

G. Lexicographically Minimal Subsequence

签到题。

D. Two Pirates - 2

首先最优策略显然是每次取最大的。问题在于如何求出第 $i$ 大的值被取走的概率。

考虑将问题转化成两人在给球染色,每次一方随机将球染白,另一方将最右边没有染色的球蓝黑。

正难则反,考虑倒过来处理,即白方随机插入一个白球,黑方往最右侧插入,这样可以 dp 求出每个位置是黑球的概率 $f_i$。

然后将原序列排序,求 $\sum f_ia_i$ 即可。复杂度 $O(n^2)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
#define N 800010
using namespace std;
struct node{
int l,r;
}p[N];
int lf[N],pre[N];
bool vis[N];
vector<int>ans,g[N];
int main()
{
int n,m=0;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&p[i].l,&p[i].r),p[i].l++,p[i].r++,m=max(m,p[i].r+1);
for(int i=1;i<=n;i++) lf[p[i].r+1]=max(lf[p[i].r+1],p[i].l),g[p[i].l].push_back(i);
for(int i=1;i<=m;i++) lf[i]=max(lf[i],lf[i-1]);
queue<int>q,res;res.push(0);
for(int i=1;i<=m;i++)
{
for(int v:g[i]) q.push(v);while(!q.empty() && p[q.front()].r<i) q.pop();
while(!res.empty() && res.front()<lf[i]) res.pop();
if(!q.empty() && !res.empty() && res.front()<p[q.front()].l) pre[i]=res.front(),res.push(i);
if(res.empty()){puts("-1");return 0;}
}
for(int u=res.front();u;u=pre[u]) ans.push_back(u-1);
printf("%d\n",(int)ans.size());
for(int v:ans) printf("%d ",v);
return 0;
}

L. Not the Longest Increasing Subsequence

和毛营的 Maximal Subsequence 很像。由于值域为 $k$,故最长上升子序列至多为 $k$,容易证明答案等于最多选取多少个互不相交的最长上升子序列。

用类似分层图的做法处理即可。复杂度 $O(n\log n)$。

F. Exactly One Point

考虑 dp。设 $i\in S$ 当且仅当存在一种合法方案,最后一个选取的点为 $i$。考虑如果 $i$ 合法要求存在一个 $j<i$,使得不存在一个区间同时包含 $i,j$,也不存在一个区间 $[l,r]$ 满足 $j<l,r<i$。换句话说,$j$ 必须在所有右端点不大于 $i$ 的区间左端点最大的位置的右边。

容易发现这个限制单调不降,直接用一个队列维护即可。复杂度 $O(n)$

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
#define N 800010
using namespace std;
struct node{
int l,r;
}p[N];
int lf[N],pre[N];
bool vis[N];
vector<int>ans,g[N];
int main()
{
int n,m=0;scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&p[i].l,&p[i].r),p[i].l++,p[i].r++,m=max(m,p[i].r+1);
for(int i=1;i<=n;i++) lf[p[i].r+1]=max(lf[p[i].r+1],p[i].l),g[p[i].l].push_back(i);
for(int i=1;i<=m;i++) lf[i]=max(lf[i],lf[i-1]);
queue<int>q,res;res.push(0);
for(int i=1;i<=m;i++)
{
for(int v:g[i]) q.push(v);while(!q.empty() && p[q.front()].r<i) q.pop();
while(!res.empty() && res.front()<lf[i]) res.pop();
if(!q.empty() && !res.empty() && res.front()<p[q.front()].l) pre[i]=res.front(),res.push(i);
if(res.empty()){puts("-1");return 0;}
}
for(int u=res.front();u;u=pre[u]) ans.push_back(u-1);
printf("%d\n",(int)ans.size());
for(int v:ans) printf("%d ",v);
return 0;
}

J. Lost Island

经典题。结论是:如果颜色为 $i$ 的人被告知 $b_i\neq 0$,那么颜色为 $i$ 的人会在第 $a_i-b_i$ 天后自尽。特别的,由于所有人知道颜色数,如果最后只剩下一种颜色的人,那么会在第二天全部自尽。

复杂度 $O(n)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 200010
#define ll long long
using namespace std;
int a[N],b[N];
int main()
{
int n,m=0,mx=0,mx2=0;ll s=0,all=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]),all+=a[i];
for(int i=1;i<=n;i++) if(b[i])
{
++m,s+=a[i];
if(mx<a[i]-b[i]+1) mx2=mx,mx=a[i]-b[i]+1;
else if(mx2<a[i]-b[i]+1) mx2=a[i]-b[i]+1;
}
if(m==n-1) mx++,s=all;
if(m==n) mx=min(mx,mx2+1);
printf("%d %lld\n",mx,s);
return 0;
}

C. Cyclically Shifted Maze

先枚举一维,然后对于另一维,相当于每次循环位移一格,要求判断是否连通。

考虑对于一个网格,我们枚举一条竖线将其划成两块,对于每一块我们单独判断它的连通性。容易发现,如果某一块中存在一个点与两块相邻的边界处不连通,那么两块合并后一定也不连通。如果不存在这样的格子,那么我们只需要保存相邻边界处的连通性,然后合并相邻处的 $O(n)$ 个点,判断合并后是否连通即可。

这样对于原题,我们预处理只保留某个后缀情况下右边界的连通性 $G_i$,再预处理只保留某个前缀情况下左边界的连通性 $F_i$。假设循环位移后边界变成了 $j$,我们只需要合并 $F_{j-1}$ 与 $G_j$ 即可。

复杂度 $O(n^3\log n)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=210;
char s[N][N];bool mp[N][N],ban[N],vis[N*N];
int n,m,f[N*N],g[N][N],h[N];
int find(int x){return x==f[x]?f[x]:(f[x]=find(f[x]));}
int find(int x,int f[]){return x==f[x]?f[x]:(f[x]=find(f[x],f));}
int id(int x,int y){return x*m+y+1;}
void merge(int x1,int y1,int x2,int y2){f[find(id(x1,y1))]=find(id(x2,y2));}
vector<pair<int,int>>ans;
void get(int j,int g[])
{
static int vis[N*N];
for(int i=0;i<n;i++) if(mp[i][j]) vis[find(id(i,j))]=i;
for(int i=0;i<n;i++) if(mp[i][j]) g[i]=vis[find(id(i,j))]; else g[i]=-1;
for(int i=0;i<n;i++) if(mp[i][j]) vis[find(id(i,j))]=0;
}
bool check(int j,int k)
{
for(int i=0;i<n;i++) vis[find(id(i,k))]=true;
bool hv=true;
for(int i=0;i<n && hv;i++) if(mp[i][j] && !vis[find(id(i,j))]) hv=false;
for(int i=0;i<n;i++) vis[find(id(i,k))]=false;
return hv;
}
bool check(int f[],int g[])
{
static int tmp[N*2];
int rt=0;
for(int i=1;i<=n*2;i++) tmp[i]=0;
for(int i=0;i<n;i++) if(f[i]!=-1) tmp[i+1]=f[i]+1;
for(int i=0;i<n;i++) if(g[i]!=-1) tmp[i+n+1]=g[i]+n+1;
for(int i=1;i<=n;i++) if(tmp[i] && tmp[i+n]) tmp[find(i,tmp)]=find(i+n,tmp);
for(int i=1;i<=n*2;i++) if(tmp[i]){rt=find(i,tmp);break;}
for(int i=1;i<=n*2;i++) if(tmp[i] && find(i,tmp)!=rt) return false;
return true;
}
int st;
void init()
{
static char t[N][N];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++) if(s[i][j]=='.') st=j+1;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++) t[i][(j-st+m)%m]=s[i][j];
for(int i=0;i<n;i++)
for(int j=0;j<m;j++) s[i][j]=t[i][j];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%s",s[i]);
init();
for(int t=0;t<n;t++)
{
for(int i=0;i<n;i++) for(int j=0;j<m;j++) mp[i][j]=s[(i+t)%n][j]=='.';
for(int i=1;i<=n*m;i++) f[i]=i;
for(int j=m-1,p=m-1;j>=0;j--)
{
for(int i=0;i<n;i++) if(mp[i][j])
{
if(mp[i][j+1]) merge(i,j,i,j+1);
if(mp[i+1][j]) merge(i,j,i+1,j);
}
for(;p>=j;p--) if(!check(p,m-1)){ban[j]=true;break;}
if(!ban[j]) get(m-1,g[j]);
}
if(!ban[0])
{
int pre=-1;
bool hv=true;for(int i=0;i<n;i++) if(g[0][i]!=-1)
{
if(pre==-1) pre=g[0][i];
else if(g[0][i]!=pre){hv=false;break;}
}
if(hv) ans.emplace_back(t,0);
}
for(int i=1;i<=n*m;i++) f[i]=i;
for(int j=0,p=0;j<m-1;j++)
{
for(int i=0;i<n;i++) if(mp[i][j])
{
if(j && mp[i][j-1]) merge(i,j,i,j-1);
if(mp[i+1][j]) merge(i,j,i+1,j);
}
get(0,h);
bool bn=false;
for(;p<=j;p++) if(!check(p,0)){bn=true;break;}
if(!bn && !ban[j+1] && check(g[j+1],h)) ans.emplace_back(t,j+1);
}
for(int i=0;i<m;i++) ban[i]=0;
}
for(auto &u:ans) u.second=(u.second+st)%m;
sort(ans.begin(),ans.end());
printf("%d\n",(int)ans.size());
for(auto u:ans) printf("%d %d\n",u.first,u.second);
return 0;
}

E. Powerless Mage

考虑第一维排序扫描线,这样变成一个平面问题:每次加入一个点,询问所有不偏序任何一个集合中点的点中两坐标之和最大的点。

容易发现真正产生限制的点是一条折线。考虑维护这条折线,显然折线上的点 $x$ 坐标单增,$y$ 坐标单减,那么每次插入一个点就大力将无效的点弹出,然后用 STL 维护折线的所有上端点的两坐标之和即可。

注意特判 $\inf$ 的情况。复杂度 $O(n\log n)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#include<queue>
#include<set>
#define fi first
#define se second
using namespace std;
const int N=200010;
typedef long long ll;
const ll inf=1000000000000;
struct node{
ll x,y;
node(ll x=0,ll y=0):x(x),y(y){}
bool operator <(const node a)const{return x==a.x?y<a.y:x<a.x;}
};
bool sub(node a,node b){return a.x<=b.x && a.y<=b.y;}
vector<node>p[N];
struct pq{
priority_queue<ll>q,d;
void ins(ll x){q.push(x);}
void del(ll x){d.push(x);}
ll top(){while(!d.empty() && q.top()==d.top()) q.pop(),d.pop();return q.top();}
}res;
set<node>s;
int n,m;ll pz[N];
void add(node a)
{
auto p=s.insert(a).first;
if(p!=s.begin()) res.ins(a.x+prev(p)->y);
if(next(p)!=s.end()) res.ins(a.y+next(p)->x);
}
void del(set<node>::iterator l,set<node>::iterator r)
{
if(r!=s.end()) ++r;
for(auto p=l;p!=r;p++)
if(p!=s.begin()) res.del(p->x+prev(p)->y);
}
void ins(node a)
{
auto r=s.lower_bound(a),l=r;
if(r!=s.begin() && sub(*prev(r),a)) return;
while(r!=s.end() && sub(a,*r)) ++r;
del(l,r),s.erase(l,r);
add(a);
}
void init()
{
static int x[N],y[N],z[N];
for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i],&y[i],&z[i]),pz[i]=z[i];
sort(pz+1,pz+n+1);
m=unique(pz+1,pz+n+1)-pz-1;
pz[m+1]=inf;
if(pz[1]!=0){puts("Infinity");exit(0);}
for(int i=1;i<=n;i++) p[lower_bound(pz+1,pz+m+1,z[i])-pz].push_back({x[i],y[i]});
}
int main()
{
scanf("%d",&n);
init();
add({0,inf});add({inf,0});
ll ans=0;
for(int i=1;i<=m;i++)
{
for(auto v:p[i])
{
if(!v.x && !v.y){if(ans>=1e10) puts("Infinity");else printf("%lld\n",ans);return 0;}
ins(v);
}
ans=max(ans,pz[i+1]-1+res.top()-2);
}
puts("Infinity");
return 0;
}

K. Bloodseeker

首先把奖励对 $m$ 取 $\min$,那么总有一种方案使这些奖励完全加到血量里。

这样题目可以抽象成:你一开始有 $m$ 的钱,有 $n$ 个物品,买第 $i$ 个需要 $t_i$ 的钱,然后会有 $h_i$ 的收益。你需要时刻保证钱数为正,问能否买完所有物品。

这是一个经典题。把所有物品按收益正负排序。显然先买收益为正的,在收益为正的部分中显然容易买的($t_i$ 较小)先买。然后对于收益为负的,可以将其反过来看,即从最终状态不断卖这些物品。所以应该先买 $h_i$ 大的。直接按这个顺序排序然后贪心取即可。

复杂度 $O(n\log n)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 200010
using namespace std;
struct node{
int t,h;
}a[N];
void solve()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].t,&a[i].h),a[i].h=min(a[i].h,m);
sort(a+1,a+n+1,[&](node a,node b){
if((a.h-a.t>0)^(b.h-b.t>0)) return a.h-a.t>0;
if(a.h-a.t>0) return a.t<b.t;
else return a.h>b.h;
});
long long s=m;
for(int i=1;i<=n;i++) if(a[i].t>s){puts("NO");return;}
else s-=a[i].t,s+=a[i].h;
puts("YES");
}
int main()
{
int t;scanf("%d",&t);
while(t --> 0) solve();
return 0;
}

H. Video Reviews - 2

首先显然有一个二分答案然后贪心的做法,显然也过不去。

考虑倒过来做。假设我们最后已经获得了 $m$ 条评论,那么贪心找到前面第一个 $a_i<m$ 的位置,说明如果前面获得了 $m-1$ 的评论,那么之后就会获得 $m$ 条评论。

特别的,如果前面只剩下了 $m$ 个人,而当前人 $a_i\geq m$,那么我们不得不花费 $1$ 代价说服他。

这里空间只有 $64\texttt{MB}$,不过由于题目规定 $z_i$ 必须是质数,所以我们可以先顺序求出每个序列最后一个位置,然后倒过来用逆元推出上一个值。

复杂度 $O(n+k\log n)$。

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
int c[N],x[N],y[N],z[N],f[N];
int ksm(int a,int b,int mod)
{
int r=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
return r;
}
int ans=0;
int main()
{
int n,m,a,k;
scanf("%d%d%d%d",&n,&m,&a,&k);
f[0]=a;
for(int i=1;i<=k;i++)
{
scanf("%d%d%d%d",&c[i],&x[i],&y[i],&z[i]);
for(int j=0;j<c[i];j++) a=(1ll*a*x[i]+y[i])%z[i];
f[i]=a;
x[i]=ksm(x[i],z[i]-2,z[i]);
}
for(int i=k;i;i--)
{
a=f[i];
for(int t=0;t<c[i];t++,n--)
{
if(a<m) m--;
else if(n<=m) ans++,m--;
a=1ll*(a-y[i]+z[i])*x[i]%z[i];
}
}
if(m && a>=m) ans++;
printf("%d\n",ans);
return 0;
}

A. Absenteeis

首先,如果两个人的区间 $[l,r],[l’,r’]$ 满足 $[l’,r’]\subseteq[l,r]$,那么 $[l’,r’]$ 就没有用了:显然监视区间越长越容易抓你的车。

如果没有人的区间 $[l,r]$ 满足 $l\leq k,r\geq m-k$,那么你完全可以不工作,因为没有人可以确定你有没有工作完 $k$ 时间。反之,如果有这样的人,那么你的左端点必须 $\leq m-k$,右端点必须 $\geq k$,否则他就可以确认你工作时长 $\leq k$。

同时,如果你的工作区间 $[x,y]$ 被某个人 $[l,r]$ 包含了,那么你必须工作至少 $k$ 小时,这显然是一个 trivial 的情况。所以可以假设最优区间不被任何区间包含。

由于我们已经将被包含的区间去掉了,所以剩下的区间左右端点均单调。

N. Premove Checkmate

大致思路:

  • 注意到虽然后很牛逼,但要将死对方本质上只有两种方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ?...
    .Q..
    ..K.
    ....



    ?..Q
    ....
    .K..
    ....

    下面的位置其实大概率是不合法的,因为王可以左右移到,大概率后会在王的攻击范围内。所以一般采用第一种方式。

  • 如果对方可行空间只有 $1\times 6$,可以使用下述方式将死:

    1
    2
    3
    4
    5
    6
    7
    ??????..     ?????...
    .......Q → ......Q.
    ......K. ......K.

    ??...... ??......
    .Q...... ← ...Q....
    ..K..... ..K.....
  • 首先,将王移到 f3,然后后移到 c5,这样对方的可能位置被分成一个 $3\times 5$ 的矩形和额外点 h3。然后移到 f4,将额外点分开。将后移到 h4,这样可以将后从两个状态区分开。
    额外点的可行空间只有不到 $1\times 6$,直接用上面做法干掉即可。

  • 然后现在变成了 $3\times 5$ 的网格:

    1
    2
    3
    4
    ...?.???
    .K..????
    ...?????
    .Q......

    此时王往右冲会分成两部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ...?....
    .K......
    ...?....
    .Q......



    .....???
    ..K.????
    ....????
    .Q......

    将上面的 K 上移,此时由于对面王必须移动,这样就让出了第 $4$ 行,王再往右下:

    1
    2
    3
    4
    ........
    ..K.?...
    ....?...
    .Q......

    由于皇后和王的位置与另一局面相同,可以合并两者。将后向右移一格,局面变成了 $3\times 4$ 的格子。

    不断递归这个过程,当只剩下一行时用上面 $1\times 6$ 的方法解决即可。

模拟程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<set>
#include<fstream>
#include<map>
using namespace std;
#define QUEEN 3
#define KING 2
#define BLACK 1
#define NONE 0
#define fi first
#define se second
struct node{
int x,y;
node(int x=0,int y=0):x(x),y(y){}
};
node operator +(node a,node b){return node(a.x+b.x,a.y+b.y);}
node operator *(node a,int x){return node(a.x*x,a.y*x);}
node operator -(node a,node b){return node(a.x-b.x,a.y-b.y);}
bool operator ==(node a,node b){return a.x==b.x && a.y==b.y;}
bool operator !=(node a,node b){return a.x!=b.x || a.y!=b.y;}
bool operator <(node a,node b){return a.x==b.x?a.y<b.y:a.x<b.x;}
int len(node a){return max(abs(a.x),abs(a.y));}
struct Board{
node king,queen;set<node>black;
int operator [](node v)const{return v==king?KING:(v==queen?QUEEN:(black.count(v)?BLACK:NONE));}
void print()
{
for(int i=8;i;i--,puts(""))
{
putchar('0'+i);
for(int j=1;j<=8;j++)
if(node(i,j)==king) putchar('K');
else if(node(i,j)==queen) putchar('Q');
else if(black.count(node(i,j))) putchar('?');
else putchar('.');
}
putchar(' ');
for(int i=0;i<8;i++) putchar('a'+i);
puts("");
}
void init()
{
for(int i=5;i<=8;i++)
for(int j=5;j<=8;j++) black.insert(node(i,j));
king={3,3},queen={4,4};
}
};
bool valid_position(node a){return a.x>0 && a.x<=8 && a.y>0 && a.y<=8;}
const node P[]={{-1,0},{1,0},{0,-1},{0,1},{-1,-1},{-1,1},{1,-1},{1,1}};
int face(node a)
{
if(a.y==0) return a.x<0?0:1;
if(a.x==0) return a.y<0?2:3;
if(a.x==a.y) return a.x<0?4:7;
if(a.x==-a.y) return a.x<0?5:6;
return -1;
}
int valid_move(const Board &a,node X,node Y)
{
if(a[Y]==KING || a[Y]==QUEEN) return 0;
if(a[X]==KING)
{
for(int i=0;i<8;i++) if(a.black.count(Y+P[i])) return -1;
if(len(Y-X)!=1) return 0;
return 1;
}
int x=face(Y-X),y=face(a.king-X),z=face(a.king-Y);
if(x==-1) return 0;
return x!=y || (x==y && y==z);
}
int Move(Board &a)//Black's move
{
set<node>valid;
static int tmp[9][9];
for(int i=1;i<=8;i++) for(int j=1;j<=8;j++) tmp[i][j]=1;
auto vis=[&](node a)->int&{return tmp[a.x][a.y];};
for(int i=0;i<8;i++) vis(a.king+P[i])=0;
for(int j=0;j<8;j++)
for(int i=1;i<8;i++) if(valid_position(a.queen+P[j]*i))
{
vis(a.queen+P[j]*i)=0;
if(a.queen+P[j]*i==a.king) break;
}
for(auto v:a.black)
{
bool spot=vis(v),can_move=false;
for(int i=0;i<8;i++)
if(valid_position(v+P[i]) && vis(v+P[i]))
{
if(a.queen==v+P[i]) return 2;//Queen was eaten
can_move=true;
valid.insert(v+P[i]);
}
if(spot && !can_move) return 3;//Draw
}
a.black=valid;
return 1;
}
int Move(Board &a,node X,node Y)//Your move
{
if(a[X]==NONE || a[X]==BLACK) return 0;
int p=valid_move(a,X,Y);
if(p!=1) return p;
if(a.king==X) a.king=Y;
else if(a.queen==X) a.queen=Y;
if(a.black.count(Y)) a.black.erase(Y);
return Move(a);
}
pair<Board,Board> split(Board a,node x)//split by position x into {valid,invalid}
{
Board b;b.king=a.king,b.queen=a.queen;
for(int i=0;i<8;i++) if(a.black.count(x+P[i])) a.black.erase(x+P[i]),b.black.insert(x+P[i]);
return{a,b};
}
struct Round{
vector<Board>F;
void print()
{
system("cls");
for(int i=0;i<F.size();i++)
{
printf("Board %d:\n",i+1);
F[i].print();
}
}
}mp[410];
int Move(Round &a,node X,node Y)
{
Round b;bool have_move=false;
for(auto t:a.F)
{
auto p=Move(t,X,Y);
if(p==-1)
{
auto q=split(t,Y);
p=Move(q.fi,X,Y);
if(p==2 || p==3) return p;
b.F.push_back(q.fi),b.F.push_back(q.se);
have_move=true;
continue;
}
if(p==2 || p==3) return p;
else b.F.push_back(t);
if(p==1) have_move=true;
}
a=b;
return have_move;
}
void Clear(Round &a)
{
map<pair<node,node>,set<node>> b;
for(auto &t:a.F)
if(!t.black.empty())
{
auto &u=b[{t.king,t.queen}];
for(auto v:t.black) u.insert(v);
}
a.F.clear();
for(auto [p,v]:b) a.F.push_back((Board){p.first,p.second,v});
}
vector<string>ans;
int main()
{
ofstream out("ans.txt");
int T=0;
mp[T].F.resize(1);mp[T].F[0].init();
while(true)
{
mp[T].print();
string str;
cin>>str;
if(str=="back"){T=max(T-1,0);ans.pop_back();continue;}
if(str=="end"){for(auto v:ans) out<<v<<" ";out.close();return 0;}
int y1=str[0]-'a'+1,x1=str[1]-'0',y2=str[2]-'a'+1,x2=str[3]-'0';
if(!valid_position({x1,y1}) || !valid_position({x2,y2})){puts("Invalid");continue;}
ans.push_back(str);
++T;mp[T]=mp[T-1];
int p=Move(mp[T],{x1,y1},{x2,y2});
if(p!=1)
{
mp[T].print();
if(p==2) puts("Queen was eaten");
else if(p==3) puts("Become Draw!");
else if(p==0) puts("Nothing was done");
while(true){cin>>str;if(str=="back") break;}
T--;ans.pop_back();
continue;
}
else Clear(mp[T]);
if(mp[T].F.empty())
{
system("cls");
puts("Win");
for(auto v:ans) out<<v<<" ";
out.close();
return 0;
}
}
return 0;
}