251121 模拟赛
T1
给定 \(n\) 个正整数,用加号乘号和小括号组成一个表达式,最大化表达式的值。
\(n\le 10^5,~a_i\le 10^6\)
根据经典结论,把一堆 \(1\) 分组相乘,肯定是 \(3\) 个一组最优。因此我们尽可能的凑出 \(3\),凑不完的凑 \(2\) 或者 \(4\),更大的数直接乘起来。
T2
简单分讨即可
T3
有 \(n\) 把椅子排成一圈,初始时第 \(i\) 个人坐在第 \(i\) 把椅子上。有两种操作:
+ p:每个人顺时针移动 \(p\) 步,坐到新椅子上;
* p:设某个人当前下标为 \(i\),顺时针移动到 \(pi\) 对应的椅子上;如果有多个人尝试坐到同一个椅子上,移动距离小的人坐,后面的人出局;
\(n,q\le 5\times 10^5\)
先把下标转换成从 \(0\) 开始。如果 \(p\) 和 \(n\) 互质,那么显然不会有人出局。然后可以归纳的证明任意时刻场上的人总是形成一个公差为 \(n\) 的因数的等差数列。因此假设场上的人组成的集合为 \(\{(tx+a)\bmod n\mid x\in [0,\frac{n}{t})\}\),那么加法等价于给 \(a\) 做模 \(n\) 意义的加法,乘法等价于给 \(x\) 做模 \(\frac{n}{t}\) 意义的乘法。发现出现合并的充要条件是 \(\gcd(\frac{n}{t},p)>1\),此时 \(t\) 变为 \(t\times \gcd(\frac{n}{t},p)\),因此一次合并人数至少减小一半,因此合并的轮数不会超过 \(O(\log n)\) 次。
每次暴力重构后记录初始的 \(a\),然后维护 \(k,a'\) 表示刚重构完时坐在 \(tx+a\) 位置的人移动到了 \(tkx+a'\)。直接维护即可,查询时用 exgcd 求一下逆元。
代码
| #include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N = 5e5 + 10;
const int LOGN = 22;
struct myPair {
int id, dis;
inline myPair(int _id = 0, int _d = 0) : id(_id), dis(_d) {}
inline bool operator<(const myPair &b) const {
return dis < b.dis;
}
inline myPair operator+(int x) const { return (myPair){id, x}; }
};
inline void gmin(myPair &a, myPair b) {
if(a.id == 0) a = b;
else if(b < a) a = b;
}
int n, q;
myPair id[LOGN][N]; int nn;
int pa[LOGN];
int t = 1, k = 1, a = 0;
void build(int x, int d) {
++nn;
for(int i = 0; i < n / t; i++) {
int p0 = (t * i + pa[nn - 1]) % n;
int p1 = (t * (k * i % n) % n + a) * x % n;
int p2 = (t * (k * i % n) % n + a) % n;
gmin(id[nn][p1], id[nn - 1][p0] + (p1 + n - p2) % n);
}
k = 1; a = a * x % n;
t = t * d; pa[nn] = a;
}
void exgcd(int a, int b, int &x, int &y) {
if(b == 0) return x = 1, y = 0, void();
exgcd(b, a % b, y, x);
y -= a / b * x;
}
int getinv(int p, int mod) {
int x, y;
exgcd(p, mod, x, y);
return (x % mod + mod) % mod;
}
signed main() {
// freopen("chair.in", "r", stdin);
// freopen("chair.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> q;
id[nn][0] = {n, 0};
for(int i = 1; i <= n; i++) id[nn][i] = {i, 0};
pa[nn] = 0;
while(q--) {
char op; int x; cin >> op >> x;
if(op == '+') {
a = (a + x) % n;
} else if(op == '*') {
int d = __gcd(x, n / t);
if(d == 1) {
k = k * x % (n / t); a = a * x % n;
} else {
build(x, d);
}
} else if(op == '?') {
x %= n;
if(x % t != a % t) cout << -1 << '\n';
else {
x = (x - a + n) % n / t;
cout << id[nn][(t * (x * getinv(k, n / t)) + pa[nn]) % n].id << '\n';
}
} else {
for(int xx = 1; xx <= n; xx++) { x = xx % n;
if(x % t != a % t) cout << -1 << ' ';
else {
x = (x - a + n) % n / t;
cout << id[nn][(t * (x * getinv(k, n / t)) + pa[nn]) % n].id << ' ';
}
}
cout << endl;
}
}
return 0;
}
|
T4
给你一个长为 \(n\) 的排列,定义一次操作为选择两个不同的位置,将它们的值进行交换。问你有多少个长为 \(k\) 的操作序列可以把原排列变成单位排列。
\(n\le 30,~k\le 50,~T\le 10^4\)
把排列的置换环拿出来,发现交换两个数等价于置换环的合并和分裂。不难发现我们只关心置换环长度组成的可重集,这样不同的可重集数量是分拆数,在 \(n=30\) 只有 \(5604\) 个。
于是爆搜一遍做完了。时间复杂度 \(O\big(kn^2p(n)\big)\)。
代码
| #include<iostream>
#include<unordered_map>
#include<random>
#define ull unsigned long long
#define ll long long
using namespace std;
const int N = 32;
const int MOD = 1e9 + 7;
const int S = 5700;
inline void add(int &a, int b) { a += b; (a >= MOD) && (a -= MOD); }
int n, k, t;
mt19937_64 rng((random_device){}());
ull w[N];
ll pw[N][N], inv[N], fact[N], ifact[N];
struct Node {
ull cnt[N];
inline Node() { for(int i = 0; i < N; i++) cnt[i] = 0; }
inline ull &operator[](int index) { return cnt[index]; }
inline const ull &operator[](int index) const { return cnt[index]; }
inline ull hash() {
ull res = 0;
for(int i = 1; i <= n; i++) res += cnt[i] * w[i];
return res;
}
inline ll count() {
ll res = ifact[n];
for(int i = 1; i <= n; i++) res = res * fact[cnt[i]] % MOD;
for(int i = 1; i <= n; i++) res = res * pw[i][cnt[i]] % MOD;
return res;
}
};
Node s[5700]; int nn;
unordered_map<ull, int> mp;
void dfs(int pre, int sum, Node v) {
if(sum == n) {
s[++nn] = v;
mp[v.hash()] = nn;
return;
}
for(int i = pre; sum + i <= n; i++) {
v[i]++;
dfs(i, sum + i, v);
v[i]--;
}
}
int f[S], g[S];
int main() {
for(int i = 1; i < N; i++) {
pw[i][0] = 1;
for(int j = 1; j < N; j++) pw[i][j] = pw[i][j - 1] * i % MOD;
}
inv[1] = fact[0] = ifact[0] = 1;
for(int i = 2; i < N; i++) inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
for(int i = 1; i < N; i++) fact[i] = fact[i - 1] * i % MOD;
for(int i = 1; i < N; i++) ifact[i] = ifact[i - 1] * inv[i] % MOD;
cin >> n >> k >> t;
for(int i = 1; i <= n; i++) w[i] = rng();
dfs(1, 0, (Node){});
f[1] = 1;
for(int i = 1; i <= k; i++) {
for(int j = 1; j <= nn; j++) {
Node u = s[j];
if(!f[j]) continue;
for(int x = 1; x <= n; x++) {
if(!u[x]) continue;
int w1 = u[x];
u[x]--;
for(int y = x; y <= n; y++) {
if(!u[y]) continue;
int w2 = u[y]; if(x == y) w2 = (ll)w2 * 500000004 % MOD;
u[y]--;
u[x + y]++;
add(g[mp[u.hash()]], (ll)f[j] * (x * y) % MOD * w1 % MOD * w2 % MOD);
u[x + y]--;
u[y]++;
}
u[x]++;
}
for(int x = 1; x <= n; x++) {
if(!u[x]) continue;
u[x]--;
for(int y = 1; 2 * y <= x; y++) {
u[y]++;
u[x - y]++;
add(g[mp[u.hash()]], (ll)f[j] * x % MOD * (x == y << 1 ? 500000004 : 1) % MOD * (u[x] + 1) % MOD);
u[y]--;
u[x - y]--;
}
u[x]++;
}
}
for(int j = 1; j <= nn; j++) f[j] = g[j], g[j] = 0;
}
while(t--) {
Node u;
static int p[N], vis[N];
for(int i = 1; i <= n; i++) cin >> p[i], vis[i] = 0;
for(int i = 1; i <= n; i++) {
if(vis[i]) continue;
vis[i] = 1; int sz = 1;
for(int j = p[i]; j != i; j = p[j]) vis[j] = 1, sz++;
u[sz]++;
}
cout << (ll)f[mp[u.hash()]] * u.count() % MOD << '\n';
}
return 0;
}
|