Tournament
input:
2
3
4
output:
1 2
1 3
2 3
1 2
1 3
1 4
2 3
2 4
3 4
有 n n n支队伍两两之间比赛一场,则进行 n ( n − 2 ) 2 \frac{n(n-2)}{2} 2n(n−2)场。现每支队伍会从他们要比赛的第一天开始直到他们的比赛的最后一场为止,即安排中他们队伍最后出现的那天为止,都会待在比赛场地里。
现你可以安排比赛顺序,要求如何安排会使得所有队伍待在场地里的时间总合最小。
首先想到的是:
f o r ( i f r o m 1 → n ) for(i\,\,from\,\,1\to n) for(ifrom1→n)
f o r ( j f r o m i + 1 → n ) \qquad for(j\,\,from\,\,i+1\to n) for(jfromi+1→n)
o u t p u t ( i , j ) ; \qquad\qquad output(i,j); output(i,j);
显然,这一策略是很劣的,因为1队虽然时间很短,但是其他队伍的时长完全拉大了整个的时长。分析后易得可以发现,我们需要使得每支队伍都是基本差不多的时长,即保证没有队伍受到优待。
接下来是官方的策略:
首先将所有的队伍分成两个部分,然后前面是 1 ∼ n 2 1\sim \frac{n}{2} 1∼2n,后面是 n 2 + 1 ∼ n \frac{n}{2}+1\sim n 2n+1∼n。那么只要考虑中间的打法就可以了。接下来我们以 n = 10 n=10 n=10为例。
显然我们之前是 1 ∼ 5 1\sim 5 1∼5的比赛,那么我们希望10号队伍最迟来(因为后面10号肯定是最后走的),又希望1号最早走(因为前面1号肯定最早来的),所以中间肯定是 ( 1 , 10 ) (1,10) (1,10)。
然后考虑前面,通过上面的思考,我们得出:希望9号是6~9之中来得最迟的,希望……所以前面是 ( 1 , 9 ) (1,9) (1,9)。然后是 ( 1 , 8 ) , ( 2 , 8 ) (1,8),(2,8) (1,8),(2,8),依此类推……
然后考虑后面,同样的,我们6~10已经全部出现了,那么是希望1先走,所以把1和10打了,那么接下来是2了,同样的2和10,9打,然后3和10,9,8打……
这样我们就得出了中间的策略,那么根据刚才对前后的要求,显然,1 ~5和6 ~10也要像中间那样搞。
那么我们就可以得出:
for(i=2;i<=n/2;++i)
for(j=1;j<i;++j)
printf("%d %d\n",j,i);
for(i=n/2+1;i<n;++i)
for(j=1;j<=n-i;++j)
printf("%d %d\n",j,i);
for(i=1;i<=n/2;++i)
for(j=n-i+1;j<=n;++j)
printf("%d %d\n",i,j);
for(i=n/2+1;i<n;++i)
for(j=i+1;j<=n;++j)
printf("%d %d\n",i,j);
这样的比较好理解,但是多试几组可以发现其实前面和中间的前半部分以及后面和中间的后半部分是可以合并的,这样代码比较短,但是难理解。
#include
#define ll long long
using namespace std;
int main()
{
int n,t;scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=2;i<=n;i++)
for(int j=1;j<=min(i-1,n-i);j++)
printf("%d %d\n",j,i);
printf("1 %d\n",n);
for(int i=2;i<n;i++)
for(int j=max(n-i+1,i+1);j<=n;j++)
printf("%d %d\n",i,j);
}
}
较长的代码取自DNdalao。