题解:致敬传奇捆绑测试题目 Perm
来自不知道什么时候的回忆。给定正整数 \(n\) ,一个 \(1\sim n\) 的排列 \(p\) 是一个好排列,当且仅当使得对于任意 \(1\le k<n\) ,都有 \(\sum_{i=1}^k p_i > p_{k+1}\) 。现在请你求出字典序第 小的好排列 \(p\) 。\(1\le n\le 10^6\) ,\(1\le k\le 10^{18}\) 。可是你出这个题开 Subtask 放 corner 被喷爆了……
你突然惊醒,发现你不仅只会 \(k=1\) ,而且还需要搞一场联测的 T1。这次你决定不绑 Subtask,而是一次把所有问题问完。
设 \(a_{l,i}\) 为对于 \(n=l,k=1\) 的上述问题答案的第 \(i\) 个数字,请你求出 \(\oplus_{1\le j\le i\le n}(a_{i,j}+j)\) ,其中 \(\oplus\) 代表按位异或。
Input
一行一个正整数 \(n\) 。
Output
一行一个整数表示答案。
Note
对于 \(60\%\) 的数据,保证 \(n\le 10^6\) 。
对于所有数据,保证 \(1\le n\le 10^{18}\) 。
分析
\(O(n)\) 部分分做法
手动模拟 \(k=1\) 的排列 \(p\),发现:
\(p_3\) 之后每次只会在排列末尾新加入 \(n\) 以保证最小字典序。
排列 \(p\) 每一项加上 \(j\) 得到最后统计答案的排列 \(p'\) :
统计答案时从第一项到最后一项被异或的次数由 \(n\) 递减,根据异或的性质只有被异或奇数次的会统计到答案里,判一下奇偶性即可。时间复杂度 \(O(n)\) ,可以通过 \(60\%\) 数据 。
\(O(1)\) 做法
利用 \(O(n)\) 做法打表,得到:
发现除开 \(n=1\) 其后每 \(8\) 个数一组存在规律且满足一次函数关系。直接 \(O(1)\) 算就完了。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;inline ll read() {ll otto = 0;char a = getchar();while(!isdigit(a)) a = getchar();while(isdigit(a)) {otto = (otto << 1) + (otto << 3) + (a ^ 48);a = getchar();}return otto;
}void solve(ll n) {if(n == 1) {printf("2");}else {--n;int op = n % 8;if(op == 1) printf("2");if(op == 2) printf("0");if(op == 3) printf("%lld", 10 + 16 * (n / 8));if(op == 4) printf("%lld", 10 + 16 * (n / 8));if(op == 5) printf("6");if(op == 6) printf("4");if(op == 7) printf("%lld", 22 + 16 * (n / 8));if(op == 0) printf("%lld", 22 + 16 * (n / 8));}
}int main() {freopen("perm.in", "r", stdin);freopen("perm.out", "w", stdout);solve(read());
}