題目大意:有n个人要进行n-1场决斗,最后只能有一个人存活,决斗可以从这n个人任意一个开始,但是只有相邻的两个人能进行决斗,问最后哪些人有可能活下来,输出人数,并把他们的序号从小到大输出。
思路如下:因为这n个人排成一个圈,任意相邻两个人可以进行决斗(最后一个人可以和第一个人决斗,一个圈),于是先用n+1长度的数组构造一个“人链”,数组的开头和结尾是同一个人比如:有5个人,先从第一个人开始看,1站排头,即a0,a1,a2,a3,a4,a5分别是1,2,3,4,5,1。每有一个人死亡,那个人就退场,直到最后,如果能够首尾相邻那就表示那个人能够获胜。考虑获胜前最后一个状态便可。即第k个人,可以被排头的人打败或者被排尾的人打败,并且最后会剩下三个人(排头,k,排尾)。如此排头(即排尾)的这个人就能获胜,这就知道是dp了。
dp[i][j]表示第 i 人与第 j 人能否相遇,能为1,否为0;a[i]表示人链中排第 i 的人;f[i][j]用来记录题目中的数组。
动态转移方程:dp[i][j]=(((f[a[i]][a[k]] || f[a[j]][a[k]]) && dp[i][k] && dp[k][j])?1:0);(i<k<j)
这里附上两份代码,因为第一次做是用递归(记忆化搜索)做的,后来想想,因为习惯,改成了循环。
AC代码:(循环)
import java.util.Scanner;
public class Main
{
static Scanner scan=new Scanner(System.in);
public static void main(String[] args)
{
int t=scan.nextInt();
while(t-->0)
{
int n=scan.nextInt();
int f[][]=new int[n+1][n+1];
String str=scan.nextLine();
for(int i=1;i<n+1;i++)
{
str=scan.nextLine();
for(int j=1;j<n+1;j++)
f[i][j]=str.charAt(j-1)-48;
}
int dp[][]=new int[n+1][n+1];
int a[]=new int[n+1];
int ans[]=new int[n+1];//这个数组用来记录获胜的人
int cas=0;
for(int len=1;len<n+1;len++)//len表示第len个人在排头(考察他是否能胜出)
{
int meet=len;
for(int k=0;k<n+1;k++)//这个循环构造人链
{
if(meet<=n)
a[k]=meet;
else
a[k]=meet-n;
meet++;
}
for(int i=1;i<n+1;i++)
for(int j=0;j+i<n+1;j++)
{
if(j+1==j+i)//如果左右相邻,直接就是能两人能相遇
dp[j][j+i]=1;
else
{
for(int k=j+1;k<j+i;k++)//找人
if((f[a[j]][a[k]]==1 || f[a[j+i]][a[k]]==1) && dp[j][k]==1 && dp[k][j+i]==1)
{dp[j][j+i]=1;break;}//满足条件就赋1,然后break
else dp[j][j+i]=0;//因为这个数组要用很多次,所以找不到还是赋0
}
}
if(dp[0][n]==1)//相遇了
ans[cas++]=len;//记
}
System.out.println(cas);
for(int i=0;i<cas;i++)
System.out.println(ans[i]);
}
}
}
AC代码:(递归)
import java.util.Scanner;
public class Main//标注的地方是跟循环不一样的地方
{
static Scanner scan=new Scanner(System.in);
public static int winner(int L, int R, int[] a, int[][] dp, int[][] f)
{
if(dp[L][R]!=-1)//记忆化搜索,看看是否检查过
return dp[L][R];
if(L+1==R)
return dp[L][R]=1;
int flag=0;
for(int i=L+1;i<R;i++)
if((f[a[L]][a[i]]==1 || f[a[R]][a[i]]==1) && winner(L, i, a, dp, f)==1 && winner(i, R, a, dp, f)==1)
{
flag=1;
break;
}
return dp[L][R]=flag;
}
public static void main(String[] args)
{
int t=scan.nextInt();
while(t-->0)
{
int n=scan.nextInt();
int f[][]=new int[n+1][n+1];
String str=scan.nextLine();
for(int i=1;i<n+1;i++)
{
str=scan.nextLine();
for(int j=1;j<n+1;j++)
f[i][j]=str.charAt(j-1)-48;
}
int dp[][]=new int[n+1][n+1];
int a[]=new int[n+1];
int ans[]=new int[n+1];
int cas=0;
for(int i=1;i<n+1;i++)
{
for(int x=0;x<n+1;x++)
for(int y=0;y<n+1;y++)
dp[x][y]=-1;//初始化,每进一次循环初始化一次,全部为-1,因为未检查(这个地方在记忆化搜索起作用)
int meet=i;//构造人链
for(int j=0;j<n+1;j++)
{
if(meet<=n)
a[j]=meet;
else
a[j]=meet-n;
meet++;
}
if(winner(0, n, a, dp, f)==1)//0与n能否相遇,即首尾
ans[cas++]=i;
}
System.out.println(cas);
for(int i=0;i<cas;i++)
System.out.println(ans[i]);
}
}
}