A - 一道可持久化并查集好题
出题人:
通过/提交:
题解: 此题数据极小,直接模拟即可。撤销操作就是不执行前面
次操作,把撤销操作视为返回
次之前的历史版本。用state[i][j][k]
表示第
次操作后第
个点和第
个点是否联通,对于
操作,复制上一次操作之后的状态,然后考虑新增的经过
边而可到达的点对。这些新点对(记为x^y
)一定是
在一侧,
在另一侧,由于新加了
而联通。那么分别找
能到达的点放到两集合内,取两个集合各一点配对都设为联通即可
。或者直接floyd
传递闭包
。对于第
种操作,复制上一次的状态,暴力找和它联通的点数。对于第三种操作,复制的不是上一次的状态,而是第
次的状态。时间复杂度
,空间复杂度
。
#include<bits/stdc++.h>
#define maxn 100 + 10
using namespace std;
int state[maxn][maxn][maxn];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) state[0][i][i]=1;
for (int i=1;i<=m;i++)
{
int op;scanf("%d",&op);
if (op==1)
{
for (int j=1;j<=n;j++)
for (int k=1;k<=n;k++)
state[i][j][k]=state[i-1][j][k];
int a,b;scanf("%d%d",&a,&b);
vector<int>A,B;A.clear();B.clear();
for (int j=1;j<=n;j++)
{
if (state[i][a][j])A.push_back(j);
if (state[i][b][j])B.push_back(j);
}
for (int j=0;j<A.size();j++)
for (int k=0;k<B.size();k++)
{
state[i][A[j]][B[k]]=1;
state[i][B[k]][A[j]]=1;
}
}else if (op==2)
{
for (int j=1;j<=n;j++)
for (int k=1;k<=n;k++)
state[i][j][k]=state[i-1][j][k];
int a,sum=0;scanf("%d",&a);
for (int j=1;j<=n;j++)if (state[i][j][a])sum++;
printf("%d\n",sum);
}else if (op==3)
{
int k;scanf("%d",&k);
for (int j=1;j<=n;j++)
for (int l=1;l<=n;l++)
state[i][j][l]=state[i-k-1][j][l];
}
}
}
B - zhb love math
出题人:
通过/提交:
题解: 考虑每个元素
对答案的贡献,一定是一些包含
的区间,且区间除了它以外,没有
是
的因数。容易想到的是,对每个
找到包含它可行区间的最左边
和包含它可行区间的最右边
那么任何区间
都是答案,这样的区间有
个,朴素的
想法是从
开始往左往右扫,找到第一个
的
就是边界,因为
,是不能通过这题的。考虑怎么优化,当枚举到
的时候, 对于
左侧的
如果
那么对于位置
的
最右只能到
的位置!, 因为
,所以可以直接枚举
的倍数
去更新答案,从左往右扫以 pre[i]
表示
最后出现的位置,对于
如果出现过其倍数
,就有 r[pre[j]]=i-1
。同理扫着扫一遍对
维护一个 last[i]
数组就能得到答案。时间复杂度
,空间复杂度
。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
/* head */
int a[maxn], l[maxn], r[maxn], pre[maxn], last[maxn];
void solve() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
l[i] = 1, r[i] = n;
}
memset(pre, 0, sizeof pre);
memset(last, 0, sizeof last);
for (int i = 1; i <= n; i++) {
for (int j = a[i]; j <= 10000; j += a[i]) {
if (pre[j] != 0 && r[pre[j]] == n) r[pre[j]] = i - 1;
}
pre[a[i]] = i;
}
for (int i = n; i >= 1; i--) {
for (int j = a[i]; j <= 10000; j += a[i]) {
if (last[j] != 0 && l[last[j]] == 1) l[last[j]] = i + 1;
}
last[a[i]] = i;
}
ll ans = 0;
for (int i = 1; i <= n; i++) ans = (ans + 1LL * (i - l[i] + 1) * (r[i] - i + 1) % mod) % mod;
printf("%lld\n", ans);
}
int main() {
int t;
scanf("%d", &t);
while (t--) solve();
return 0;
}
C - 旅かえる
出题人:
通过/提交:
题解: 将问题反过来看就是问第 片荷叶到第 片荷叶所需要花的最短时间。就是一个简单的最短路模型,我们读入时候倒着建边,从 点开始跑一遍单源最短路就是答案!时间复杂度 ,空间复杂度 。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define maxn 100005
#define INF 0x3f3f3f3f
typedef long long ll;
using namespace std;
struct Edge
{
int to, next;
}edge[maxn*2];
int head[maxn], tot, dis[maxn];
bool vis[maxn];
void Init()
{
memset(head, -1, sizeof(head));
memset(dis, INF, sizeof(dis));
memset(vis, false, sizeof(vis));
tot = 0;
}
void add(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void spfa(int s)
{
queue<int> que;
while(!que.empty()) que.pop();
que.push(s); vis[s] = true;
dis[s] = 0;
while(!que.empty()){
int tmp = que.front();
que.pop(); vis[tmp] = false;
for (int i = head[tmp]; ~i; i = edge[i].next){
int to = edge[i].to;
if (dis[to] > dis[tmp] + 1){
dis[to] = dis[tmp] + 1;
if (!vis[to]){
que.push(to); vis[to] = true;
}
}
}
}
}
int main()
{
int T, n, x;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
Init();
for (int i = 1; i <= n; i++){
scanf("%d", &x);
if (i - x >= 1) add(i - x, i);
if (i + x <= n) add(i + x, i);
}
spfa(n);
for (int i = 1; i < n; i++){
if (dis[i] == INF) printf("-1 ");
else printf("%d ", dis[i]);
}
printf("0\n");
}
return 0;
}
D - zhb love chess
出题人:
通过/提交:
题解: 考虑动态规划 当前先手是 位置在 走到 的分数,根据他们两个人的游戏策略有:
这个转移有些奇怪,不好直接递推,但是因为行数只有 ,不妨直接讨论当前位于第 列第 行和第 行,如果当前位于 那么它只可能走向 或 ,而 下一步只能走到 也就是 可以转化为从 或 转移,同理 也可以转化为从 或 转移过来。因此最后从 到 倒着 , 就是答案,注意边界情况。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int a[2][maxn];
ll f[2][2][maxn];
int main() {
int m;
scanf("%d", &m);
for (int i = 0; i <= 1; i++) {
for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]);
}
f[0][0][m] = f[1][0][m] = a[0][m] + a[1][m];
f[0][1][m] = f[1][1][m] = a[1][m];
for (int i = m - 1; i >= 1; i--) {
f[0][0][i] = a[0][i] + max(f[1][0][i + 1], f[0][1][i + 1] + a[1][i]);
f[1][0][i] = a[0][i] + min(f[0][0][i + 1], f[1][1][i + 1] + a[1][i]);
f[0][1][i] = a[1][i] + max(f[0][1][i + 1], a[0][i] + f[0][0][i + 1]);
f[1][1][i] = a[1][i] + min(f[0][1][i + 1], a[0][i] + f[1][0][i + 1]);
}
printf("%lld\n", f[1][0][1]);
return 0;
}
E - zy love banner
出题人:
通过/提交:
题解: 读入字符串以后
扫一遍,维护连续zy
字串的长度最大值就是答案。
#include <bits/stdc++.h>
using namespace std;
char s[2005];
int main() {
scanf("%s", s + 1);
int mx = 0, cnt = 0;
int len = strlen(s + 1);
for (int i = 1; i <= len; i++) {
if (s[i] == 'z' && s[i + 1] == 'y') {
cnt++;
i++;
} else cnt = 0;
mx = max(mx, cnt);
}
printf("%d\n", mx);
return 0;
}
F - 新食堂
出题人:
通过/提交:
题解: 通过分析从 时到 时可以发现,当每两个人之间恰好相隔一个座位时,桌子长度一直增加直到每个人之间相隔两个时都无法坐下下一个人,而之后只要一个一个的增加长度就可以增加能坐下的人数,于是我们只用分析恰好只间隔一个座位就能坐下的人数就行了。也就是分析数列 可以发现,他们之间的差值是以 为底的幂,而且可以根据之前的分析可以确定这个数列就是这样增加的。所以整理之后可以得到: 其中 是数列中小于 的最大项。
#include <stdio.h>
int main()
{
int n;
int num[5]={0,1,3,5};
while(scanf("%d",&n)!=EOF)
{
if(n<=3)
{
printf("%d\n",num[n]);
continue;
}
int base=3,temp=2;
while(base+temp<n)
{
base+=temp;
temp*=2;
}
printf("%d\n",base*2-2+n);
}
}
G - 一道平衡树好题
出题人:
通过/提交:
题解: 首先得意识到,每插入一个数,函数执行的次数就是在这颗平衡树上经过的边数 ,平衡树上的一条边,就对应于一个区间,例如节点 ,假设它的直接左儿子是 ,那么当你询问往平衡树插入一个节点要经过多少条边的时候,实际上就是询问目前有多少个包含了它的区间。首先我们把序列做离散化,此时 。
对于一个树上的点
,只有某些数字插入时会经过它。假设根节点数字是
,根节点左儿子数字是
。如果插入时经过根节点,
的值一定属于
;如果经过根节点的左儿子,
的值属于
;如果经过了 ”根节点左儿子” 的右儿子,
的值属于
……因此每个点对应一个区间,如果新加入的数字属于这个区间就会经过它。插入一个数的时候,执行次数实际上是 “当前包含这个点的区间个数”
,
是访问空子树并新建节点。同时,每加入一个点也会增加一个它对应的区间。这是一个简单的单点查询、区间修改的问题,树状数组或线段树都可以胜任。至于计算这个新点对应的区间,这个值是当前它的前驱当前它的后继
当前它的前驱
当前它的后继
(只有属于这个区间的值才会和新点走一样的路径到这里),用 set
维护即可,可以设置两个哨兵
和
防止越界。
另外,在验题时候还有神仙发现另一种做法——把当前数的前驱和后继的插入执行次数取个max加一就是答案,大家可以试一试。
时间复杂度 ,空间复杂度 。
树状数组版本:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define mx 200010
ll c[mx],ans;
void add(int x,int d){for (int i=x;i<mx;i+=i&(-i))c[i]+=d;}
ll ask(int x){ll sum=0;for (int i=x;i;i-=i&(-i))sum+=c[i];return sum;}
int n,q,cnt,a[mx];
map<int,int>mp;
set<int>s;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)scanf("%d",a+i),mp[a[i]]=1;
for (auto i=mp.begin();i!=mp.end();i++)i->second=++cnt;
for (int i=1;i<=n;i++)a[i]=mp[a[i]];
s.insert(0);s.insert(n+1);
for (int i=1;i<=n;i++)
{
auto succ=s.lower_bound(a[i]);
auto pred=succ;pred--;
int l=(*pred)+1,r=(*succ)-1;
add(l,1);add(r+1,-1);
ans+=ask(a[i]);
s.insert(a[i]);
}
printf("%lld\n",ans);
return 0;
}
线段树版本:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
struct SegmentTree {
#define ls (rt<<1)
#define rs (rt<<1|1)
struct Tree {
int l, r, tag, s;
int len() { return r - l + 1; }
} t[maxn << 2];
void push_up(int rt) {
t[rt].s = t[ls].s + t[rs].s;
}
void push_down(int rt) {
if (t[rt].tag) {
if (t[ls].l != t[ls].r) t[ls].tag += t[rt].tag;
if (t[rs].l != t[rs].r) t[rs].tag += t[rt].tag;
t[ls].s += t[ls].len() * t[rt].tag;
t[rs].s += t[rs].len() * t[rt].tag;
t[rt].tag = 0;
}
}
void build(int l, int r, int rt) {
t[rt].l = l, t[rt].r = r, t[rt].tag = 0;
if (l == r) {
t[rt].s = 0;
return;
}
int m = (l + r) / 2;
build(l, m, ls);
build(m+1, r, rs);
}
void update(int l, int r, int rt) {
if (l <= t[rt].l && t[rt].r <= r) {
t[rt].s += t[rt].len();
t[rt].tag++;
return;
}
push_down(rt);
int m = (t[rt].l + t[rt].r) / 2;
if (l <= m) update(l, r, ls);
if (r > m) update(l, r, rs);
push_up(rt);
}
int query(int p, int rt) {
if (t[rt].l == t[rt].r) return t[rt].s;
push_down(rt);
int m = (t[rt].l + t[rt].r) / 2;
if (p <= m) return query(p, ls);
return query(p, rs);
}
} T;
set<int> s;
int a[maxn], b[maxn];
int main() {
int n;
scanf("%d", &n);
int up = 0, cnt = 0;
b[++cnt] = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[++cnt] = a[i];
up = max(up, a[i]);
}
b[++cnt] = up + 1;
sort(b + 1, b + cnt + 1);
T.build(1, cnt, 1);
s.insert(0), s.insert(up + 1);
ll ans = 0;
for (int i = 1; i <= n; i++) {
int p = lower_bound(b + 1, b + cnt + 1, a[i]) - b;
s.insert(a[i]);
auto it = s.find(a[i]);
int pre = *(--it);
++it;
int suf = *(++it);
int l = lower_bound(b + 1, b + cnt + 1, pre) - b;
int r = lower_bound(b + 1, b + cnt + 1, suf) - b;
T.update(l, p - 1, 1);
T.update(p, r, 1);
ans += T.query(p, 1);
}
printf("%lld\n", ans);
return 0;
}
H - 曜神拯救世界
出题人:
通过/提交:
题解: 首先将 种攻击按神器 需要的耐久降序排序,接下来枚举把第一件神器的耐久加到多少。当第一件神器的耐久恰能抵挡第 种攻击时,只需考虑前 种攻击对神器 和神器 的影响。注意 ,所以可再维护一个数组 代表当前神器 的耐久度为 时神器 的耐久度最少为多少,此时答案则为 。接下来考虑当神器 的耐久度从 变为 时对 数组的影响:对所有 ,该操作可用线段树区间操作来 实现;还有 的计算也可以用线段树来维护区间最小值,因此总的时间复杂度为 。
具体实现:显然数组
是随着
的增加单调递减的,所以上文中提到的
操作可变成找到一点
,对所有 $k\leq j< b_i,B[j]=c_i $ ,这就成了裸的线段树区间操作;可用线段树维护该节点中
数组的最大值最小值以及
的最小值,当该节点的
时则可以直接return
不用做任何修改;当该节点的
时则可以将该节点的
均改为
,
改为该区间左端点的下标加上
,同时用
标记该节点;否则就继续往该节点的两个叶子节点进行递归修改。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
/* head */
struct SegmentTree {
#define ls (rt<<1)
#define rs (rt<<1|1)
struct Tree {
int l, r, mi, mx, res, tag;
} t[maxn << 2];
void push_up(int rt) {
t[rt].mi = min(t[ls].mi, t[rs].mi);
t[rt].mx = max(t[ls].mx, t[rs].mx);
t[rt].res = min(t[ls].res, t[rs].res);
}
void push_down(int rt) {
if (t[rt].tag) {
if (t[ls].l != t[ls].r) t[ls].tag = max(t[ls].tag, t[rt].tag);
if (t[rs].l != t[rs].r) t[rs].tag = max(t[rs].tag, t[rt].tag);
t[ls].mi = t[ls].mx = t[rt].tag;
t[rs].mi = t[rs].mx = t[rt].tag;
t[ls].res = t[ls].mi + t[ls].l;
t[rs].res = t[rs].mi + t[rs].l;
t[rt].tag = 0;
}
}
void build(int l, int r, int rt) {
t[rt].l = l, t[rt].r = r, t[rt].tag = 0;
if (l == r) {
t[rt].mi = t[rt].mx = 0;
t[rt].res = l;
return;
}
int m = (l + r) / 2;
build(l, m, ls);
build(m+1, r, rs);
push_up(rt);
}
void update(int l, int r, int c, int rt) {
if (t[rt].mi >= c) return;
if (l <= t[rt].l && t[rt].r <= r && t[rt].mx <= c) {
t[rt].mi = t[rt].mx = c;
t[rt].res = c + t[rt].l;
t[rt].tag = max(t[rt].tag, c);
return;
}
push_down(rt);
int m = (t[rt].l + t[rt].r) / 2;
if (l <= m) update(l, r, c, ls);
if (r > m) update(l, r, c, rs);
push_up(rt);
}
} T;
struct node {
int a, b, c;
friend bool operator< (const node& x, const node& y) {
return x.a > y.a;
}
} p[maxn];
void solve() {
int n;
scanf("%d", &n);
int up = 0;
for (int i = 1; i <= n; i++) {
scanf("%d%d%d", &p[i].a, &p[i].b, &p[i].c);
up = max(up, p[i].b);
}
sort(p + 1, p + n + 1);
T.build(0, up, 1);
int ans = 1e9;
for (int i = 1; i <= n; i++) {
ans = min(ans, p[i].a + T.t[1].res);
T.update(0, p[i].b - 1, p[i].c, 1);
}
ans = min(ans, T.t[1].res);
printf("%d\n", ans);
}
int main() {
int t;
scanf("%d", &t);
while (t--) solve();
return 0;
}
附: 本题被验题人周神仙
用std::set
更简短优美地实现了,大家可以尝试一下。
I - 数字集合
出题人:
通过/提交:
题解: 分两种情况考虑,若 , 中有 个为 ,则集合中最后只存在 , 两个数。若 , 都不为 。考虑集合中如果可以存在 和任意其他正整数则所有非零正整数都能通过 , 被造出。首先可以通过辗转相减使 其存在于集合中。设 ,则可使 , 加入集合。则可使 加入集合,由于已知 ,因此可使 加入集合。则所有非零数都可以被造出。
#include <cstdio>
int main() {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(a==0||b==0){
if(c==a||c==b) printf("YES\n");
else printf("NO\n");
} else {
if(c!=0) printf("YES\n");
else printf("NO\n");
}
return 0;
}
J - 不过都一个套路
出题人:
通过/提交:
题解: 直接正面求复杂度是不可接受的。而区间 所有数的总和是 ,答案可以通过总和减去那些与 不互素的数求得。而对于一个数 如果它与 不互素,那么它的倍数 也与 不互素,而且构成以 为首项 为公差的等差数列。而发现 ,所以 的因数不会特别多,我们可以对 进行因数分解,枚举它的因子(可以通过状压或者爆搜实现) 地算出小于 以 为因子的数的总和,但是我们会把不互素的因子 和 把它们的倍数多算(比如算 的时候把 算上去了, 同理在 的时候也把 算上去了)因此还得把因子容斥一下才是最终答案。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
/* head */
vector<int> fac;
inline void solve() {
int n, k;
scanf("%d%d", &n, &k);
fac.clear();
for (int i = 2; i * i <= n; i++) {
if (n > 0 && n % i == 0) {
fac.push_back(i);
while (n && n % i == 0) n /= i;
}
}
if (n > 1) fac.push_back(n);
ll sum = 1LL * (2 + k) * (k - 1) / 2;
ll res = 0;
int sz = fac.size();
for (int i = 1; i < (1 << sz); i++) {
ll tmp = 1;
int flag = 0;
for (int j = 0; j < sz; j++) {
if (i & (1 << j)) {
flag++;
tmp *= fac[j];
}
}
int cnt = k / tmp;
if (flag & 1) res += tmp * cnt * (cnt + 1) / 2;
else res -= tmp * cnt * (cnt + 1) / 2;
}
printf("%lld\n", sum - res);
}
int main() {
int t;
scanf("%d", &t);
while (t--) solve();
return 0;
}
K - 踩人
出题人:
通过/提交:
题解: 简化一下题意就是多次询问无向图中 “点权前 大的点” 的诱导子图中 两点是否联通。诱导子图就是选一个点集合 ,从原图中取出 中的点,以及 “边两端点都在 内” 的边,构成的一个新图。
不难想到把所有的询问读下来,然后按照 从小到大排序离线做。那么只要每次加一个点进来,并查集维护图的信息,使得图的状态从 “点权前 大” 变成 “点权前 大” ,就能够处理连通性询问了。那么问题的关键是怎么找出新加入一个点后,所有需要加入的新边。注意到一条边需要被加入图中时,一定是它的两端点都是点权前 大了。假设一条边 的两端点分别是前 大和前 大, 会被加入图中当且仅当现在处理到点权前 大。那么不妨定义一个边的点权为 “两端点的点权排名更大的那个” ,这样只要当前的 等于边权,即可加入这条边。排序尺取即可。时间复杂度 ,空间复杂度 。
#include<bits/stdc++.h>
using namespace std;
struct query{int k,a,b,id,ans;}q[100010];
bool cmp1(const query &a,const query &b){return a.k<b.k;}
bool cmp2(const query &a,const query &b){return a.id<b.id;}
struct edge{int x,y,z;}e[100010];
bool operator<(const edge &a,const edge &b){return a.z<b.z;}
struct point{int v,id;}p[100010];
bool operator<(const point &a,const point &b){return a.v>b.v;}
int fa[100010];inline int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
int n,m,t;
int rnk[100010];
int main()
{
scanf("%d%d%d",&n,&m,&t);
for (int i=1;i<=n;i++)scanf("%d",&p[i].v),p[i].id=i;
sort(p+1,p+n+1);
for (int i=1;i<=n;i++)rnk[p[i].id]=i;
for (int i=1;i<=m;i++)scanf("%d%d",&e[i].x,&e[i].y),e[i].z=max(rnk[e[i].x],rnk[e[i].y]);
sort(e+1,e+m+1);
for (int i=1;i<=t;i++)scanf("%d%d%d",&q[i].k,&q[i].a,&q[i].b),q[i].id=i;
sort(q+1,q+t+1,cmp1);
for (int i=1;i<=n;i++)fa[i]=i;
int nm=1,nt=1;
for (int i=1;i<=n;i++)
{
while (nm<=m&&e[nm].z==i)
{
int fx=getfa(e[nm].x),fy=getfa(e[nm].y);
nm++;
if (fx==fy)continue;
fa[fy]=fx;
}
while (nt<=t&&q[nt].k==i)
{
if (q[nt].a==q[nt].b)q[nt].ans=1,nt++;else
q[nt].ans=(getfa(q[nt].a)==getfa(q[nt].b)),nt++;
}
}
sort(q+1,q+t+1,cmp2);
for (int i=1;i<=t;i++)printf("%s\n",q[i].ans?"7777777":"2333333");
return 0;
}
L - Unsuccessful hack
出题人:
通过/提交:
题解: 注意到,对于所有长度 的小写字母字符串(共 个),它们的hash值已经包括了 一共 个数,是所有hash的可能结果了。因此直接暴力对每个hash值记两个对应的串输出即可。记两个是为了防止输出和输入串一样被判为错,此时需要用另一个替换。
另解: 注意到这个hash有这样性质,假设 是一个长度是 的倍数的字符串, 是任意字符串,如果 , ,那么 。这里 号代表的是字符串的拼接。本地暴力打表即可发现长度是 的hash值为0的串就有两个,分别是 和 。只要在读入串之前加上它们之一然后原封不动地输出即可。
时间复杂度 ,空间复杂度 。
另外,在比赛中一位神仙发现了在原串后面加入444个同样字母来使得hash相等的做法,出题人并不会证明QAQ
拓展: 在取模数很大的时候,可能没有3位的hash是 的字符串,我们可以通过双向广搜找6位的字符串,倒着找的时候需要使用模意义下乘法逆元。取模数可以设定为非质数,那么改为使用中国剩余定理求逆或者拓展欧几里得法求逆即可。
#include<bits/stdc++.h>
using namespace std;
char s[1010];
int main()
{
int T;
scanf("%d",&T);
for (int i=1;i<=T;i++)
{
scanf("%s",s+1);
printf("sxj%s\n",s+1);
}
}