题目链接
题目大意:
有n个城市,编号0 ~ n-1,每个城市权值为‘0’ ~ ‘9’,第i个城市可以到达第(i * i + 1) % n个城市,求经过n个城市的权值所构成长度为n的且字典序最大的字符串(1 <= n <= 150000)。
思路:
字典序最大需要保证在走到第i个城市时走所有能走的城市中权值最大的(0 <= i < n - 1)。
那么步骤就是:
- 计算当前所有能走的城市中的权值最大值maxx
- 将所有能走的城市中权值=maxx的城市下标记录下来
- ans[++ tot] = maxx;
- 重复上述步骤直到tot == n
我们可以使用^方便实现,当然我们也可以使用优先队列,让能走的城市中权值最大的排前,而不用每次筛出权值==maxx的城市,但是没什么必要,由于优先队列本身的复杂度,总的复杂度不一定更优。
如果只是这样,是不够的,因为我们只要让所有的城市权值都相等,就可让复杂度上升到n平方,显然会超时。
第i个城市可以到达(i * i + 1) % n个城市,取模会发生冲突像hash一样,而且由于本题的n很小,即hash表长小,并且如果权值==maxx的城市越多的话,冲突概率越大(剪枝效果越明显),比如x和y权值相同,但是x和y都走到z。
于是我们可以增加一个剪枝:每一层要走的权值==maxx的城市用v标记避免重复记录(即z只记录一次)
这个剪枝可以使TLE(3000ms) -> 187ms,在hdu6223 best solution中可以排第8。
代码:
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> pll;
const int mod = 1e9 + 7;
const int N = 15e4 + 10;
const int INF = 0x3f3f3f3f;
ll qpow(ll base, ll n){ll ans = 1; while (n){if (n & 1) ans = ans * base % mod; base = base * base % mod; n >>= 1;} return ans;}
ll gcd(ll a, ll b){return b ? gcd(b, a % b) : a;}
char s[N], ans[N];
int q[2][N], v[N];
int main()
{
int t;
int cas = 0;
cin >> t;
while (t --){
int n;
scanf("%d", &n);
scanf("%s", s);
int maxx = 0;
for (int i = 0; i < n; ++ i) {
maxx = max(maxx, (int)s[i]);
}
int cnt = 0, cur = 0;
for (int i = 0; i < n; ++ i) {
v[i] = 0;//标记最近使用时所在层数,用于剪枝
if (s[i] == maxx) {
q[cur][++ cnt] = i;
v[i] = 1;
}
}
int tot = 0, p;
ans[++ tot] = maxx;
while (tot < n){//搜索n层
maxx = 0, p = 0;//每层的最大值初始为0,计数器清零
for (int i = 1; i <= cnt; ++ i){
int u = q[cur][i];
u = (1ll * u * u + 1) % n;//计算下一个元素的下标
maxx = max(maxx, (int)s[u]);//更新这层最大值
q[cur ^ 1][++ p] = u;
}
ans[++ tot] = maxx;
cur ^= 1, cnt = 0;
for (int i = 1; i <= p; ++ i) {
if (v[q[cur][i]] == tot) continue;//剪枝,当前层已经算过的不算
if (s[q[cur][i]] == maxx) {//将值最大的下标放进队列,减小搜索规模
v[q[cur][i]] = tot;//更新
q[cur][++ cnt] = q[cur][i];//放进队列
}
}
}
ans[n + 1] = '\0';
printf("Case #%d: %s\n", ++ cas, ans + 1);
}
return 0;
}