USACO DEC 2019 参赛总结

USACO DEC 2019 参赛总结(2019-12-13~2019-12-16)

2019-12-19 xiaoh

金组Gold

A-Milk Pumping

题意

给定一个n个点,m条边的无向图,每一条边都有一个最大流量f和一个价格c(1<=n,m<=1000,1<=c,f<=1000),找一条从1到n的路径(保证存在),求最大化Min{f}/Σ{c}的路径,将这个值×1e6取下整后输出

题解

首先,很明显,当流量最小值固定时,我们只要求满足条件的最小的Σ{c}即可。所以考虑枚举每一个Min{f},然后将所有流量>=f的边建图,跑Dijkstra即可,时间复杂度O(maxf×(n+m)lg(n+m)),大约在1e7多一点的样子,可以过,敲板子即可。

Code

#include<bits/stdc++.h>
#define inf 9999999
using namespace std;
inline int read()//快读
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n,m;
int tot=1;
struct node{
	int v,c;
	bool flag;
}edge[2010];
int nxt[2010];
int hd[1010];
inline void add_edge(int u,int v,int c)//前向星
{
	edge[tot].v=v,edge[tot].c=c;
	nxt[tot]=hd[u];
	hd[u]=tot++;
}
struct node2{
	int u,v,c,f;
}a[1010];
bool cmp(node2 a,node2 b)//将边按流量排序(实际没什么卵用)
{
	return a.f<b.f;
}
struct node3{//Dijkstra专用堆+运算符重载
	int pos,num;
	friend bool operator < (node3 a,node3 b)
	{
		return a.num>b.num;
	}
};
long double ans=0;
priority_queue<node3> q;
int dis[1010];
bool book[1010];
inline void init(int limit)//按照流量限制初始化并建图
{
	tot=1;
	memset(edge,0,sizeof(edge));
	memset(nxt,0,sizeof(nxt));
	memset(hd,0,sizeof(hd));
	memset(book,0,sizeof(book));
	for(int i=1;i<=n;i++) dis[i]=(i==1)? 0:inf;
	for(int i=1;i<=m;i++)
	if(a[i].f>=limit)
	{
		add_edge(a[i].u,a[i].v,a[i].c);
		add_edge(a[i].v,a[i].u,a[i].c);
	}
}
void Dijkstra()//Dijkstra板子,不解释
{
	q.push({1,0});
	while(!q.empty())
	{
		int x=q.top().pos;
		q.pop();
		if(book[x]) continue;
		book[x]=1;
		for(int i=hd[x];i;i=nxt[i])
		if(dis[edge[i].v]>dis[x]+edge[i].c)
		{
			dis[edge[i].v]=dis[x]+edge[i].c;
			q.push({edge[i].v,dis[edge[i].v]});
		}
	}
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++) a[i].u=read(),a[i].v=read(),a[i].c=read(),a[i].f=read();
	sort(a+1,a+m+1,cmp);
	for(int i=1;i<=1000;i++)
	{
		init(i);
		Dijkstra();
		ans=max(ans,(long double)i/(long double)dis[n]);
	}
	ans*=(long double)1000000;
	ans=floor(ans);
	printf("%lld\n",(long long)ans);
	return 0;
}

B-Milk Visits

题意

给定一棵有n个节点的树(1<=n<=1e5),每个节点上都有特定种类的牛(种类在[1,n]间),有m个询问,每次给一个ai,bi表示树上的一条在ai,bi间的路径,问路径中是否存在至少一头种类为ci的牛

题解

笔者刚看到题的时候,第一个反应是用LCA维护啊!用LCA暴力维护每一种牛是否存在,至于维护的具体方式只能是bitset之类的数据结构,可就算是使用bitset,我们可以大致的计算一下空间复杂度:1e5×lg1e5×1e5/32,大概是20个G,根本不行!所以只能换思路了。
由于题目没有强制要求询问在线,因此可以考虑将询问离线处理。首先观察询问的特点,发现每个询问只涉及一种牛,所以我们可以考虑先假设整棵树是空的,然后往里加入种类为i的牛,处理对应查询,再把这些牛删掉。这样每次只需要在节点上维护一个bool值,表示当前位置是否有一头当前正在处理的种类的牛即可,而不必单独维护每一种牛是否存在,很大的简化了问题。但是这个做法有一个很大的缺陷:你需要用一个数据结构,使它能够支持动态修改和查询,而前面提到的LCA在查询上表现相当优异,在面对修改时却退化成了O(nlgn)的复杂度,远远不能支持算法。所以我们要考虑更适合的数据结构。
树,修改,查询路径权值……想必各位也想到了,树链剖分是最适合的。所以算法便自然而然的出来了:先将空树剖分,然后每个节点维护一个bool值,表示当前位置是否有一头当前正在处理的种类的牛,用线段树维护重链上是否至少存在一个true值。每次处理询问前,将对应种类上的对应位置的牛的bool值改为true,处理对应查询,并将对应位置上的牛的bool值重新改为false即可。时间复杂度O(nlgnlgn),大约在3×1e7左右,刚好可以卡过去。

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()//快读
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n,m;
int tot=1;
int edge[200010];
int nxt[200010];
int hd[100010];
inline void add_edge(int u,int v)//前向星
{
	edge[tot]=v;
	nxt[tot]=hd[u];
	hd[u]=tot++;
}
int a[100010];
bool ans[100010];
//Segment_Tree
struct node{
	int l,r;
	bool num;
}f[100010*4];
inline void pushup(int p)
{
	f[p].num=0;//注意这一步,否则节点有可能一直是true而在其子节点都被改为false后由于or的性质没有被改变
	f[p].num|=f[p*2].num,f[p].num|=f[p*2+1].num;
}
void build(int p,int l,int r)//建树
{
	f[p].l=l,f[p].r=r;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
}
bool query(int p,int l,int r)//查询
{
	if(f[p].l>=l&&f[p].r<=r) return f[p].num;
	int mid=(f[p].l+f[p].r)>>1;
	bool ans=0;
	if(l<=mid) ans|=query(p*2,l,r);
	if(r>mid) ans|=query(p*2+1,l,r);
	return ans;
}
void modify(int p,int x,bool y)//修改
{
	if(f[p].l==f[p].r)
	{
		f[p].num=y;
		return;
	}
	int mid=(f[p].l+f[p].r)>>1;
	if(x<=mid) modify(p*2,x,y);
	else modify(p*2+1,x,y);
	pushup(p);
}
//树剖
int son[100010],rk[100010],id[100010],sz[100010],fa[100010],top[100010],d[100010];
void dfs1(int p,int f,int dep)//第一次深搜
{
	fa[p]=f,d[p]=dep,sz[p]=1;
	for(int i=hd[p];i;i=nxt[i])
	{
		if(edge[i]==f) continue;
		dfs1(edge[i],p,dep+1);
		sz[p]+=sz[edge[i]];
		if(sz[edge[i]]>sz[son[p]]) son[p]=edge[i];
	}
}
int cnt=0;
void dfs2(int p,int tp)//第二次深搜
{
	id[p]=++cnt,rk[cnt]=p,top[p]=tp;
	if(son[p]) dfs2(son[p],tp);
	for(int i=hd[p];i;i=nxt[i])
	{
		if(edge[i]==fa[p]||edge[i]==son[p]) continue;
		dfs2(edge[i],edge[i]);
	}
}
bool query_LCA(int x,int y)//查询答案
{
	bool ans=0;
	while(top[x]!=top[y])
	{
		if(d[top[x]]<d[top[y]]) swap(x,y);
		ans|=query(1,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	ans|=query(1,min(id[x],id[y]),max(id[x],id[y]));
	return ans;
}
vector<int> v1[100010];//储存每种牛在哪些位置
struct node2{
	int a,b,id;
};
vector<node2> v2[100010];//储存查询每种牛的查询操作在哪个位置,方便最后储存答案
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read(),v1[a[i]].push_back(i);
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	dfs1(1,-1,1);
	dfs2(1,1);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int a=read(),b=read(),c=read();
		v2[c].push_back({a,b,i});
	}
	for(int i=1;i<=n;i++)
	{
		for(vector<int>::iterator it=v1[i].begin();it!=v1[i].end();it++) modify(1,id[*it],1);
		for(vector<node2>::iterator it=v2[i].begin();it!=v2[i].end();it++) ans[it->id]=query_LCA(it->a,it->b);
		for(vector<int>::iterator it=v1[i].begin();it!=v1[i].end();it++) modify(1,id[*it],0);
	}
	for(int i=1;i<=m;i++)
	if(ans[i]) printf("1");
	else printf("0");
	printf("\n");
	return 0;
}

C-Moortal Cowmbat

P.S. 由于笔者水平不够,只能写出O(n^2)的算法,然鹅过不了此题,就不放在这里丢人现眼了QAQ(虽然我已经看出这道题是四边形不等式优化DP了)

银组

A-MooBuzz

题意

从1开始报数,报到3或5的倍数就跳过去,求第n个报到的数是多少(1<=n<=1e9)

题解

签到题。显然,每15个数一循环,只有1,2,4,7,8,11,13,14可以取,所以直接枚举出现了几个完整的循环并统计余数即可。

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
long long n;
long long a[]={-1,1,2,4,7,8,11,13};
int main()
{
	cin>>n;
	cout<<(n/8)*15+a[n%8]<<endl;
	return 0;
}

B-Meetings

题意

有n头牛在一根长度为l的数轴上(1<=n<=5×1e4,1<=l<=1e9),每头牛要么朝左,要么朝右,都按照1的单位每秒的速度运动,当运动到0或l时就会停下。当两头牛在同一个点上时(即相遇,未必在整点上发生),他们会原路返回(即反弹)。每头牛都有一个质量m(1<=m<=1e3),当停下牛的质量>=总质量的一半时,求相遇牛的对数。

题解

先喷一句,不愧是Benjamin Qi出的题,果然够毒瘤,恶心了我十几分钟才想出正解。真是不折不扣的毒瘤
首先,将算法分为2部分。第一部分:求停下的时间t;第二部分:求相遇牛的对数。先考虑第一部分。如果有人做过POJ的一道题Ants的话,就会发现这道题就是它的升级版。当两头牛反弹时,我们可以理解为两头牛互相穿过对方,继续前进,然后得出一个序列a,其中ai表示第i头停下的牛(注意不是第i头牛,是第i头停下的牛)是在最左边停的还是在最右边停的。这一步想必各位都能写的出来,排个序一算就好了。然后考虑题目的现状:两头牛相遇后反弹,此时不难发现,此时得到的a序列与将“相遇”理解为“穿过”时的a序列竟然完全相同!(如果想不通的话自己手动模拟一下就好啦)然后再考虑:每头停下的牛,若他是在最左边停下的,那么说明他的左边一定没有牛,右边同理。所以我们先假设“相遇”就是“穿过对方”,求出a序列,再将每头牛按照位置排序,由于停下的牛一定在两端,所以我们就可以得到停下牛的顺序,从而求出相应的停下时间t。
再考虑第二部分:相遇对数。有了前面的t,这一步就相当简单了,你可以将这种情况理解为逆序对。先统计初始状态下每头牛的位置上,从左到右重新按1~n编号(在代码中的体现就是排序),然后再统计t秒后每头牛的位置。由于同向的牛不会发生追击,所以很明显我们不必考虑停下的状况,假设他们一直运动的结果和到两端停下的结果一定相同。逆序对求解即可。

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n,l;
struct node{
	int w,x,d;
}a[50010];
bool cmp(node x,node y)
{
	return x.x<y.x;
}
struct node2{
	int t,dir;
}b[50010];
bool cmp2(node2 x,node2 y)
{
	return x.t<y.t;
}
int totw=0,t;
int f[50010];//树状数组板子
inline int lowbit(int x)
{
	return x&(-x);
}
inline void modify(int x,int y)
{
	for(;x<=n;x+=lowbit(x)) f[x]+=y;
}
inline int query(int x)
{
	int ret=0;
	for(;x;x-=lowbit(x)) ret+=f[x];
	return ret;
}
struct node3{
	int pos,id;
}c[50010];
bool cmp3(node3 x,node3 y)
{
	if(x.pos!=y.pos) return x.pos<y.pos;
	else return x.id>y.id;//注意:有可能有多头牛最后在同一个点,此时他们也算相遇,所以要加这一句
}
long long ans=0;
int main()
{
	n=read(),l=read();
	for(int i=1;i<=n;i++) a[i].w=read(),a[i].x=read(),a[i].d=read(),totw+=a[i].w;
	sort(a+1,a+n+1,cmp);//按照数轴上的位置排序
	for(int i=1;i<=n;i++)
	if(a[i].d==1) b[i]={l-a[i].x,1};
	else b[i]={a[i].x,-1};//按照“互相穿过”计算A序列
	sort(b+1,b+n+1,cmp2);
	int lp=1,rp=n,cnt=0;
	for(int i=1;i<=n;i++)
	{
		if(b[i].dir==1) cnt+=a[rp--].w;
		else cnt+=a[lp++].w;
		t=b[i].t;
		if(cnt>=(totw+1)/2) break;
	}//计算时间t
	for(int i=1;i<=n;i++)
	if(a[i].d==1) c[i]={a[i].x+t,i};
	else c[i]={a[i].x-t,i};//计算t秒后的位置
	sort(c+1,c+n+1,cmp3);//排序
	for(int i=n;i>=1;i--)
	ans+=(long long)query(c[i].id-1),modify(c[i].id,1);//逆序对
	printf("%lld\n",ans);
	return 0;
}

C-Milk Visits(弱化版)

题意

题面与金组T2类似。给定一棵有n个节点的树(1<=n<=1e5),每个节点上都有特定种类的牛,种类为Guernsey(用'G'表示)或Holstein(用'H'表示),有m个询问,每次给一个ai,bi表示树上的一条在ai,bi间的路径,问路径中是否存在至少一头种类为ci的牛(ci是'G'或'H'中的一个)

题解

是金组T2的魔改版,基本属于模板题。利用LCA维护整颗树,并在维护LCA时同时维护两个bool变量,表示目前的这一段是否有至少一头H牛和目前的这一段是否至少有一头G牛,然后打LCA的板子即可。

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n,m;
int a[100010];
int tot=1;
int edge[200010];
int nxt[200010];
int hd[100010];
inline void add_edge(int u,int v)//前向星
{
	edge[tot]=v;
	nxt[tot]=hd[u];
	hd[u]=tot++;
}
int d[100010];
struct node{
	int pos;
	bool val1,val2;
}LCA[100010][20];
queue<int> q;
bool book[100010];
int max_lg;
void prework()//LCA预处理
{
	book[1]=1;
	d[1]=1,q.push(1);
	max_lg=log2(n)+1;
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=hd[x];i;i=nxt[i])
		{
			int y=edge[i];
			if(book[y]) continue;
			book[y]=1;
			if(a[x]==0) LCA[y][0].val1=1;
			else LCA[y][0].val2=1;
			LCA[y][0].pos=x;
			d[y]=d[x]+1;
			for(int j=1;j<=max_lg;j++)
			{
				LCA[y][j].pos=LCA[LCA[y][j-1].pos][j-1].pos;//在维护LCA的同时维护val1和val2
				LCA[y][j].val1|=LCA[LCA[y][j-1].pos][j-1].val1;
				LCA[y][j].val1|=LCA[y][j-1].val1;
				LCA[y][j].val2|=LCA[LCA[y][j-1].pos][j-1].val2;
				LCA[y][j].val2|=LCA[y][j-1].val2;
			}
			q.push(y);
		}
	}
}
bool query(int x,int y,int type)//查询LCA的同时查询val1或val2
{
	bool ans1=0,ans2=0;
	if(a[x]==0) ans1=1;
	else ans2=1;
	if(a[y]==0) ans1=1;
	else ans2=1;
	if(d[x]<d[y]) swap(x,y);
	for(int i=max_lg;i>=0;i--)
	if(d[LCA[x][i].pos]>=d[y])
	{
		ans1|=LCA[x][i].val1,ans2|=LCA[x][i].val2;
		x=LCA[x][i].pos;
	}
	if(x==y)
	{
		if(type==1) return ans1;
		else return ans2;
	}
	for(int i=max_lg;i>=0;i--)
	if(LCA[x][i].pos!=LCA[y][i].pos)
	{
		ans1|=LCA[x][i].val1,ans2|=LCA[x][i].val2;
		x=LCA[x][i].pos;
		ans1|=LCA[y][i].val1,ans2|=LCA[y][i].val2;
		y=LCA[y][i].pos;
	}
	ans1|=LCA[x][0].val1,ans2|=LCA[x][0].val2;
	ans1|=LCA[y][0].val1,ans2|=LCA[y][0].val2;
	if(type==1) return ans1;
	else return ans2;
}
int main()
{
	n=read(),m=read();
	string s;
	cin>>s;
	for(int i=0;i<s.length();i++)
	if(s[i]=='H') a[i+1]=0;
	else a[i+1]=1;
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		add_edge(u,v);
		add_edge(v,u);
	}
	prework();
	for(int i=1;i<=m;i++)
	{
		int x=read(),y=read();
		char c;
		cin>>c;
		if(c=='H')
		{
			if(query(x,y,1)==0) printf("0");
			else printf("1");
		}
		else
		{
			if(query(x,y,2)==0) printf("0");
			else printf("1");
		}
	}
	printf("\n");
	return 0;
}

铜组

A-Cow Gymnastics

题意

有n头牛,k次训练课。给出每次训练课每头牛的排名。求满足所有训练课中,一头牛都比另一头排名靠前的牛的对数(1<=k<=10,1<=n<=20)

题解

暴力枚举即可,怎么样写都能过,就不细讲了。

Code

#include<bits/stdc++.h>
using namespace std;
inline int read()//快读
{
	int ret=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
	return w*ret;
}
int n,k;
int a[25][25];
int ans=0;
int main()
{
	k=read(),n=read();
	for(int i=1;i<=k;i++)
	for(int j=1;j<=n;j++)//读入
	{
		int x=read();
		a[i][x]=j;
	}
	for(int i=1;i<=n;i++)//枚举牛
	for(int j=1;j<=n;j++)
	if(i!=j)
	{
		for(int l=1;l<=k;l++)//检查是否可行
		if(a[l][i]<a[l][j]) goto nxt;
		ans++;
		nxt:;
	}
	cout<<ans<<endl;
	return 0;
}

B-Where am I?

题意

给一个字符串S(1<=|s|<=100,且S有大写字母构成),对于每一个位置i,我们用一段子串si,si+1,si+2,...,si+k表示它的位置。求最小的满足条件的k,使所有的位置i,表示它位置的子串各不相同

题解

截断子串,并保证其独一无二性,让我们想到了利用map进行查重操作。建立一个map,表示string到bool的映射。考虑暴力枚举k,并把每个字串在map中的值改为1。若该字串在修改之前已经为1,说明重复了,排除,进入下一个k进行枚举。注意map不需要实时清空,因为每个substring的长度都不同,因而不会在不同的k时出现相同的字串。

Code

#include<bits/stdc++.h>
using namespace std;
int n;
string s;
map<string,bool> mp;
int main()
{
	cin>>n>>s;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j+i-1<n;j++)
		{
			string tmp=s.substr(j,i);//取表示位置当前的子串
			if(mp[tmp]) goto nxt;//若重复,则不可行,进入下一个k
			mp[tmp]=1;//将子串标记为存在
		}
		cout<<i<<endl;//若没有冲突,则说明当前解可行,输出并返回
		return 0;
		nxt:;
	}
	return 0;
}

C-Line Up

题意

有8头牛,分别叫,要排成一列。给定n个限制条件(1<=n<=7),每个条件为"A must be milked after B",表示A、B必须在队伍中相邻。求满足条件的字典序最小的排列

题解

个人认为是三场比赛中最最最无耻的一道题,差点逼我用拓扑(虽然拓扑也做不出来),尤其对于新手相当不友好,将暴力的正解藏得严严实实,令人浮想翩翩,不知不觉进入贪心和DP的大坑QAQ。而且说是字典序,这牛名字有一大半都是B打头的,光找字典序我就差点自闭,还考了字符串的输入输出,实在是恶心到了令人发指的地步。请所有该题原地爆炸的同学仔细读一下数据,就8头牛,你觉得对于将一个数据复杂度卡的刚刚好的毒瘤OJ——USACO,怎么可能这么友好,一个复杂度n方一下的算法会只可能给大小为8的数据吗?(况且铜组里一般不会出现此类算法的)仔细揣摩,复杂度能恰好卡到8的算法,也只有O(n!×n)或O(2^n)之类了吧。所以只可能是暴力搜索。我这里采用了map映射的形式,将牛按照字典序进行哈希,然后使用STL中的next_permutation,那么第一个可行解一定是字典序最小的,只需全排列+检查即可。

Code

#include<bits/stdc++.h>
using namespace std;
int n;
int a[10];
map<string,int> mp1;
map<int,string> mp2;
int b[8][2];
void check()
{
	for(int i=1;i<=n;i++)//检查当前解是否可行
	{
		int x,y;
		for(int j=1;j<=8;j++)
		if(a[j]==b[i][0]) x=j;
		else if(a[j]==b[i][1]) y=j;
		if(abs(x-y)!=1) return;
	}
	for(int i=1;i<=8;i++) cout<<mp2[a[i]]<<endl;//若可行,直接输出并返回
	exit(0);
}
int main()
{
	mp1["Beatrice"]=1,mp2[1]="Beatrice";//正向+反向手动按字典序映射
	mp1["Belinda"]=2,mp2[2]="Belinda";
	mp1["Bella"]=3,mp2[3]="Bella";
	mp1["Bessie"]=4,mp2[4]="Bessie";
	mp1["Betsy"]=5,mp2[5]="Betsy";
	mp1["Blue"]=6,mp2[6]="Blue";
	mp1["Buttercup"]=7,mp2[7]="Buttercup";
	mp1["Sue"]=8,mp2[8]="Sue";
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		string s;
		cin>>s;
		b[i][0]=mp1[s];
		cin>>s,cin>>s,cin>>s,cin>>s;//这里连着那么多cin主要是为了处理中间的那堆废话
		cin>>s;
		b[i][1]=mp1[s];
	}
	for(int i=1;i<=8;i++) a[i]=i;
	do{
		check();//查询当前解是否可行
	}while(next_permutation(a+1,a+9));//暴力枚举
	return 0;
}