E - Blackout 2
题意:1−−n 的点为城市,n+1−−n+m的点为供电站,城市能实现供电的条件是至少连了一个发电站。现连了E条边,有q次操作,每次操作删去一条边,问此时有多少个城市能供上电
删边等于反向加边,然后把所有供电站都连接到超级源点0上(因为所有供电站都是等价的),用并查集维护连通性,以及连通块内编号即可,每次 cnt0 就是所需答案
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int fa[maxn];
map<int, pair<int, bool>> vis;
map<int, bool> notDis;
int find(int x) {
if(x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void solve() {
int n, m, e; cin >> n >> m >> e;
for(int i = 1; i <= n + m; i++) {
fa[i] = i;
if(i <= n) vis[i] = {1, false};
else vis[i] = {0, true};
}
vector<pair<int, int>> edge(e + 1);
for(int i = 1; i <= e; i++) {
cin >> edge[i].first >> edge[i].second;
}
int q; cin >> q;
vector<int> X(q + 1);
for(int i = 1; i <= q; i++) {
cin >> X[i];
notDis[X[i]] = true;
}
int ans = 0;
vector<int> res;
for(int i = 1; i <= e; i++) {
if(notDis[i]) continue;
int tx = find(edge[i].first);
int ty = find(edge[i].second);
if(tx == ty) continue;
if(vis[tx].second && vis[ty].second) {
fa[tx] = ty;
vis[ty].first += vis[tx].first;
} else if(vis[tx].second || vis[ty].second) {
if(!vis[tx].second) ans += vis[tx].first;
if(!vis[ty].second) ans += vis[ty].first;
fa[tx] = ty;
vis[tx].second = true;
vis[ty].second = true;
} else {
vis[ty].first += vis[tx].first;
fa[tx] = ty;
}
}
for(int i = q; i >= 1; i--) {
res.push_back(ans);
int tx = find(edge[X[i]].first);
int ty = find(edge[X[i]].second);
if(tx == ty) continue;
if(vis[tx].second && vis[ty].second) {
fa[tx] = ty;
vis[ty].first += vis[tx].first;
} else if(vis[tx].second || vis[ty].second) {
if(!vis[tx].second) ans += vis[tx].first;
if(!vis[ty].second) ans += vis[ty].first;
fa[tx] = ty;
vis[tx].second = true;
vis[ty].second = true;
} else {
vis[ty].first += vis[tx].first;
fa[tx] = ty;
}
}
for(int i = (int)res.size() - 1; i >= 0; i--) {
cout << res[i] << "\n";
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
while (T--) {
solve();
}
return 0;
}
题意:给定大小为 n∗m 的 01 矩阵,翻转行i的代价为ri, 翻转列j的代价为cj,最少操作多少次能存在一条路径,使得从 (1,1) 到 (n,m) 路径上颜色相同
分析:对于某行/列,操作两次等于不变,所以最多操作一次。考虑将行列的操作次数加入状态表示,则有 f[i][j][k][l]: i 行改变了 k 次, j 列改变了 l 次。
利用异或性质(0⨁1=1,1⨁1=0,0⨁0=0)实现转移。
注意一次只能改变行/列,行变则列不变,列变则行不变
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2005, inf = 1e18;
int a[N][N], r[N], c[N];
int n, m;
int f[N][N][2][2];
int main () {
memset (f, 0x3f, sizeof f);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> r[i];
for (int i = 1; i <= m; i++) cin >> c[i];
for (int i = 1; i <= n; i++) {
string s; cin >> s;
for (int j = 1; j <= m; j++) {
a[i][j] = s[j-1] - '0';
for (auto k : {0, 1})
for (auto l : {0, 1}) {
f[i][j][k][l] = inf;
}
}
}
for (auto i : {0, 1})
for (auto j : {0, 1})
f[1][1][i][j] = r[1] * i + c[1] * j;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (auto k : {0, 1})
for (auto l : {0, 1}) {
if (i < n) {
int sta = a[i][j] ^ k ^ a[i+1][j];
f[i+1][j][sta][l] = min (f[i+1][j][sta][l], f[i][j][k][l] + r[i+1] * sta);
}
if (j < m) {
int sta = a[i][j] ^ l ^ a[i][j+1];
f[i][j+1][k][sta] = min (f[i][j+1][k][sta], f[i][j][k][l] + c[j+1] * sta);
}
}
int ans = inf;
for (auto i : {0, 1})
for (auto j : {0, 1})
ans = min (ans, f[n][m][i][j]);
cout << ans << endl;
}
LINE Verda Programming Contest(AtCoder Beginner Contest 263)
A - Full House
输入5个数,判断是否满足两个数相等,另外三个数相等
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
void solve() {
int arr[10];
for(int i = 1; i <= 5; i++) cin >> arr[i];
sort(arr + 1, arr + 1 + 5);
if(arr[1] == arr[2] && (arr[3] == arr[4] && arr[4] == arr[5]) && arr[1] != arr[3] || (arr[1] == arr[2] && arr[2] == arr[3] && arr[4] == arr[5] && arr[1] != arr[4])) {
cout << "Yes";
} else cout << "No";
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
// int T; cin >> T;
int T = 1;
while(T--) {
solve();
}
}
B - Ancestor
找爸爸:i的爸爸是pi,问n和1之间隔了几代
挨个找过去即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
void solve() {
int n; cin >> n;
vector<int> arr (n + 1);
for(int i = 2; i <= n; i++) {
cin >> arr[i];
}
int idx = n;
int res = 0;
while(idx != 1) {
res++;
idx = arr[idx];
}
cout << res;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
// int T; cin >> T;
int T = 1;
while(T--) {
solve();
}
}
C - Monotonically Increasing
输出所有长度为n,可选数范围为1-m的严格上升序列
全排列 + 筛选
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
void dfs(int fa, int n, int m, int idx, string res) {
if(idx == n + 1) {
cout << res << "\n";
return;
}
for(int i = fa + 1; i + (n - idx) <= m; i++) {
dfs(i, n, m, idx + 1, res + to_string(i) + " ");
}
}
void solve() {
int n, m; cin >> n >> m;
dfs(0, n, m, 1, "");
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
// int T; cin >> T;
int T = 1;
while(T--) {
solve();
}
}
D - Left Right Operation
题意
进行如下操作各一次:
把
a
1
,
a
2
,
.
.
.
,
a
x
a_1,a_2,...,a_x
a1,a2,...,ax 全替换为 L;
把
a
n
−
y
+
1
,
.
.
.
,
a
n
−
1
,
a
n
a_{n−y+1},...,a_{n−1},a_n
an−y+1,...,an−1,an全替换为 R;
0≤x,y≤n
问a的和最小为多少
分析
f1[i]表示 1 ~ i 最小可能和,f2i 表示 1 ~ i 最小可能和
则答案在
f
1
i
+
f
2
i
+
1
f1_i+f2_{i+1}
f1i+f2i+1 中取(尽可能替换更多的,故枚举分界点)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
void solve() {
ll n, l, r;
cin >> n >> l >> r;
vector<ll> arr(n + 2), L(n + 2), R(n + 2);
for (int i = 1; i <= n; i++) cin >> arr[i];
for (int i = 1; i <= n; i++) {
L[i] = min(L[i - 1] + arr[i], i * l);
}
for (int i = n; i >= 1; i--) {
R[i] = min(R[i + 1] + arr[i], (n - i + 1) * r);
}
ll res = LLONG_MAX;
for (int i = 1; i <= n + 1; i++) {
res = min(res, L[i - 1] + R[i]);
}
cout << res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// int T; cin >> T;
int T = 1;
while (T--) {
solve();
}
}
E - Sugoroku 3
题意
1 ~ n−1 每个上都有一个骰子🎲,数字范围为 0 ~
a
i
a_i
ai
在i上掷出的数字为j,则走到i+j
求在n时的掷骰子次数的期望值
分析
f[i]表示i到n的期望次数(相当于逆推,以n为起点,则f[n]=0)
转移就是枚举每一种可能的步数:f[i]=(1+f[i+1]+f[i+2]+…+f[i+a[i]])/a[i]+1 (最开始的1表示投掷到了0)
后缀和优化区间求和
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
ll qpow(ll a, ll x) {
ll res = 1;
while(x) {
if(x & 1) res = res * a % mod;
a = a * a % mod;
x >>= 1;
}
return res;
}
void solve() {
int n; cin >> n;
vector<ll> A(n + 1), dp(n + 1), suf(n + 2);
for(int i = 1; i < n; i++) cin >> A[i];
for(int i = n - 1; i >= 1; i--) {
dp[i] = ((suf[i + 1] - suf[i + A[i] + 1] + A[i] + 1) % mod + mod) % mod * qpow(A[i], mod - 2) % mod;
suf[i] = (suf[i + 1] + dp[i]) % mod;
}
cout << dp[1];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// int T; cin >> T;
int T = 1;
while (T--) {
solve();
}
}
ARC 144
C - K Derangement
细节的构造题,一看这题,立马就想到了构造方法,但是还是有小瑕疵。
例如:9 2
我们先可以按2*k分组,依次分成【1,4】,【5,8】,【9,9】
如果有多余的组别,可与最后一组2*k合并【1,4】,【5,9】
此时我们只要每个组别的数字前k个数+k,后k个数-k即可,此时数列即为
3,4,1,2,7,8,9,5,6 这就是最后构造结果;
但是还要一种特殊情况(我当时就是没注意到,呜呜呜呜…)
例如 :15 4
按上面方法构造出的即是:5 6 7 8 9 10 11 12 13 14 15 1 2 3 4
但是你可以发现最后一个组别1,2…有的数还可以在这个组别内继续往前调整位置,使得字典序更小,变成 5 6 7 8 1 2 3 12 13 14 15 4 9 10 11
做题技巧:在构造题完全没思路的时候,可以尝试打表看看规律
例如官方题解:
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
//#define rep(i,a,b) for(int i=a;i<=b;i++)
//#define rep2(i,a,b) for(int i=a;i>=b;i--)
///* run this program using the console pauser or add your own getch, system("pause") or input loop */
#define sc(x) scanf("%d",&x)
#define sl(x) scanf("%lld",&x)
#define ll long long
#define pb push_back
typedef pair<int,int>PII;
const int Max=1e6+5;
const ll INF=1e15+5;
int a[Max],n;
int main(){
sc(n);int k;
sc(k);
if(n<k*2){
printf("-1\n");
return 0;
}
int num=n/(2*k);
int ans=1,i=1;
if(n%(2*k)==0){
while(num>0){
for(int j=0;j<k;j++){
a[i+j+k]=ans;
ans++;
}
for(int j=0;j<k;j++){
a[i+j]=ans;
ans++;
}
i+=2*k;
num--;
}
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
return 0;
}
while(num>=2){
for(int j=0;j<k;j++){
a[i+j+k]=ans;
ans++;
}
for(int j=0;j<k;j++){
a[i+j]=ans;
ans++;
}
i+=2*k;
num--;
}
int pre=abs(n-k);int len=n;
int j;int tmp,temp,cnt;
for(j=pre;j>=i;j--){
a[j]=len;
if(len==pre){
tmp=j;
cnt=len;
}
len--;
}
temp=a[i+k-1];
// cout<<tmp<<' '<<cnt<<"----\n";
for(int m=i+k;m<=tmp;m++){
a[m]=ans;ans++;
}
for(int j=pre+1;j<=n;j++) a[j]=ans,ans++;
for(int j=n;j>=1;j--){
if(cnt<=temp) break;
a[j]=cnt;
cnt--;
}
// if(!flag) printf("-1\n");
// else{
// for(int j=i;)
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
// }
}
Educational Codeforces Round 133 (Rated for Div. 2)
A - 2-3 Moves(Rated 800)
题意
有一数轴,hy在坐标0上,hy每次有四种操作:
- idx ± \pm ± 2
- idx ± \pm ± 3
问:最少操作几次,hy可以到达左边n
分析
贪心的角度来看,肯定优先考虑+3使步数更少,当然一直+3可能无法到达n点,现在来分析一下到达n点的“最后一公里”
-
n % 3 = = 1 n\%3==1 n%3==1
前面我们一直选择+3,最后到达了n-1的位置,一种选择是(+3, -2) = +1,那么答案就是 n / 3 + 2 n/3+2 n/3+2,有一种更优的选择是把之前的一次+3改为+2,这样由n-1位置变为n-2位置,只需要(+2)就可以到达n点,答案是 n / 3 + 1 n/3+1 n/3+1,因为我们只有一部额外操作(+2),这种情况的前提条件是前面至少有一次+3操作,所以特判1。
-
n % 3 = = 2 n\%3==2 n%3==2
直接往后+2即到达n点
Code
void solve() {
int n; cin >> n;
if(n == 1) cout << 2 << "\n";
else if(n % 3 == 0) cout << n / 3 << "\n";
else if(n % 3 == 1) cout << n / 3 + 1 << "\n";
else if(n % 3 == 2) cout << n / 3 + 1 << "\n";
}
B - Permutation Chain(Rated 800)
题意
构造一个最长排列链,输出这条链。
排列链:
1 每一个结点都是一个[1,n]的排列
2 a[i+1]必须是由a[i]交换两个元素得到
3 a[i+1]中固定点的个数必须严格小于a[i]
固定点的数量:
数组b中,b[j] = j的个数
分析
要求排列链结点的固定点的数量是严格递减的,那么最理想的状态就是每次-1,从n变为0。所以初始状态必须是b[1,2,3…n],固定点的数量为n
但是经过一次变换,我们发现n->n-2,无法变为n-1。
第二次变换开始,我们只需要把b[i]与b[1]互换,每次固定点数-1,这样总共我们就可以共造出长度为n的最长链(n, n-2, n-3, ,1,0)
Code
void solve() {
int n; cin >> n;
vector<int> arr(n);
iota(arr.begin(), arr.end(), 1);
cout << n << "\n";
for(int i = 0; i < n; i++) {
swap(arr[i], arr[0]);
for(int j = 0; j < n; j++) {
cout << arr[j] << " \n"[j == n - 1];
}
}
}
C - Robot in a Hallway
题意
有一个2*m的地图,每个格子上有一个封印,当格子解除封印后,才允许进入格子,且一个格子不允许经过两次。
hjm是一个不服输的人,他想要从(1,1)最快的走完所有格子,以彰显他在212的地位。
sct想知道他用了多久完成了这个目标,但是sct太菜了,所以他求助你们,帮他算出hjm用了多久走完所有格子。
分析
图源网络
总的来说,走完全程只有一种情况,那就是↓↑走到(i,j)
,然后从(i,j)
出发走U型。
那我们枚举U型起点(i,j)
,累计答案需要知道(1,1)走到(i,j)
和(i,j)走完U型
分别需要多长时间
设dp[i][j]
表示从(i,j)
出发走U型到(i,j^1)
的需要的最大等待(解锁)时间
Code
void solve() {
int m; cin >> m;
vector<vector<int>> lock(m + 2, vector<int>(3));
vector<vector<int>> dp(m + 2, vector<int>(3));
for(int j = 0; j <= 1; j++) {
for (int i = 1; i <= m; i++) {
cin >> lock[i][j];
}
}
lock[1][0] = -1;
for(int i = m; i >= 1; i--) {
for(int j = 0; j <= 1; j++) {
dp[i][j] = max(lock[i][j] + 1, //解锁之后才允许进入,所以最早进入ij的时间是loak[i][j]+1
max(dp[i + 1][j] - 1, // 因为(i,j)走到(i+1,j)需要1的时间,所以(i+1,j)出发的最大等待时间,相当于-1
/*为什么是比较lock[i][j^1]: 因为1层走U,需要经过[i][2],虽然2层走U,不需要经过[i][1],但是不影响答案累计
为了减少讨论,直接认为他们都需要走到[i][j^1]
*/
lock[i][j ^ 1] - (2 * (m - i + 1) - 1 - 1)
/*因为求的是等待时间,解锁减去走到[i+1,j]需要的时间即为需要在[i+1,j]上等待的时间*/)));
}
}
// pre: 走到(i,j)前一共花费了多长时间
int res = INT_MAX, pre = 0;
int x = 1, y = 0;
for(int i = 1; i <= m; i++) {
// 2 * (m - i + 1) - 1 := 点-1=边
// 为什么上面说直接让他们都走到[i][j^1]是合法的:因为答案累计是全程的等待时间+路径时间
// 虽然[i][1]->[i][2]->U->[i][1]重复了,但是pre时间就包含了[i][1]的时间,所以答案取max不影响答案统计
// 即前面的时间答案后面的等待时间,等价于后面不需要等待
res = min(res, max(pre, dp[x][y]) + 2 * (m - i + 1) - 1);
y ^= 1;
pre = max(pre + 1, lock[x][y] + 1);
x++;
if(i == m) {
res = min(res, pre);
} else {
pre = max(lock[x][y] + 1, pre + 1);
}
}
cout << res << "\n";
}
D - Chip Move
题意
给定一个n和一个k,初始时从0位置开始,第i步可以选择长度为(k + i - 1)正倍数的步长。问1~n中的每个x,从0走到x有多少种方案。
分析
朴素的状态设计可以设dp[j][i]
表示经过j步,到达i的方案数。
对于dp[j][i]
,有两种转移
- 继续加
k+i-1
,即增大第j步的倍数,dp[j][i + k + i - 1] += dp[j][i]
- 继续走一步,
dp[j + 1][i + k + i] += dp[j][i]
因为只设计j,j+1
两个状态,所以可以利用滚动数组压缩空间
Code
const int mod = 998244353;
void solve() {
int n, k; cin >> n >> k;
vector<vector<int>> dp(2, vector<int>(n + 2));
vector<int> res(n + 1);
int idx = 1;
int m = 2 * sqrt(n);
dp[0][0] = 1;
for(int j = 0; j < m; j++) {
fill(dp[idx].begin(), dp[idx].end(), 0);
idx ^= 1;
for(int i = 0; i + k + j <= n; i++) {
dp[idx][i + k + j] = (dp[idx][i + k + j] + dp[idx][i]) % mod;
// 初始状态只能走k的倍数
if(i == 0 && j == 0) continue;
dp[idx ^ 1][i + k + j + 1] = (dp[idx ^ 1][i + k + j + 1] + dp[idx][i]) % mod;
}
// 累计idx步走到i的方案数
for(int i = 1; i <= n; i++) res[i] = (res[i] + dp[idx][i]) % mod;
}
for(int i = 1; i <= n; i++) {
cout << res[i] << " ";
}
}
牛客暑假(一)
G-Lexicographical Maximum
题意
给定一个整数n,输出1-n中字典序最大的数
分析
读入一个字符串 S ,长度为 len
当 S 的前 len-1 位都是 9 时输出 S 本身
否则输出 len-1 个 9 即可
#include<bits/stdc++.h>
using namespace std;
int main()
{
string S;
cin>>S;
int len=S.length();
bool flag=true;
if(len==1){
cout<<S<<endl;
return 0;
}
for(int i=0;i<len-1;i++) {
if(S[i]!='9') {
flag=false;
break;
}
}
if(flag) cout<<S<<endl;
else for(int i=0;i<len-1;i++) cout<<'9';
cout<<endl;
}
A-Villages:Landlines
题意
在一维坐标上存在着 n 个建筑物,其中一个为发电站,剩余 n-1 个为用电建筑物 ,你可以建造一些电塔来联通发电站与建筑物。发电站与用电建筑物都有自己的接收范围,在该范围中建造电塔使该建筑物联入电力系统(无需电线),电塔之间的连接需要电线,当所有建筑物联入时,需要最短的电线长度。
分析
将所有区间按照左端点进行排序,维护一个最右边的端点,将所有左端点与该右端点比较
当左端点小于等于最右端点时:
说明两个区间是相交的,未产生间隙,不管,更新最右端点为更大的右端点
ed=max(ed,seg.second)
当左端点大于最右端点时:
说明两个区间产生了间隙,我们将间隙累加到答案中,更新最右端点
ans+=seg.first-ed
ed=seg.second
#include<bits/stdc++.h>
using namespace std;
typedef struct {
int st;
int ed;
}PII;
bool cmp(PII a,PII b)
{
return a.st<b.st;
}
int main() {
int n,st,ed,ans=0,cen,rad;
cin>>n;
PII segs[n];
for(int i=0;i<n;i++) {
cin>>cen>>rad;
segs[i].st=cen-rad;
segs[i].ed=cen+rad;
}
sort(segs,segs+n,cmp);
ed=segs[0].ed;
for(int i=1;i<n;i++) {
if(segs[i].st<=ed) ed=max(ed,segs[i].ed);
else{
ans+=segs[i].st-ed;
ed=segs[i].ed;
}
}
cout<<ans<<endl;
}
D.Link with Game Glitch
题意
有m个配方,每个配方可以使用 k × a i k × a_i k×ai 个 b i b_i bi种原料生成 k × c i k × c_i k×ci个 d i d_i di种原料。现在要求在k上乘一个权值w,使得所有配方间不存在可以循环生成无限物品的局面。要求最大化m。
分析
显然要生成无限物品,需要存在一个环且沿该环生成一轮得到的物品数目比原来更多,即为环上满足所有的边有k ( c [ i ] − a [ i ] ) > 0
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10, MOD = 1e9 + 7;
struct node{
int v;
double w;
};
vector<node> g[N];
int n, m, cnt[N];
double dis[N];
bitset<N> vis;
bool check(double x){
queue<int> q;
x = -log(x);
vis.set();
memset(dis, 0, sizeof(dis));
memset(cnt, 0, sizeof(cnt));
for(int i = 1; i <= n; i++) q.emplace(i);
while(q.size()){
int u = q.front(); q.pop();
vis.reset(u);
for(auto nxt : g[u]){
int v = nxt.v;
double w = nxt.w;
if(dis[v] > dis[u] + w + x){
dis[v] = dis[u] + w + x;
cnt[v] = cnt[u] + 1;
if(cnt[v] > n) return true;
if(!vis[v]){
q.emplace(v);
vis.set(v);
}
}
}
}
return false;
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= m; i++){
int a, b, c, d; cin >> a >> b >> c >> d;
g[b].emplace_back(node{d, -log((1.0 * c) / a)});
}
double l = 0, r = 1;
for(int i = 1; i <= 300; i++){
double mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid;
}
cout << fixed << setprecision(12) << l << endl;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 1; //cin >> t;
while(t--) solve();
return 0;
}
G. Link with Monotonic Subsequence
题意
要求构造一个长度为n nn的序列,使得序列的max ( lis ( p ) , lds ( p ) ) 尽可能的小
分析
狄尔沃斯定理:对偏序集< A , ≤ > ,设A中最长链的长度是n,则将A 中元素分成不相交的反链,反链个数至少是n。
那么有lds ( p ) = c n t ( lis§ ) ,那么显然max ( lis ( p ) , lds ( p ) ) ≥ max ( k , k n ) ≥ n 。因此直接构造 n \sqrt{n} n 个块即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 1e9 + 7;
void solve(){
int n = 0; cin >> n;
int b = ceil(sqrt(n));
while(n > 0){
for(int i = max(1ll, n - b + 1); i <= n; i++) cout << i << " ";
n -= b;
}
cout << endl;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0);
int t = 1; cin >> t;
while(t--) solve();
return 0;
}