给定一个字符串 ,你可以任意打乱 中字符的顺序,记打乱后的字符串为 。记 的价值为将 转换为 所需的最小交换次数(交换指交换两个相邻元素)。输出价值最大的 。
若有多种答案,任意输出一种。
。
这种题都有两个方向:直接构造 和研究 还原到 的性质。
发现直接构造不好做,那么考虑研究 还原到 的性质。
显然若知道了 表示 最终要去的位置,那么把 还原到 所需的操作就是 的逆序对个数。
不难发现,对于两个满足 的位置 , 显然是最优的,因为 会增加至少一对逆序对,并且由于一个更大的数被放到前面了,所以逆序对个数并不会减少。
继续研究两个满足 的两个位置 ,考虑 ,令 ,显然有 ,那么令 。
显然所有 和 的 对逆序对个数都有 的贡献,所有 的 对逆序对个数都没有贡献。那么考虑把 换到 和把 换到 这两种操作:
- 把 换到 ,此时逆序对个数会减少 ,但是又会加上 ;
- 把 换到 ,此时逆序对个数会减少 ,但是又会加上 ;
并且两种操作都会造成一些额外的非负的来自 的逆序对个数贡献,这两种操作中对逆序对贡献更多的操作的贡献肯定至少为 ,那么让 和 相邻显然更优。
所以所有相同的字符相邻肯定是最优的。
那么枚举所有 种字符串, 计算逆序对个数即可。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int S=200005;
int n;
char a[S];
vector<int> pos[4];
int b[S],c[S];
char res[S];
inline int id(char x)
{
	return x=='A'?0:(x=='N'?1:(x=='T'?2:3));
}
inline char di(int x)
{
	return x==0?'A':(x==1?'N':(x==2?'T':'O'));
}
inline void add(int pos,int val)
{
	for(int i=pos;i<=n;i+=i&-i) c[i]+=val;
}
inline int que(int pos)
{
	int res=0;
	for(int i=pos;i>=1;i-=i&-i) res+=c[i];
	return res;
}
inline void slove()
{
	scanf("%s",a+1);
	n=strlen(a+1);
	for(int i=0;i<4;i++) pos[i].clear();
	for(int i=1;i<=n;i++) pos[id(a[i])].push_back(i);
	long long mx=-1;
	for(int i=0;i<4;i++)
	{
		for(int j=0;j<4;j++)
		{
			if(i==j) continue;
			for(int k=0;k<4;k++)
			{
				if(i==k||j==k) continue;
				for(int l=0;l<4;l++)
				{
					if(i==l||j==l||k==l) continue;
					int p=0;
					for(int &u:pos[i]) b[++p]=u;
					for(int &u:pos[j]) b[++p]=u;
					for(int &u:pos[k]) b[++p]=u;
					for(int &u:pos[l]) b[++p]=u;
					for(int o=1;o<=n;o++) c[o]=0;
					long long pre=0;
					for(int o=n;o>=1;o--)
					{
						pre+=que(b[o]-1);
						add(b[o],1);
					}
					if(pre>mx)
					{
						mx=pre;
						p=0;
						for(int &u:pos[i]) res[++p]=di(i);
						for(int &u:pos[j]) res[++p]=di(j);
						for(int &u:pos[k]) res[++p]=di(k);
						for(int &u:pos[l]) res[++p]=di(l);
					}
				}
			}
		}
	}
	res[n+1]=0;
	printf("%s\n",res+1);
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T-->0) slove();
	return 0;
}
