[dp] ARC160F Count Sorted Arrays
posted on 2024-10-08 11:13:49 | under | source
排列个数有足足 \(n!\) 个,无法直接做,那么可不可以将信息“压缩”起来,换种方式表示排列呢?
有套路:将排列 \(P\) 表示拆成 \(n\) 个 01 串 \(C_1\dots C_n\),其中 \(C_{i,j}=[P_j\ge i]\),那么 \(P\) 与 \(C\) 构成双射。\(C_1\dots C_n\) 实际上可视为按 \(i\) 从小到大将 \(P_q=i\) 的 \(q\) 点亮。
也就是说,考虑二进制数 \(S\in \{0\dots 2^n-1\}\),让 \(i\in S\) 向 \(j\) 连有向边,其中 \(j\) 恰好比 \(i\) 多一个 \(1\),那么一条 \(0\to 2^n-1\) 的路径恰好对应一个排列。
而且 \(P\) 排好序当且仅当 \(C_1\dots C_n\) 都排好序,于是每次统计只经过排好序的点的路径总数,就是排好序的 \(P\) 的总数。简单 dp 即可,统计一次复杂度 \(O(n2^n)\)。
但是本题 \(m\) 较大,\(O(mn2^n)\) 不足以通过。感性地想,真正有效的操作应该不多,有效操作 \(x,y\) 表示存在 01 串满足 \(c_x=1\) 且 \(c_y=0\),只在有效操作时重新 dp 即可。
那么有效操作是什么量级呢?先记 \((p,q)\) 表示 \(p,q\) 是否为有效操作。现在进行有效操作 \(x,y\),我们将 01 串分为 \(A,B\) 两类,\(A\) 类满足 \(c_x=1\) 且 \(c_y=0\),\(B\) 类则不满足。
我们将证明操作前后有效操作数量单调递减:考虑 \(A\) 类串中由无效变为有效的操作,显然只能是 \(p<x\) 或 \(y<p\),考虑前者后者同理。那么必须有 \(a_p=1\),容易发现,原本 \((p,y)=1\) 但是操作后 \((p,y)=0\),那么恰好抵消掉了。
证明结束?上述讨论只考虑了 \(A\) 类串,那么 \(B\) 类串呢?\(B\) 产生的影响只可能是存在 \((p,y)=1\),使得操作后 \((p,y)\) 依旧不变,那么证明就错了。可是,若有 \(B\) 类串 \((p,y)=1\),那么 \(c_y=0\),又因为它是 \(B\) 类串所以 \(c_x=0\),于是会有 \((p,x)=1\),于是操作后 \((p,x)\) 也跟着不变,那么没有影响。
初始时 \(O(n^2)\) 个有效操作,于是 dp 部分复杂度 \(O(n^32^n)\)。
然后拿个 vector 存 \((p,q)=1\) 的 01 串,每次取出修改就好。假如 01 串操作后某些 \((p,q)\) 变为 \(0\),不必删掉,只需要每次取出时检查下是不是真的满足条件即可;假如 \((p,q)\) 变为 \(1\),扔进去就好。
一个 01 串产生变化的 \((p,q)\) 至少一端为 \(x,y\),数量为 \(O(n)\),而一次取出 \(O(2^n)\) 个串。每次那么该部分复杂度 \(O(n^32^n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ADD(a, b) a = (a + b)
#define chk(x) (!(x) || __builtin_ctz(x) == n - __builtin_popcount(x))
#define id(x, p) (((x) >> p) & 1)
#define ID(x, y) ((x) * n + (y))
//x=0时ctz出错
const int N = 15;
int n, m, x, y, a[1 << N], f[1 << N], ans;
bool vis[1 << N];
vector<int> buc[N * N];
inline void ins(int a, int b, int x) {buc[ID(a, b)].push_back(x);}
inline void upd(){
memset(f, 0, sizeof f), f[0] = 1;
for(int i = 1; i < 1 << n; ++i)
if(vis[i])
for(int j = 0; j < n; ++j)
if(id(i, j)) ADD(f[i], f[i ^ (1 << j)]);
ans = f[(1 << n) - 1];
}
signed main(){
cin >> n >> m;
for(int i = 0; i < 1 << n; ++i){
vis[i] = chk(a[i] = i);
for(int x = 0; x < n; ++x)
for(int y = x + 1; y < n; ++y)
if(id(i, x) && !id(i, y)) ins(x, y, i);
}
upd(), ans = 1;
while(m--){
scanf("%lld%lld", &x, &y);
x = (x + ans) % n, y = (y + 2 * ans) % n;
if(x > y) swap(x, y);
bool fl = false;
for(auto i : buc[ID(x, y)])
if(id(a[i], x) && !id(a[i], y)){
a[i] ^= (1 << x) | (1 << y);
if(chk(a[i])) vis[i] = true, fl = true;
else{
for(int p = 0; p < x; ++p) if(id(a[i], p)) ins(p, x, i);
for(int p = y + 1; p < n; ++p) if(!id(a[i], p)) ins(y, p, i);
}
}
buc[ID(x, y)].clear();
if(fl) upd();
printf("%lld\n", ans);
}
return 0;
}

浙公网安备 33010602011771号