猜对了一半 (20分)
赛场内 n (0<n≤10) 名运动员正在参加百米短跑比赛。赛场外有 m (0<m≤100) 名热心观众,他们每人都对比赛结果作出了 2 个预测。比赛结束后,运动员的名次各不相同,但令人惊奇的是每位观众都猜对了一半。请问运动员取得的实际名次是多少?
例如场内有 4 名运动员参加比赛,场外 3 名观众的预测分别为:
1 号队员第 1,2 号队员第 3
3 号队员第 1,4 号队员第 4
4 号队员第 2,1 号队员第 3
根据每人猜对一半因此可以推理得:
1 号队员第 4
2 号队员第 3
3 号队员第 1
4 号队员第 2
请编写程序,根据观众的预测来推算运动员的实际名次。
输入格式
正整数 n
正整数 m
m 行数据,为 m 位观众的预测
其中每行包含 4 个整数:x1 r1 x2 r2
它们分别表示观众的两个预测:x1号运动员第 r1,x2号运动员第 r2
输出格式
若问题无解,则输出 None
若问题有解,则输出多行数据,每一行表示一个答案,按字典序输出。
每一行包含 n 个整数,分别是 1 ~ n 号运动员取得的实际名次。
注:整数间用空格间隔,行末没有空格。
输入样例1
4
3
1 1 2 3
3 1 4 4
4 2 1 3
输出样例1
4 3 1 2
输入样例2
4
3
3 4 2 1
4 3 3 2
1 4 2 3
输出样例2
None
输入样例3
4
3
2 4 4 1
4 2 2 3
3 4 1 1
输出样例3
1 4 3 2
2 3 4 1
【思路】
题中每位观众都猜对了一半,即可把该观众的两个预测分成两种情况讨论。假设观众的第一个预测为真,则第二个预测为假,或者假设观众的第一个预测为假,则第二个预测为真。(预测为真即是运动员的名次为真,预测为假即是运动员的名次为假)
同一个运动员只能有一个名次,同一个名次只能有一个运动员,假设观众预测某一运动员的名次为假时,之后的观众预测该运动员的名次就不能为真,否则两则相矛盾。若观众预测运动员的名次少于所给运动员的个数时,则要补齐剩余运动员的名次。不同观众预测同一个运动员的假名次,可能有多个。若观众预测运动员的名次少于所给运动员的个数时,则要补齐剩余运动员的名次。
代码:
#include<stdio.h>
/*n运动员人数,m观众人数,c所求运动员名次的方案数,
falg[]标记观众预测为真的名次,a[][]保存观众的预测*/
int n, m, c, flag[11], a[105][6];
//mc运动员名次,k观众猜测此运动员假名次个数,jiamc[]保存观众猜测此运动员名次为假的名次
struct{
int mc, k, jiamc[105];
}ydy[11];
//per[]为每个方案运动员的名次,sum保存每个方案运动员名次的十进制整数
struct{
int per[11];
long long sum;
} ans[1000], t;
int pj(int n, int mc){//判断n号运动员,mc是否为假
int i;
for(i = 0; i < ydy[n].k; i++){
if(ydy[n].jiamc[i] == mc){
return 0;//mc为假,返回0
}
}
return 1;//mc为真,返回1
}
int shu(int cur){//回溯法把剩余没有名次的运动员根据约束条件补齐
if(cur > n){
int i;
for(i = 1; i <= n; i++){
ans[c].per[i] = ydy[i].mc;
}
c++;
}
else{
int i;
if(ydy[cur].mc == 0){//运动员cur没有名次
for(i = 1; i <= n; i++){//运动员名次
if(flag[i] == 0 && pj(cur, i)){//名次i未被标记且第cur个运动员的名次i不在假名次中
ydy[cur].mc = i;
flag[i] = 1;
shu(cur + 1);
ydy[cur].mc = 0;
flag[i] = 0;
}
}
}
else{//运动员cur有名次
shu(cur + 1);
}
}
}
int search(int cur){
if(cur < m){
int i;
for(i = 0; i < 2; i++){/*i == 0时,假设观众cur第一个预测为真,则其第二个预测为假,
i == 1时,假设观众cur第二个预测为真,则其第一个预测为假 */
if(ydy[a[cur][2 * i + 1]].mc == a[cur][2 * i + 2]){/*观众cur预测同一个运动员的名次
与之前观众预测相同*/
if(pj(a[cur][3 - 2 * i], a[cur][4 - 2 * i])){//观众cur预测的假名次,还未保存在在该运动员的jiamc[]中
ydy[a[cur][3 - 2 * i]].jiamc[ydy[a[cur][3 - 2 * i]].k] = a[cur][4 - 2 * i];//保存相应运动员名次为假的名次
ydy[a[cur][3 - 2 * i]].k++;//假名次个数增加
search(cur + 1);
ydy[a[cur][3 - 2 * i]].jiamc[ydy[a[cur][3 - 2 * i]].k] = 0;
ydy[a[cur][3 - 2 * i]].k--;
}
else{//观众cur预测的假名次,已经保存在该运动员的jiamc[]中
search(cur + 1);
}
}
else{//观众cur预测不是同一个运动员
//名次未被标记且运动员名次不相同且运动员的jiamc[]等于1,即运动员名次为真,或者第一层
if((flag[a[cur][2 * i + 2]] == 0 && ydy[a[cur][2 * i + 1]].mc == 0 && pj(a[cur][2 * i + 1], a[cur][2 * i + 2])) || cur == 0){
ydy[a[cur][2 * i + 1]].mc = a[cur][2 * i + 2];//保存相应运动员的名次
flag[a[cur][2 * i + 2]] = 1;//标记名次
ydy[a[cur][3 - 2 * i]].jiamc[ydy[a[cur][3 - 2 * i]].k] = a[cur][4 - 2 * i];//保存相应运动员名次为假的名次
ydy[a[cur][3 - 2 * i]].k++;//假名次个数增加
search(cur + 1);
ydy[a[cur][2 * i + 1]].mc = 0;
flag[a[cur][2 * i + 2]] = 0;
ydy[a[cur][3 - 2 * i]].jiamc[ydy[a[cur][3 - 2 * i]].k] = 0;
ydy[a[cur][3 - 2 * i]].k--;
}
}
}
}
else if(cur == m){//cur等于观众人数,进入shu()补齐其余运动员的名次
shu(1);
}
}
int main()
{
int i, j, k;
scanf("%d", &n);
scanf("%d", &m);
for(i = 0; i < m; i++){
scanf("%d %d %d %d", &a[i][1], &a[i][2], &a[i][3], &a[i][4]);
}
search(0);
if(c == 0){//方案数为0
printf("None\n");
return 0;
}
for(i = 0; i < c; i++){//把每一行运动员名次转换成十进制整数
for(j = 1; j <= n; j++){
ans[i].sum = ans[i].sum * 10 + ans[i].per[j];
}
}
for(i = 0; i < c - 1; i++){//通过上面得到的整数从小到大排序,即可得到字典序
k = i;
for(j = k + 1; j < c; j++){
if(ans[j].sum < ans[k].sum){
k = j;
}
}
t = ans[k];
ans[k] = ans[i];
ans[i] = t;
}
for(i = 0; i < c; i++){//输出答案
for(j = 1; j <= n; j++){
if(j == 1){
printf("%d", ans[i].per[j]);
}
else{
printf(" %d", ans[i].per[j]);
}
}
printf("\n");
}
return 0;
}
虽然我给出的程序可以直接通过全部测试,但限于本人能力,不一定是最完美的解决方案。