苹果又来讲课的记录
太困难。
苹果的 dp 划分:带环转移和不带环转移,不带环转移分为区间 dp 和顺序 dp。
吃鱼问题
有 \(n\) 条鱼,每条鱼有大小 \(a_i\),一个鱼可以吃旁边的鱼(吃完之后新的接上来),如果 \(a_i\ge a_j\) 就直接吃,否则需要 \(1\) 点能量。
问最后 \(i=1,2,\dots,n\) 吃掉所有鱼的最小能量。\(n\le 10^6\)。
Solution:
比较容易的是 \(O(n^2)\) 的区间 dp。转移是能不能吃掉左右的鱼,最后求 \(f(i,i)\)。
观察是 \(f(l,r)=O(\log nV)\) 因为翻倍性质。进行换维,\(f(l,x)\) 表示 \(f(l,r)=x\) 的最大 \(r\)。
考虑转移。扫描线 \(l\),\(l\) 转移是 \(f(l+1,r)=f(l,r)+[s_r-s_l\le a_l]\),这个相当于加了一个断点。
然后 \(r\) 转移也是加(值域)断点。最后求答案相当于把这些区间每次搞个区间 \(\max\)。怎么不在 \(n\log n\) 个区间上又带个 \(\log\)??方法是在 ST 表上打上那两个区间的 tag,最后 dfs 一下 ST 表下放标记即可。
据说可以线性。
一个扩展是 \(a_i\ge a_j+k\) 才能吃。这个翻倍性质变为 \(sum-k\) 翻倍。但是此时注意到 \(s_r-s_{l-1}\) 的区间有 \(f(l,r)=r-l+1\) 所以也可以做。
UOJ 主旋律
统计 DAG、强连通图一类的方法:
DAG 子图(边):
钦定一个集合 \(T\subset S\) 作为零入度点,然后进行计算。
这里的 \(g(A,B)\) 是 \(2\) 的 \(A\) 到 \(B\) 连边的次方。苹果说这里的重要是每个图被统计了 \(1\) 遍(但是这好像就是容斥原理。。),这是因为
强连通图大致相同,比如这道:
钦定一个集合 \(T\subset S\) 表示在缩点后的零入度点的点的集合。这个时候剩下的 \(S-T\) 和 \(T\to S-T\) 的边可以随意定向不影响 \(T\) 的性质。但是 \(T\) 内部不能有环。那么只需要设 \(g_T\) 是这样 \(T\)(独立于原图)的方案数。接下来我容斥 \(T\):钦定强连通分量,如果有 \(c\) 个那么获得 \((-1)^{c+1}\) 的系数。
转移 \(g\) 即枚举一个强连通分量集合乘上 \(f\)(\(f\) 不满足是强连通分量性质,所以要容斥),保证唯一性是要包含 lowbit。因为增加了强连通分量需要乘上 \(-1\)。先转移 \(g\)。
这里有一个问题:我目前没有/没法计算 \(g\) 只有一个强连通分量转移过来的情况(因为这依赖 \(f_S\))。但是恰好这样的情况无需考虑;这只会干扰 \(g_S\to f_S\) 的情况,而这种情况恰好只有一个强连通分量,是被 ban 掉的。最后才加上修正 \(g_S\gets g_S+f_S\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int f[(1<<15)+5],g[(1<<15)+5],n,m,a[16][16];
bitset<210> ed[(1<<15)+5][2];
int geted(int S,int T){
return (ed[S][0]&ed[T][1]).count();
}
int pw[40430];
signed main(){
pw[0]=1;
for(int i=1;i<=2192;i++)pw[i]=pw[i-1]*2%mod;
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;cin>>x>>y;
a[x][y]=1;
for(int j=0;j<(1<<n);j++){
if(j&(1<<x-1))ed[j][0][i]=1;
if(j&(1<<y-1))ed[j][1][i]=1;
}
}
g[0]=mod-1,f[0]=1;
for(int S=1;S<(1<<n);S++){
int t=S&(-S);
for(int T=(S-1)&S;T;T=(T-1)&S)if(T&t)(g[S]+=(mod-1)*f[T]%mod*g[S-T]%mod)%=mod;
f[S]=pw[geted(S,S)];
for(int T=S;T;T=(T-1)&S)
(f[S]+=(mod-1)*g[T]%mod*pw[geted(T,S-T)+geted(S-T,S-T)])%=mod;
(g[S]+=f[S])%=mod;
}
cout<<f[(1<<n)-1]<<endl;
return 0;
}
Calculate Cactus
(边)子图边仙人掌计数,有点 🐒。
MEX counting
设 \(f(i,j,k)\) 是考虑了 \(1\sim i\),\(mex=j\),有 \(k\) 种数 \(>mex+1\) 的。
这样的状态能施行是因为钦定了 \(i,j,k\) 一样的所有情况答案相等。因为转移,这些状态确实相等。
然后列出转移式最重要的情况就是加入了 \(j\)。
对于一个 \(i\),设这个时候的辅助数组 \(g_{j,k}\),我想干的事情是多步换成单步。由于我们的钦定,考虑 bitmask:可能从每个扩张 mex 的方案转移过来,也就是说,单步的 \((j,k)\) 有 \(k'+1\) 种方案从 \((j-1,k'+1)\) 的状态转移过来。
还有一种方法是直接考虑多步然后前缀和,但是泛用性没有辅助数组强。
苹果:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 2000 + 5, Mod = 998244353;
ll n = 0, k = 0, b[N] = {}, p = 0, A[N][N] = {};
ll dp[N][N] = {}, g[N][N] = {}, ans = 0;
int main(){
scanf("%lld %lld", &n, &k);
for(ll i = 1 ; i <= n ; i ++) scanf("%lld", &b[i]);
dp[0][0] = 1;
for(ll i = 0 ; i <= n ; i ++){
A[0][i] = 1;
for(ll j = 1 ; j <= i ; j ++) A[j][i] = A[j - 1][i] * (i - j + 1) % Mod;
}
for(ll i = 1 ; i <= n ; i ++){
for(ll mex = max(1ll, p) ; mex <= min(b[i] + k, n) ; mex ++) for(ll j = 0 ; j <= i ; j ++) g[j][mex] = (dp[j][mex - 1] + g[j + 1][mex - 1] * (j + 1)) % Mod;
for(ll j = 0 ; j <= i ; j ++) for(ll mex = p ; mex <= min(b[i] + k, n) ; mex ++){
if(j) g[j][mex] = (g[j][mex] + dp[j - 1][mex]) % Mod;
g[j][mex] = (g[j][mex] + dp[j][mex] * (j + mex)) % Mod;
}
for(ll j = 0 ; j <= i ; j ++) for(ll mex = p ; mex <= min(b[i - 1] + k, n) ; mex ++) dp[j][mex] = 0;
for(ll j = 0 ; j <= i ; j ++) for(ll mex = max(p, b[i] - k) ; mex <= min(b[i] + k, n) ; mex ++) dp[j][mex] = g[j][mex];
for(ll j = 0 ; j <= i ; j ++) for(ll mex = p ; mex <= min(b[i] + k, n) ; mex ++) g[j][mex] = 0;
p = max(p, b[i] - k);
}
for(ll j = 0 ; j <= n ; j ++) for(ll mex = p ; mex <= min(b[n] + k, n) ; mex ++) ans = (ans + dp[j][mex] * A[j][n - mex]) % Mod;
printf("%lld", ans);
return 0;
}
(min,+) 卷积
凸包+凸包:闵可夫斯基和
凸包+匪徒包:SMAWK/决策单调性分治
匪徒包+匪徒包(随机):取前 \(n\log n\) 小的 \(a_i+b_j\),然后把没被更新的位置暴力跑。
背包
01 背包的牛逼人士复杂度 \(O(nv^2/nw^2)\)。
方法是:按大小加入,每次把当前的 \(f\) 按 \(\bmod i\) 分组,然后我们搞得是凸包加匪徒包,SMAWK 就可以做到 \(O(nv^2)\)。
完全背包的牛逼人士复杂度 \(O(w^2\log W)\),\(w\) 是一个的重量的 max,\(W\) 是总重量。
方法是:完全背包无限制,所以可分开体积 \(f(x+y)\gets f(x)+f(y)\)。
每次计算 \(mid-w/2,\dots,mid+1,mid+2,\dots mid+w/2\),这样下去。这正确的原因是把物品放出来,尽量沿中间剖开,得到的一定在这个区间内。
多重背包的牛逼人士