题目大意:
给你一个数 V ( V ≤ 10000 ) V(V \leq 10000) V(V≤10000).让你构造出一个只含小写字母的字符串使得其逆序对恰好为 V V V.如果有多个解,输出长度最短的.如果还有解,输出字典序最小的.
题目思路:
1.这个题考虑从前往后一位一位填,找规律后应该先要看出几个结论:
1.1 最终总长度不会很长. 因为逆序对最大可以是 n 2 n^2 n2级别的.(虽然只有26个字母,但是长度大于26后我们还是可以构造一个有 n 2 n^2 n2级别的逆序对)
1.2 最终字符串一定是非递增的.
个人认为这个直觉是很强烈的.因为只有非递增的字符串才能让逆序对尽量大,才可以使得长度能控制的尽量短.
1.3.假设去重后字符串变成 s s s.那么一定满足 s i + 1 = s i + 1 s_{i+1}=s_{i}+1 si+1=si+1.这个也很好证明.
2.现在有了上面三个结论,我们自然能够想出一个算法:
2.1 先枚举长度 1 1 1到 V V V.看长度为 i i i的字符串的[最大逆序对]是多少.第一个[最大逆序对]大于等于 V V V的长度一定就是最终长度.
2.2 接着从小到大枚举[只使用前 j j j个字符]是否能够.过程跟上面类似 (因为我们在知道最终长度后,能用的字母越多,逆序对自然也会越大,但字典序也会越大!).
2.3 然后再枚举这个字母 j j j放多少.放的越少越好.
因为根据结论 1.2 1.2 1.2 和 结论 1.3 1.3 1.3.我们最后字符串一定是长这样的:
j , . . , j , j − 1 , . . . , j − 1 , j − 2 , . . , j − 2 , . . . , 1 j,..,j,j-1,...,j-1,j-2,..,j-2,...,1 j,..,j,j−1,...,j−1,j−2,..,j−2,...,1.
那么对于当前的 j j j,肯定是放的越少,最终的字符串的字典序才会越小.
2.4 递归一下这个过程,直到 j = 1 j=1 j=1时剩下的全填 a a a即可.
根据上面的过程,我们可以先记忆化搜索预处理一下: d p ( p r e , x , a f t ) dp(pre,x,aft) dp(pre,x,aft)代表前面已经放了 p r e pre pre个字符串,当前放字符 ′ a ′ + x − 1 'a'+x-1 ′a′+x−1,且后面还有 a f t aft aft个字符需要放的最大逆序对贡献.自然有一个转移( x x x必须放,因为结论1.3):
d p ( p r e , x , a f t ) = max i = 1 a f t { d p ( p r e + i , x − 1 , a f t − i ) + p r e ∗ i } dp(pre,x,aft)=\max_{i=1}^{aft}\{dp(pre+i,x-1,aft-i)+pre*i\} dp(pre,x,aft)=maxi=1aft{ dp(pre+i,x−1,aft−i)+pre∗i}.
根据结论 1.1 1.1 1.1:令 l e n = V len=\sqrt{V} len=V
上述 d p dp dp复杂度为 O ( 26 l e n 3 ) O(26len^3) O(26len3).预处理完这个东西后 d f s dfs dfs的复杂度就从指数级变成了 O ( V ) O(V) O(V)了.
3.所以总时间复杂度为: O ( 26 l e n 3 ) = O ( 26 V V ) O(26len^3)=O(26V\sqrt{V}) O(26len3)=O(26VV).
PS:实测 l e n ≤ 150 len \leq 150 len≤150且本地 90 m s 90ms 90ms以内跑完一组.自己的代码和另一种搜索+剪枝的思路跑了大概1000组对拍结果都一样.它那份代码勉强过 V = 10000 V=10000 V=10000的情况, d f s dfs dfs运行次数大概在 1.3 e 8 1.3e8 1.3e8左右.
4.问题进一步拓展:
我这个思路可以解决多次询问.做到 O ( V ) O(V) O(V)的回答.
5.代码:
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
int dp[152][26][152];
int dfs (int pre , int x , int aft)
{
int &d = dp[pre][x][aft] ;
if ( ~d ) return d;
if (x == 1) return d = pre * aft;
if (aft == 0) {
if (x != 0) return -1e9;
return 0;
}
int ans = 0;
for (int i = 1 ; i <= aft ; i++){
int res = dfs(pre + i, x - 1 , aft - i) + i * pre;
if (ans < res){
ans = res;
}
}
return d = ans;
}
void dfs2 (int pre , int x , int aft , int rest , string &res)
{
if (x == 1){
res += string(aft , 'a');
return ;
}
int len = 1e9;
for (int i = 1 ; i <= aft ; i++){
int d = dfs(pre + i , x - 1 , aft - i) + i * pre;
if (d >= rest){
len = i;
break;
}
}
res += string(len , 'a' + x - 1);
rest -= len * pre;
dfs2(pre + len , x - 1 , aft - len , rest , res);
return ;
}
int calc (string a)
{
int n = a.size();
int ans = 0;
for (int i = 0 ; i < n ; i++){
for (int j = i + 1 ; j < n ; j++){
if (a[i] > a[j]) ans++;
}
}
return ans;
}
void solve (int v)
{
memset(dp , -1 , sizeof dp);
int len = 1e9 , x = 1e9;
for (int i = 1 ; i <= 150 ; i++){
for (int j = 1 ; j <= 26 ; j++){
if (dfs(0ll , j , i) >= v){
if (len > i){
len = i , x = j;
}else if (len == i){
x = min(x , j);
}
}
}
}
string ans;
dfs2(0 , x , len , v , ans);
cout << ans << endl;
return ;
}
int main()
{
ios::sync_with_stdio(false);
int n; cin >> n;
solve(n);
return 0;
}