导出子图
题目给了一个限制: ∣ai−bi∣≤12 。
这启发我们进行状压DP,具体来说可以维护选择了哪些联通块,然后进行一个 DP 的做就 OK 了。
#include <cstdio> #include <iostream> #include <map> #include <vector> using namespace std; typedef long long ll; const int N = 55; const int P = 998244353; ll ans; int n, m; int g[N]; map<vector<int>, ll> f[2]; int main() { cin >> n >> m; for(int i = 1; i <= m; i++) { int x, y; cin >> x >> y; if(x < y) swap(x, y); g[x] |= (1<<(12-x+y)); } f[0][vector<int>()] = 1; for(int i = 1; i <= n; i++) { f[i&1].clear(); for(auto tmp : f[(i-1)&1]) { auto now = tmp.first; auto val = tmp.second; bool flag = false; for(int j = 0; j < now.size(); j++) if(now[j] == 1) {flag = true; break;} if(flag and now.size() == 1) (ans += val) %= P; if(flag and !(g[i]&1)) continue; vector<int> to2; int np = 1<<11; for(int j = 0; j < now.size(); j++) { if(now[j]&g[i]) np |= (now[j]>>1); else to2.push_back(now[j]>>1); } to2.push_back(np); (f[i&1][to2] += val) %= P; if(flag) continue; auto to1 = now; for(int j = 0; j < to1.size(); j++) to1[j] >>= 1; (f[i&1][to1] += val) %= P; } } for(auto i : f[n&1]) if(i.first.size() == 1) (ans += i.second) %= P; cout << ans << "\n"; }
|
Delete 1,4,7,…
设 f(n) 为进行一次删除后第 n 个位置的数,手玩一下发现 f(n)=⌊23n+1⌋,这个式子是可以推广到 k 次删除的,套 k 层 f 就行了,所以我们记 fk(n)=f(f(⋯f(n)))。
我们可以很快地递推出进行 k 次操作后数列中剩下的数的个数,设其为 cnt ,我们只需要求出 ∑i=1cntfk(i) 就行了。
暴力求的话是 O(nk(23)k) 的时间复杂度,但如果 k 较小的话显然会超时。
在上面我们只找到了 fk 和 fk−1 之间的关系,如果能找到 fk(i) 与 fk(j) 之间的关系就可以进行一个递推的做了。
一个直觉上很对的等式: fk(n+2k)=fk(n)+3k ,这个式子直接拆开一层就可以得证了。
但是这样做完时间复杂度是 O(k∗2k) 的,还是不太够。
考虑进行一个类似折半搜索的东西,将 fk(i) 拆成 fx(fy(i)) ,那么 fk(i+2yj)=fx(fy(i+2yj))=fx(fy(i)+3yj)。
设 g(a,i) 为 j=0∑2i−1fx(a+3yj),那么 g(a,i)=g(a,i−1)+g(a+3y2i−1,i−1)。
在小范围内进行一个记搜就可以通过了。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int P = 998244353; ll n ,k, x, y, ans; ll lim, pw3[105]; unordered_map<ll,int> mem[105]; ll f(ll x, ll k); ll g(ll a, ll i); void work1(); void work2(); int main() { cin >> n >> k; for(int i = 1;i <= k;i++) n -= (n+2)/3; if(k > 36) work1(); else work2(); cout << ans; } void work2() { pw3[0] = 1; x = k>>1; y = k-x; lim = (1ll<<x)-1; for(int i = 1; i <= 29; i++) pw3[i] = pw3[i-1]*3 % P; for(int i = 1; i <= n and i <= (1ll<<y); i++) { ll a = f(i, y), lim = ((n - i)>>y) + 1; for(int j = 50; j >= 0; j--) if(lim>>j&1) { (ans += g(a,j)) %= P; a += pw3[y]<<j; } } } void work1() { for(ll i = 1;i <= n;i++) ans = (ans + f(i,k)) % P; } ll g(ll a, ll i) { if(a - 1 > lim) return (g(((a-1)&lim)+1, i) + ((a-1)>>x)%P * pw3[x]%P * ((1ll<<i)%P)%P) % P; if(i == 0) return mem[i][a] = f(a,x)%P; if(mem[i].count(a)) return mem[i][a]; return mem[i][a] = (g(a,i-1) + g(a + pw3[y] * (1ll << (i-1)),i-1)) % P; } ll f(ll x, ll k) { if(!k) return x; else return f((x*3+1)/2, k-1); }
|
Priorty Queue
考虑将 A 个 1 和 B 个 2 依次排列开来,并且按 1⋯n 标号,我们尝试去构造一组字典序最大的最终集合,那么字典序比它大的都无法构造,比它小的都一定可以构造。
字典序最大的集合构造方法就是在第 i 次操作插入第 i 个数就行了,遇到操作 2 就找一个不在最终集合里的数垫背。
然后再进行一个 O(n2) 的 DP 求出字典序小于等于这个的集合有多少个就 OK 了。
#include <cstdio> #include <iostream> using namespace std; const int N = 5005; const int P = 998244353; int A, B; int st[N], top, cnt; int f[2][N]; int main() { cin >> A >> B; for(int i = 1; i <= A+B; i++) { int x; cin >> x; if(x == 1) st[++top] = ++cnt; else top--; } f[0][0] = 1; for(int i = 1; i <= top; i++) { f[i&1][0] = 0; for(int j = 1; j <= st[i]; j++) f[i&1][j] = (f[i&1][j-1]+f[(i-1)&1][j-1])%P; } int ans = 0; for(int i = 1; i <= st[top]; i++) (ans += f[top&1][i]) %= P; cout << ans << "\n"; }
|