0%

hdu多校(第五场)

A. Tetrahedron

题目描述:

一个四面体,顶角是三个直角。给出一个 $ n $ ,三个直角边边长 $ a, b, c \in [1,n] $ ,顶点到地面的高 $ h $ ,求 $ \frac{1}{h^2} $ 的期望。

image-20200805185327422

数据范围:

题解:

看这个图就很迷惑,刚开始以为 $ h $ 直接是补成长方体后的对角线的一半。后来发现是高。原本的方法是使用体积相等求出 $ h $ ,底面面积使用海伦公式计算。后来发现有个定理。

定理:直角三棱锥的三个直角面面积的平方和等于底面面积的平方。

证明:

  1. 可以使用海伦公式体积相等证明一波

  2. 直接使用几何知识证明

    2020-08-05 21.15.57

知道这个定理之后就好办了。一波代换化简就可以得到 $ \frac{1}{h^2} = \frac{1}{a^2} + \frac{1}{b^2} + \frac{1}{c^2} $

故 $ E(\frac{1}{h^2}) = 3E(\frac{1}{a^2}) = 3 \times \frac{1}{n} \sum_{i=1}^n \frac{1}{i^2} $

所以线性求逆元,然后平方一下,再求一下前缀和

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
#define ll long long
#define ull long long
using namespace std;
const ll mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_LL = 0x3f3f3f3f3f3f3f3f;
const double eqs = 1e-5;
const int maxn = 100 + 10;
const int maxm = 1e5 + 10;
int t, n, m;
ll inv[maxn], sum[maxn];
void init(int n)
{
inv[0] = inv[1] = 1;
for(int i = 2; i <= n; i++)
{
inv[i] = ((mod - mod / i) * inv[mod % i]) % mod;
}
sum[0] = 0;
for(int i = 1; i <= n; i++)
{
sum[i] += (sum[i-1] + inv[i] * inv[i] % mod) % mod;
}
}
ll solve(int t)
{
return (3 * inv[t]) % mod * sum[t] % mod;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
init(maxn - 5);
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
printf("%lld\n", solve(n));

}
return 0;
}

B. Funny String

题目描述:

数据范围:

题解:

需要使用后缀数组,刚好之前写过模板。

代码:

1
2


C. Boring Game

题目描述:

$ n $ 张纸折叠 $ k $ 次,然后从上往下标 $ 1-n $ ,求展开之后的序列。

1213221

数据范围:
$ 1\le T \le 30 \\ 1\le n \le 200, 1\le k \le 10 \\ \sum 2\times n \times 2^k \le 10^6 $

题解:

直接模拟,将上半部分反转放在左侧,想象 $ n $ 张纸展开的情况,每次截取上半部分,倒置(左转 $ 90 $ 度)后将其放置于剩余部分的左侧,模拟 $ k $ 次即可

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const ll INF_LL = 0x3f3f3f3f3f3f3f3f;
const double eqs = 1e-5;
const int maxn = 1e6 + 10;
const int maxm = 1e5 + 10;
int t, n, m, k;

vector<int> node[maxn];
int main()
{
// #define COMP_DATA
#ifndef ONLINE_JUDGE
#ifdef COMP_DATA
freopen("/mnt/f/文档/课件/ACM比赛/2020hdu多校/第四场/data/E.in", "r", stdin);
freopen("out.txt", "w", stdout);
#else
freopen("in.txt", "r", stdin);
#endif
#endif
scanf("%d", &t);
while (t--)
{
int n, k, len;
scanf("%d%d", &n, &k);
len = 2 * n * pow(2, k);
for (int i = 1; i <= len; i++)
node[i].clear();
for (int i = 1; i <= len; i++)
{
int a;
scanf("%d", &a);
node[i].push_back(a);
}
int mid = 1;
while (k--)
{
mid = (mid + len) / 2;
for (int j = mid + 1; j <= len; j++)
{
int pos = mid - (j - (mid + 1));
reverse(node[pos].begin(), node[pos].end());
node[j].insert(node[j].begin(), node[pos].begin(), node[pos].end());
node[pos].clear();
}
}
for (int i = len - 2 * n + 1; i <= len; i++)
{
for (int j = 0; j < node[i].size(); j++)
{
if (j == node[i].size() - 1 && i == len)
printf("%d", node[i][j]);
else
printf("%d ", node[i][j]);
}
}
printf("\n");
}
return 0;
}

D.

题目描述:

数据范围:

题解:

代码:

1
2


E.

题目描述:

数据范围:

题解:

代码:

1
2


F.

题目描述:

数据范围:

题解:

代码:

1
2


G. Tree

题目描述:

一棵无根树,带有边权。找出一个连通子图,权值最大,而且度数超过 $ k $ 的结点最多只有一个。

数据范围:
$ 1\le n \le 2 \times 10^5 ,0\le k \le n \\ 1\le u, v\le n ,0\le d \le 10^9 \\ \sum n \le 10^6 $

题解:

看着应该像是一个树形 $ dp $ 的题目,奈何树形 $ dp $ 不太会,所以不知道怎么搞。后面学习树形 $ dp $ 再好好看看

题解中的方法:

  • 首先 $ k=0 $ 时答案显然为 $ 0 $

  • 对于最终所求的连通块,要求不超过一个点的度大于 $ k $ ,其余点的度均小于等于 $ k $ 的树,采用树形 $ dp $ 。

  • 首先将原树看成一颗有根树,根节点任意:

    • 我们假设 $ dp[x] [0] $ 表示 $ x $ 为根节点,所有节点儿子数量均小于等于 $ k-1 $ 的点的子连通图最大边权和。
    • $ dp[x] [1] $ 表示 $ x $ 点为根节点,有不超过 $ 1 $ 个节点儿子数量大于 $ k-1 $ ,其余节点儿子个数等小于等于 $ k-1 $ 的子连通图最大边权和。
    • 只要保证点儿子数量均不超过 $ k-1 $ ,那么显然该点度不会超过 $ k $ 。
  • 转移方程:

    • 假设 $ x $ 有 $ c $ 个儿子,表示为 $ s_1,s_2….s_c $ ,我们首先将儿子节点按 $ dp[s_i] [0] $ 由大到小排序。

    • 可以得到 $ dp[x][0]= \sum_{i=1}^{k-1}dp[s_i][0]+w_{xs_i} $ ,其中 $ w_{xs_i} $ 表示 $ x $ 到 $ s_i $ 点的边的边权。

    • 而对于 $ dp[x] [1] $ ,由于度大于 $ k $ 的不超过一个,那最多只能有 $ 1 $ 个点儿子数量大于 $ k-1 $

      • 若该点为 $ x $ 点,则有

      • 而若该点为x点的子孙节点,则选取 $ x $ 的 $ k-2 $ 个儿子的 $ dp[0] $ 以及 $ 1 $ 个儿子的 $ dp[1] $ ,可以先贪心的将最大的 $ k-2 $ 个 $ dp[0] $ 取下,由于 $ dp[1] $ 只需要取一个,那么有两种取法:

        • 第一种为取前 $ k-1 $ 个点中的某点的 $ dp[1] $ ,那么 $ dp[0] $ 就少了一个,需要将 $ dp[s_{k-1}][0] $ 一并选取,即有

        • 第二种为取 $ k-2 $ 个节点后的某个节点,有

  • 最终方案显然是可以选取某个点的 $ dp[x][1] $ ,但是存在一种情况,即选取了某一个节点和其 $ k $ 个子树,而没有将联通块和该节点的父亲节点相连,这时候答案为该节点的 $ k-1 $ 个儿子节点 $ s_i $ 的 $ dp[s_i][0] $ 以及一个儿子节点的 $ dp[s_i][1] $ 组成,做法和求该节点 $ dp[x][1] $ 相同,只是多选了一个儿子节点的 $ dp[0] $ 而已,在 $ dfs $ 的时候顺便求了即可。

  • 时间复杂度为 $ O(n \log n) $

代码:

题解代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair <ll,ll> pii;
#define rep(i,x,y) for(int i=x;i<y;i++)
#define rept(i,x,y) for(int i=x;i<=y;i++)
#define per(i,x,y) for(int i=x;i>=y;i--)
#define all(x) x.begin(),x.end()
#define pb push_back
#define fi first
#define se second
#define mes(a,b) memset(a,b,sizeof a)
#define mp make_pair
#define dd(x) cout<<#x<<"="<<x<<" "
#define de(x) cout<<#x<<"="<<x<<"\n"
#define debug() cout<<"I love Miyamizu Mitsuha forever.\n"
const int inf=0x3f3f3f3f;
const int maxn=2e5+5;
int n,k;
ll dp[maxn][3];
vector<pii> v[maxn];
ll ans=0;
pii val[maxn];
ll prefix[maxn],suffix[maxn];

bool comp(const pii &s1,const pii &s2)
{
return s1.fi>s2.fi||(s1.fi==s2.fi&&s1.se>s2.se);

}
ll getinit(int n)
{
sort(val+1,val+1+n,comp);
prefix[0]=0;
rept(i,1,n) prefix[i]=max(prefix[i-1],val[i].se-val[i].fi);
suffix[n+1]=0;
for(int i=n;i>=1;i--) suffix[i]=max(suffix[i+1],val[i].se);
}

ll getval(int n,int cnt)
{
if(cnt<=0) return 0;
ll sum=0,ans=0;
rept(i,1,min(n,cnt))
{
sum+=val[i].fi;
}
if(n<=cnt) return sum+prefix[n];
ans=max(ans,sum+prefix[cnt]);
ans=max(ans,sum-val[cnt].fi+suffix[cnt+1]);
return ans;
}

void dfs(int id,int f)
{
int cnt=0;
rep(i,0,v[id].size())
if(v[id][i].fi!=f) dfs(v[id][i].fi,id);
rep(i,0,v[id].size())
{
if(v[id][i].fi!=f)
{
cnt++;
val[cnt].fi=dp[v[id][i].fi][0]+v[id][i].se;
val[cnt].se=dp[v[id][i].fi][1]+v[id][i].se;
}
}
getinit(cnt);
dp[id][0]=dp[id][1]=0;
rept(i,1,cnt) dp[id][1]+=val[i].fi;
rept(i,1,min(cnt,k-1)) dp[id][0]+=val[i].fi;
dp[id][1]=max(dp[id][1],getval(cnt,k-1));
ans=max(ans,getval(cnt,k));
ans=max(ans,dp[id][1]);
}

void test()
{
ans=0;
cin>>n>>k;
rept(i,1,n) v[i].clear();
rep(i,1,n)
{
int a,b,d;
cin>>a>>b>>d;
v[a].pb(mp(b,d));
v[b].pb(mp(a,d));
}
if(k==0)
{
cout<<"0\n";
return ;
}
dfs(1,-1);
cout<<ans<<"\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int q;
cin>>q;
while(q--) test();
return 0;
}

H.

题目描述:

数据范围:

题解:

代码:

1
2


I. Paperfolding

题目描述:

一张纸,有 $ n $ 次操作,每次可以选择横折或竖折,最后从中间切个十字形,得到若干纸片。问纸片数量的期望。

数据范围:
$ 1\le T \le 10^5 \\ 0\le n \le 10^{18} \\ mod (998244353) $

题解:

对于横折 $ x $ 竖折 $ y $ 可以得到 $ (2^x + 1)(2^y + 1) $ , $ x+y = n $

期望为

需要使用错位相减法进行求解,然后使用二项式定理合并。

得到公式后由于只涉及到 $ n $ ,所以直接快速幂就行了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
#define ll long long
#define ull long long
using namespace std;
const ll mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_LL = 0x3f3f3f3f3f3f3f3f;
const double eqs = 1e-5;
const int maxn = 100 + 10;
const int maxm = 1e5 + 10;
int t;
ll n;
ll qpow(ll a, ll b, ll mod)
{
ll ans = 1, tmp = a;
while(b)
{
if(b & 1) ans = ans * tmp % mod;
tmp = tmp * tmp % mod;
b >>= 1;
}
return ans % mod;
}

ll solve(ll n)
{
ll pow2n = qpow(2, n, mod);
ll pow3n = qpow(3, n, mod);
ll pow2ninv = qpow(pow2n, mod - 2, mod);
return ((pow2n + 1) % mod + 2LL * pow3n % mod * pow2ninv % mod) % mod;
}

int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
scanf("%d", &t);
while(t--)
{
scanf("%lld", &n);
printf("%lld\n", solve(n));
}
return 0;
}

J.

题目描述:

数据范围:

题解:

代码:

1
2


K. Exam

题目描述:

$ n $ 场考试,每个考试有两个时间段 $ [a, a+at] $ , $ [b, b + bt] $ ,选择一个时间段参加,而且两场考试的时间不能重叠,就是不能有交集。如果可以找到方案则输出最早的结束时间,否则输出 $ -1 $

数据范围:
$ T \\ 1\le n \le 25000 \\ 0\le a, at, b,bt \le 10^9 \\ \sum n \le 10^5 $

$ time\ limit:5500ms $

题解:

很明显能否找到是一个是一个 $ 2-sat $ 问题,但是需要找出最早的结束时间,发现该时间满足单调性,可以使用二分答案。

设 $ [a,a+at] $ 选择与否为变量 $ i $ , $ [b,b+bt] $ 选择与否为变量 $ i’ $

  • $ \to $ 表示连边,假设当前二分的值为 $ mid $ ,如果 $ a+at>mid $ ,则说明考试 $ i’ $ 是必选的, $ i \to i’ $ ,如果 $ b+bt>mid $ ,则说明考试 $ i $ 是必选的, $ i’ \to i $ 。

    image-20200807000215804

  • 剩下只需要将有冲突的考试进行连边。

  • 如果 $ [a_i,a_i+at_i] $ 与 $ [a_j,a_j+at_j] $ 有交,则说明是冲突的, $ i \to j’ , j \to i’ $ 。

  • 如果 $ [a_i,a_i+at_i] $ 与 $ [b_j,b_j+bt_j] $ 有交,则说明是冲突的, $ i \to j , j’ \to i’ $ 。

  • 如果 $ [b_i,b_i+bt_i] $ 与 $ [a_j,a_j+at_j] $ 有交,则说明是冲突的, $ i’ \to j’ , j \to i $ 。

  • 如果 $ [b_i,b_i+bt_i] $ 与 $ [b_j,b_j+bt_j] $ 有交,则说明是冲突的, $ i’ \to j , j’ \to i $ 。

  • 显然直接暴力建边的话时间和空间复杂度都是 $ n^2 $ 的。

  • 对于每一场考试 $ i $ ,我们发现只需要考虑其他的考试 $ j $ , $ a_i \leqslant a_j \leqslant a_i+at_i $ ,就可以包括所有情况

  • 如果将所有考试按开始时间排序,注意到与每一场考试 $ i $ 有冲突考试 $ j $ 都是一段连续的区间

  • 可以通过线段树优化建图在 $ n \log n $ 的时间内完成建图

  • 时间复杂度 $ O( n \log^2 n ) $

  • 空间复杂度 $ O( n \log n ) $

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<string>
#include<stack>
#include<map>
#include<set>
#include<queue>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#include<unordered_set>
#define PI acos(-1)
#define pb push_back
#define all(x) x.begin(),x.end()
#define INF 0x3f3f3f3f
#define dd(x) cout<<#x<<" = "<<x<<","
#define de(x) cout<<#x<<" = "<<x<<"\n"
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+5;
struct node{
int id1,id2,l,r;
};
node p[2*N];
int a[N],at[N],b[N],bt[N],ls[10*N],rs[10*N],cnt,n;
vector<int> v[10*N];
int build1(int l,int r){
int now=++cnt;
if(l==r){
v[now].pb(p[l].id2);
return cnt;
}
int mid=(l+r)/2;
ls[now]=build1(l,mid);
rs[now]=build1(mid+1,r);
v[now].pb(ls[now]);
v[now].pb(rs[now]);
return now;
}
int build2(int l,int r){
int now=++cnt;
if(l==r){
v[p[l].id1].pb(cnt);
return cnt;
}
int mid=(l+r)/2;
ls[now]=build2(l,mid);
rs[now]=build2(mid+1,r);

v[ls[now]].pb(now);
v[rs[now]].pb(now);
return now;
}
void link1(int id,int ql,int qr,int l,int r,int k){
if(ql>r||qr<l){
return;
}
else if(ql<=l&&qr>=r){
v[id].pb(k);
return;
}
int mid=(l+r)/2;
link1(id,ql,qr,l,mid,ls[k]);
link1(id,ql,qr,mid+1,r,rs[k]);
}
void link2(int id,int ql,int qr,int l,int r,int k){
if(ql>r||qr<l){
return;
}
else if(ql<=l&&qr>=r){
v[k].pb(id);
return;
}
int mid=(l+r)/2;
link2(id,ql,qr,l,mid,ls[k]);
link2(id,ql,qr,mid+1,r,rs[k]);
}
bool cmp(node a,node b){
if(a.l!=b.l)
return a.l<b.l;
return a.r<b.r;
}
int dfn[10*N],low[10*N],belong[10*N],cnt1,cnt2;
int s[10*N],top;
void tarjan(int x){
dfn[x]=low[x]=++cnt1;
s[++top]=x;
for(int i=0;i<v[x].size();i++){
if(!dfn[v[x][i]]){
tarjan(v[x][i]);
low[x]=min(low[x],low[v[x][i]]);
}
else if(!belong[v[x][i]]){
low[x]=min(low[x],dfn[v[x][i]]);
}
}
if(dfn[x]==low[x]){
cnt2++;
while(1){
belong[s[top]]=cnt2;
if(s[top]==x){
top--;
break;
}
top--;
}
}
}
int temp[2*N];
int rt1,rt2;
void init(){
for(int i=1;i<=cnt;i++){
v[i].clear();
}
cnt=0;
for(int i=1;i<=n;i++){
p[i]={cnt+1,cnt+2,a[i],a[i]+at[i]};
p[i+n]={cnt+2,cnt+1,b[i],b[i]+bt[i]};
cnt+=2;
}
sort(p+1,p+2*n+1,cmp);
for(int i=1;i<=2*n;i++){
temp[i]=p[i].l;
}
rt1=build1(1,2*n);
rt2=build2(1,2*n);
for(int i=1;i<=2*n;i++){
int pos1=lower_bound(temp+1,temp+2*n+1,p[i].l)-temp;
int pos2=upper_bound(temp+1,temp+2*n+1,p[i].r)-temp-1;
link1(p[i].id1,pos1,i-1,1,2*n,rt1);
link1(p[i].id1,i+1,pos2,1,2*n,rt1);
link2(p[i].id2,pos1,i-1,1,2*n,rt2);
link2(p[i].id2,i+1,pos2,1,2*n,rt2);
}
}
bool check(int mid){
for(int i=1;i<=cnt;i++){
dfn[i]=low[i]=belong[i]=0;
}
cnt1=cnt2=top=0;
int num=0;
for(int i=1;i<=n;i++){
if(a[i]+at[i]>mid){
v[num+1].pb(num+2);
}
if(b[i]+bt[i]>mid){
v[num+2].pb(num+1);
}
num+=2;
}
for(int i=1;i<=2*n;i++){
if(!dfn[i]){
tarjan(i);
}
}
num=0;
for(int i=1;i<=n;i++){
if(a[i]+at[i]>mid){
v[num+1].pop_back();
}
if(b[i]+bt[i]>mid){
v[num+2].pop_back();
}
num+=2;
}
for(int i=1;i<=2*n;i+=2){
if(belong[i]==belong[i+1])
return false;
}
return true;
}

int c[2*N];
int main()
{
int t;
scanf("%d",&t);
while(scanf("%d",&n)!=EOF){
t--;
int l=n,r=0;
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&a[i],&at[i],&b[i],&bt[i]);
c[++r]=a[i]+at[i];
c[++r]=b[i]+bt[i];
}
init();
if(!check(2e9)){
printf("-1\n");
continue;
}
sort(c+1,c+r+1);
int ans=-1;
while(l<=r){
int mid=(l+r)/2;
if(check(c[mid])){
ans=c[mid];
r=mid-1;
}
else{
l=mid+1;
}
}
printf("%d\n",ans);
}
}

L. Set1

题目描述:

给定一个 $ 1-n $ 的集合S,每次删除当前集合中最小的元素,再随机删掉 $ 1 $ 个元素,直到 $ |S| = 1 $ ,求每个元素最后被留下来的概率。

数据范围:
$ 1\le T \le 40\\ 1\le \sum n \le 5\times 10^6 \\ mod(998244353) $

题解:

首先前一半元素概率为零。

对于元素 $ i $ ,前面有 $ i-1 $ ,后面有 $ n-i $ ,当 $ n-i \le i-1 $ 时, $ i $ 才可能留下。

从前面选 $ n-i $ 个和后面的对应将后面的删掉 $ C_{i-1}^{n-i} \times (n-1)! $ 注意阶乘,对后面的全排列一下进行对应

对于前面剩下的没有匹配的 $ i-1 - n + i = 2 \times i - n - 1 $ 个元素两两配对

因此 $ i $ 被留下的方案数为

总方案数为 $ sum = \sum_{i=1}^{n} cnt[i] $

其实也可以打表,就类似 $ dp $ 那样

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 998244353;
const int INF = 0x3f3f3f3f;
const ll INF_LL = 0x3f3f3f3f3f3f3f3f;
const double eqs = 1e-5;
const int maxn = 5e6 + 10;
const int maxm = 1e5 + 10;
int t, n, m, k;

ll ifac[maxn], fac[maxn];

ll qpow(ll a, ll b, ll mod)
{
ll ans = 1, tmp = a;
while (b)
{
if (b & 1)
ans = ans * tmp % mod;
tmp = tmp * tmp % mod;
b >>= 1;
}
return ans % mod;
}
void init(int n)
{
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = fac[i - 1] * i % mod; // 算出 n!
ifac[n] = qpow(fac[n], mod - 2, mod);
for (int i = n - 1; i >= 0; i--)
ifac[i] = (i + 1) * ifac[i + 1] % mod;
}

ll nCm(int n, int m)
{
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
ll cnt[maxn];
ll solve()
{
ll sum = 0;
for (int i = 1; i <= n; i++)
{
int l = i - 1;
int r = n - i;
int tmp = 2 * i - n - 1;
if (r <= l)
{
cnt[i] = nCm(i - 1, n - i) * fac[n - i] % mod * fac[tmp] % mod * qpow(ifac[2], tmp / 2LL, mod) % mod * ifac[tmp / 2] % mod;
sum = (sum + cnt[i]) % mod;
}
else
{
cnt[i] = 0;
}
}
sum = qpow(sum, mod - 2, mod);
for (int i = 1; i <= n; i++)
{

printf("%lld", cnt[i] * sum % mod);
if (i == n)
printf("\n");
else
printf(" ");
}
}

int main()
{
// #define COMP_DATA
#ifndef ONLINE_JUDGE
#ifdef COMP_DATA
freopen("/mnt/f/文档/课件/ACM比赛/2020hdu多校/第四场/data/E.in", "r", stdin);
freopen("out.txt", "w", stdout);
#else
freopen("in.txt", "r", stdin);
#endif
#endif
init(maxn - 5);
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
solve();
}
return 0;
}