题解:P11969 「ALFR Round 7」T2 Game
赛时只有 5 分,交了 24 次才 AC,因为不是足够聪明。
字典序无论是越大越好还是越小越好,都是针对于最终得到的序列而言。因为 \(t\) 的范围显然不适合模拟,考虑从特殊性质入手。
- \(t=1\) 时,只有先手进行一次操作,因此他只用将第一个不是升序排列的元素改为升序排列应有的正确元素即可。
- \(t=2\) 时,由于最后一次操作由后手进行,因此他一定会把最大的元素,也就是把 \(n\) 放到该排列的第一位。因此,如果 \(n\) 本来就在第一位,那么先手会把它移走,后手又移回来,相当于没有发生任何变化;如果 \(n\) 不在第一位,则后手下一步一定会把它移到第一位,使最终得到的序列字典序最大,应将第一位后面部分的字典序最小,便回到了 \(t=1\) 时讨论过的情况了。
然而,对于更大的 \(t\) 而言,后面的也都是“迂回战术”,即先手会把 \(n\) 从第一位移走,后手又把它移回来,周而复始。因此,上文讨论的两种情况,可以直接视为 \(t \equiv 1 \pmod{2}\) 时与 \(t \equiv 0 \pmod{2}\) 时进行的操作。
其他的就是细节了(尤其是 \(t\) 为偶数时):
- 特判第一位就是 \(n\) 的情况:由上述分析得,此时整个排列是不变的,故直接输出;
- 特判第一位就是 \(1\) 而第二位不是 \(n\) 的情况,此时先手会将第一二两项交换;
- 特判第一位就是 \(1\) 而第二位是 \(n\) 的情况,此时后手将 \(n\) 换到第一位时 \(1\) 就自动到了第二位,因此先手只用保证从第三位起的字典序最小即可;
- 其他的就可以直接按照前面说过的思路写了。
到这里基本上就没啥问题了。其他部分见代码注解。
Code:
#include<bits/stdc++.h>
#define ll long long
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
using namespace std;
ll t;
int n,a[114514];
int x,flag=1;
int main()
{
ios;cin>>t>>n;
t%=2;//只关心t的奇偶性
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
//t==1时,先手进行最后一次操作
//先手可以直接将第一个不符合升序排列的位置改为正确元素
//后面的操作互相抵消
if(t==1)
{
for(int i=1;i<=n;i++)
{
if(flag&&a[i]!=i)x=i,flag=0;
if(a[i]==x)swap(a[x],a[i]);//先手操作
}
for(int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
//t==0时,后手进行最后一次操作
//如果第一个元素就是n,则全部操作互相抵消,否则:
//先手应该使第二个元素开始的字典序最小
//后手应该将a[1]改为n
//后面的操作互相抵消
if(t==0)
{
if(a[1]==n)
{
for(int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
if(a[1]==1){//特判1在开头的情况(因为先手要把1放在a[2]处)
if(a[2]!=n)
{
swap(a[1],a[2]);//把1换到a[2]
}
if(a[2]==n){//此时后手把n换到后面来时自动把1换到了a[2],所以从a[3]开始考虑
for(int i=3;i<=n;i++)
if(a[i]!=i-1)
{
x=i;
break;
}
for(int i=1;i<=n;i++)
{
if(a[i]==x-1)swap(a[i],a[x]);
}
}
}
else
{
for(int j=1;j<n;j++)
{
if(flag&&a[j+1]!=j)
{
x=j+1,flag=0;
for(int i=1;i<=n;i++)
{
if(a[i]==j)
{
swap(a[i],a[x]);
break;
}
}
}
if(!flag)break;
}
}
for(int i=2;i<=n;i++)
{
if(a[i]==n)swap(a[i],a[1]);//后手操作
}
for(int i=1;i<=n;i++)
{
cout<<a[i]<<' ';
}
return 0;
}
}
感谢阅读。
本文来自博客园,作者:Circle_Table,转载请注明原文链接:https://www.cnblogs.com/Circle-Table/articles/19177402

浙公网安备 33010602011771号