解题思路:x! + (x-1)! = (x-1)! (x + 1),所以当x == k - 1时,必然可以整除。
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)|1
typedef long long ll;
const int mx = 2e5 + 10;
const int mod = 1e9 + 7;
int main() {
int t, n;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
printf("%d\n", n-1);
}
return 0;
}
解题思路:计算1、2、3....的递增序列长度,这一个不用动,其他的都放到后面即可。
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)|1
typedef long long ll;
const int mx = 2e5 + 10;
const int mod = 1e9 + 7;
int a[mx];
int pos[mx];
int main() {
int t, n, k;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &k);
for (int i=1;i<=n;i++) {
scanf("%d", a+i);
pos[a[i]] = i;
}
int l = 1;
while (l < n && pos[l] < pos[l+1]) {
l++;
}
if (l == n)
printf("0\n");
else
printf("%d\n", 1 + (n - l - 1) / k);
}
return 0;
}
解题思路:从给定的序列中可以得出肯定a、b两个序列有一个位置的值和它相同,所以先放到a,如果a已经有了就放到b,如果都有了那么就是无解了。然后就继续贪心,一个位置的另一个序列的值就尽量取最大,也就是x<=a[i]就行。
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)|1
typedef long long ll;
const int mx = 2e5 + 10;
const int mod = 1e9 + 7;
int a[mx];
int b[mx][2];
bool v[mx];
set <int> st[2];
int main() {
int t, n, k;
scanf("%d", &t);
while (t--) {
st[0].clear();
st[1].clear();
scanf("%d", &n);
for (int i=1;i<=n;i++) {
st[0].insert(i);
st[1].insert(i);
scanf("%d", a+i);
}
bool flag = 1;
for (int i=1;i<=n;i++) {
int id;
if (st[0].count(a[i])) {
id = 0;
} else if (st[1].count(a[i])) {
id = 1;
} else {
flag = 0;
break;
}
b[i][id] = a[i];
v[i] = id ^ 1;
st[id].erase(a[i]);
}
if (flag == 0) {
puts("NO");
continue;
}
for (int i=1; i<=n; i++) {
int id = v[i];
auto it = st[id].upper_bound(a[i]);
if (it == st[id].begin()) {
flag = 0;
break;
}
it--;
b[i][id] = *it;
st[id].erase(it);
}
if (flag == 0)
puts("NO");
else {
puts("YES");
for (int i=0; i<2;i++) {
for (int j=1;j<=n;j++) {
printf("%d ", b[j][i]);
}
puts("");
}
}
}
return 0;
}
解题思路:以位置和对应值建立一条有向边,可以得到若干个圈。我们每次操作可以将某个圈减少一个点,让这个点自环。也可以合并两个圈,但是这个操作显然是没有意义的。最终要留下的是一对相邻的值形成的环即可。那么如果一个圈内有两个相邻的值,那么肯定可以进行操作之后只留下这两个相邻的值,如果没有,则需要再加一次操作让两个相邻自环的点变成一个圈即可。
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)|1
typedef long long ll;
const int mx = 2e5 + 10;
const int mod = 1e9 + 7;
set<int> st;
bool vis[mx];
int a[mx];
int main() {
int t, n, k;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i=1;i<=n;i++) {
scanf("%d", a+i);
vis[i] = 0;
}
bool flag = 0;
int ans = 0;
for (int i=1;i<=n;i++) {
if (!vis[i]) {
st.clear();
int j = i, last = -1;
while (!vis[j]) {
vis[j] = 1;
st.insert(j);
j = a[j];
}
for (auto it: st) {
if (it == last + 1) {
flag = 1;
break;
}
last = it;
}
ans += st.size() - 1;
}
}
//printf("%d %d\n", ans, flag);
if (ans == 0)
puts("1");
else
printf("%d\n", ans - (flag? 1:-1));
}
return 0;
}
解题思路:可以得出最多操作数只需要3次,0次的情况只有当这个序列是有序的时候。那么我们就只需要讨论1、2、3的情况即可,我们将三段分别标记为1、2、3。
1) 1的情况肯定是1段是有序的或者3段是有序的。那么已知一段有序的情况下其他的排列就是(2n)! - 1,减1是因为排除掉完全有序0的情况。因为1和3都可以,所以要*2。但是多算了一次1段和3段都有序的情况,所以要减去2段的全排列,n! - 1,减一原因同上。
2)算2的情况和1其实类似,都有对称性,都需要容斥减一重复,以1段举例,2次的情况要保证1-n的数都在1和2段之内,并且第三段不是(2n+1~3n)有序的,不然就是第一种情况了。那么就有,减一是排除掉1-n在第一段完全有序的情况。后面减去n!就是排除掉第三段(2n+1~3n)有序的情况。因为对称性,第三段也是如此,所以要*2。但是多算了一次1-n的数都在1和2段之内和(2n+1~3n)也在2和3段之内的情况。所以要减去这一个情况。枚举1-n的数在2段中的数量即可,然后其他的可以使用排列组合。时间复杂度O(nlogn)。
3)3n! 减去0、1、2的个数就是3的个数了。
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)|1
typedef long long ll;
const int mx = 3e6 + 10;
ll c[mx], d[mx];
ll qpow(ll x, ll y, int mod) {
ll ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
y >>= 1;
x = x * x % mod;
}
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
d[0] = c[0] = 1;
for (int i=1;i<=3*n;i++)
c[i] = c[i-1] * i % m;
ll ans = 0, sum = 1;
// 只需要一次的情况
ans += (c[2 * n] - 1) * 2 % m;
ans -= (c[n] - 1) % m;
ans = (ans + m) % m;
sum = (sum + ans) % m;
// 2次
ll c_2n_n = c[2*n] * qpow(c[n], m - 2, m) % m * qpow(c[n], m - 2, m) % m;
ll v = (c_2n_n * c[n] % m - 1) % m * (c[2*n] - c[n] + m) % m;
ans += v * 2 * 2 % m;
sum += v * 2 % m;
for (int i=1;i<=n;i++)
d[i] = qpow(c[i], m - 2, m);
// 容斥剪去相同的部分,也就是两边操作顺序都可以的
for (int i=1;i<=n;i++) {
ll c_n_i = c[n] * d[n-i] % m * d[i] % m;
ll c_2ni_n = c[2*n-i] * d[n-i] % m * d[n] % m; // c[2n-i,n]
//两边可以操作的情况
v = c_n_i * c_n_i % m * c[i] % m;
v = v * c_n_i % m * c[n-i] % m; // c[n, n-i]
v = v * (c_2ni_n * c[n] % m - 1) % m * c[n] % m;
ans = (ans - v * 2 + m) % m;
sum = (sum - v + m) % m;
}
ll c_2ni_n = c[2*n] * qpow(c[n], m - 2, m) % m * qpow(c[n], m - 2, m) % m;
v = (c[n] - 1) * (c_2ni_n * c[n] % m - 1) % m * c[n] % m; //1-n全在第一个区间的情况
ans = (ans - v * 2 + m) % m;
sum = (sum - v + m) % m;
// 三次
ans += (c[3*n] - sum + m) * 3 % m;
printf("%lld\n", (ans + m) % m);
return 0;
}