2026.2
NarrowRectangles
考虑设 \(dp_{i,j}\) 表示考虑前 \(i\) 个矩形,把第 \(i\) 个矩形左端点移到 \(j\) 的最小代价。
记 \(len_i\) 为第 \(i\) 个矩形长度,不难得到:
slope trick,每次相当于就是把原函数为 0 的一段往两边拉,加上一个绝对值函数。
由于 \(dp_i\) 图像斜率最低为 \(-i\),最高为 \(i\),所以两边移动的数量是确定的。
那么直接用可重集维护斜率变化点就好。
可重集需要支持查询极值,删除极值和加入元素,直接用堆实现就行,复杂度 \(O(n \log n)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=1e5+9,M=1e5+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,l[N],r[N],ans;
int len[N],tagl,tagr;
priority_queue<int> sl;
priority_queue<int,vector<int>,greater<int> > sr;
void Mian(){
cin>>n;
for(int i=1;i<=n;++i) cin>>l[i]>>r[i];
for(int i=1;i<=n;++i) len[i]=r[i]-l[i];
sl.push(l[1]); sr.push(l[1]);
tagl=tagr=0; ans=0;
for(int i=2;i<=n;++i){
tagl-=len[i],tagr+=len[i-1];
int L=sl.top()+tagl,R=sr.top()+tagr;
if(l[i]<L){
ans+=(L-l[i]);
sl.pop();
sl.push(l[i]-tagl); sl.push(l[i]-tagl);
sr.push(L-tagr);
}else if(l[i]>R){
ans+=(l[i]-R);
sr.pop();
sr.push(l[i]-tagr); sr.push(l[i]-tagr);
sl.push(R-tagl);
}else{
sl.push(l[i]-tagl); sr.push(l[i]-tagr);
}
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
烟花表演
设 \(f_{u,j}\) 为子树 \(u\) 内的烟花在 \(j\) 时刻引爆的最小调整代价,有:
考察一下这个 min 里面是在干什么,令 \(f'_{v,k}\) 是加上后面那个绝对值之后的新函数,\(L,R\) 为原函数斜率为 0 的那一段的左右端点,记 \(w=w(u,v)\),分类讨论一下:
然后考虑这样一个 \(f'_{v,k}\) 如何贡献到 \(f_{u,j}\)。把横轴改为 \(j\) 观察一下图像,发现关于原函数 \(f_{v,k}\) 其实就是把斜率 \(\leq -1\) 的部分值加上 \(w\),把 \([L,R]\) 段整体向右平移 \(w\),并在 \([L,L+w]\) 间插入一条斜率为 \(-1\) 的线段,然后把 \(R\) 后的部分修改为一条斜率为 \(1\) 的射线。
slope trick,把 \(R\) 后的部分修改为一条斜率为 \(1\) 的射线只需要弹出最大的 \(s-1\) 个斜率变化点,其中 \(s\) 为这个点的儿子数量,平移 \([L,R]\) 直接继续弹出两个点加上 \(w\) 再塞回去就行了。而值加上 \(w\) 和插入 \(-1\) 的线段其实不需要操作,因为没有产生新的斜率变化点。
最后考虑计算答案,一个显然的事情是 \(f_{1,0}\) 是容易计算的,为所有边权之和,那么接下来遍历一边可重集就能得到在最低点处的值了。
对于可重集,我们需要查询和删除极值,以及合并操作,为了保证复杂度正确,使用左偏树实现,复杂度 \(O((n+m) \log (n+m))\),别忘了需要开双倍空间。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=6e5+9,M=1e5+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,m,ans,a[N],fa[N],deg[N];
int val[N],rt[N],idx;
int ls[N],rs[N],d[N];
int merge(int u,int v){
if(!u || !v) return (u|v);
if(val[u]<val[v]) swap(u,v);
rs[u]=merge(rs[u],v);
if(d[ls[u]]<d[rs[u]]) swap(ls[u],rs[u]);
if(!rs[u]) d[u]=0;
else d[u]=d[rs[u]]+1;
return u;
}
int pop(int u){ return merge(ls[u],rs[u]);}
void Mian(){
cin>>n>>m;
for(int i=2;i<=n+m;++i){
cin>>fa[i]>>a[i];
deg[fa[i]]++; ans+=a[i];
}
for(int i=n+m;i>1;--i){
int l=0,r=0;
if(i<=n){
while(--deg[i]) rt[i]=pop(rt[i]);
r=val[rt[i]]; rt[i]=pop(rt[i]);
l=val[rt[i]]; rt[i]=pop(rt[i]);
}
val[++idx]=l+a[i]; val[++idx]=r+a[i];
rt[i]=merge(rt[i],merge(idx-1,idx));
rt[fa[i]]=merge(rt[fa[i]],rt[i]);
}
while(deg[1]--) rt[1]=pop(rt[1]);
for(int lst=rt[1],p=-1;rt[1];--p){
rt[1]=pop(rt[1]);
ans+=(val[lst]-val[rt[1]])*p;
lst=rt[1];
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
Ribbons on Tree
思考了一会发现可以设 \(dp_{u,j}\) 为 \(u\) 子树内有 \(j\) 个点要匹配,为了保证合法每次只能跨子树匹配,那么转移合并的时候就是枚举两边点数和匹配对数,有 \(dp_{u,i} \times dp_{v,j} \times \binom{i}{k} \times \binom{j}{k} \longrightarrow dp'_{u,i+j-2k}\),是 \(O(n^3)\) 的,无法通过。
考虑容斥,发现钦定某条边不选的情况是容易计算的,考虑 \(n-1\) 个条件形如第 \(i\) 条边被覆盖,设 \(f_S\) 为钦定 \(S\) 边集内的边不被覆盖下的匹配数,答案就为 $\sum_S (-1)^{|S|}f_S $。而一个边集内的边钦定不被覆盖相当于把原树分割成了若干连通块,一个大小为 \(n\) 的连通块的匹配数量 $g_n= {\textstyle \prod_{i=2}^{\frac{n}{2} } (i-1)} $ 是容易计算的,故设 \(dp_{u,j}\) 为 \(u\) 所在连通块大小为 \(j\) 时的方案数,把容斥系数带入 dp 方程,转移为 \(dp_{u,i}\times dp_{v,j} \times (-1) \times g_i \longrightarrow dp'_{u,i},dp_{u,i}\times dp_{v,j}\longrightarrow dp'_{u,i+j}\),即 \(u,v\) 间的边是否被钦定,此时答案即 \(\sum d p_{1,i} \times g_i\) 复杂度 \(O(n^2)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=5000+9,M=(1<<20)+9;
const int MOD=1e9+7,base=251;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,f[N][N],g[N],ans;
int siz[N],tmp[N];
vector<int> G[N];
void dfs(int u,int fa){
f[u][1]=1;
siz[u]=1;
for(auto v:G[u]){
if(v==fa) continue;
dfs(v,u);
for(int i=0;i<=siz[u]+siz[v];++i) tmp[i]=0;
for(int i=0;i<=siz[u];++i){
for(int j=0;j<=siz[v];++j){
(tmp[i]+=(f[u][i]*f[v][j]%MOD*(-g[j]+MOD)%MOD))%=MOD;
(tmp[i+j]+=f[u][i]*f[v][j]%MOD)%=MOD;
}
}
for(int i=0;i<=siz[u]+siz[v];++i) f[u][i]=tmp[i];
siz[u]+=siz[v];
}
}
void Mian(){
cin>>n;
g[0]=1;
for(int i=2;i<=n;++i) g[i]=g[i-2]*(i-1)%MOD;
for(int i=1;i<n;++i){
int x,y; cin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1,0);
for(int i=0;i<=n;++i)
(ans+=(f[1][i]*g[i]%MOD))%=MOD;
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
按位或
好题。
全部出现的期望时间不太好求,但是发现元素首次出现的期望时间是好求的。
考虑 min-max 容斥,把一个二进制数看成一个集合,定义 \(max(S)\) 为 \(S\) 最后一个元素出现时所用次数, \(min(S)\) 为第一个元素出现时所有次数,有 \(E(max(S))=\sum_{T \subset S} (-1)^{(|T|+1)} E(min(S))\)。
接下来看看 \(min(S)\) 怎么算。记 \(P_S=1-\sum_{k \in \bar{S}} p_k\),其中 \(\bar{S}\) 为 \(S\) 的补集,即所有会贡献到集合 \(S\) 中的数出现概率之和。则 $$E(min(S))=1 \times P_S+2\times (1-P_S)\times P_S+3\times (1-P_S)^2\times P_S+...$$
那么只需要一次 FMT 这个题就做完了,复杂度 \(O(n2^n)\)。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e9)
#define fir first
#define sec second
const int N=20+9,M=(1<<20)+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,siz[M];
double p[N],f[M],ans;
void Mian(){
cin>>n;
for(int i=0;i<(1<<n);++i) cin>>f[i];
for(int i=0;i<n;++i){
for(int S=0;S<(1<<n);++S) if((S>>i)&1){
f[S]+=f[S^(1<<i)];
}
}
for(int S=0;S<(1<<n);++S) siz[S]=siz[S>>1]+(S&1);
for(int S=1;S<(1<<n);++S){
if(1-f[((1<<n)-1)^S]<eps) return puts("INF"),void();
if(siz[S]&1) ans+=(1.0/(1.0-f[((1<<n)-1)^S]));
else ans-=(1.0/(1.0-f[((1<<n)-1)^S]));
}
printf("%.15lf",ans);
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
异或图
哦哦,哦哦哦!
连通图限制很难处理,翻译一下把限制改为连通块数量为 1。\(n\) 极小,可以直接钦定最终的连通块(即钦定的连通块间一定不存在边,连通块内可能有没边),相当于一个至少 \(k\) 个连通块的计数。这时我们知道哪些边一定不存在,等价于选出图的异或值在这些点上面为 0,问题转化为求一些异或方程解的数量,这个直接扔到线性基里面求出来一组线性无关向量个数就行了,贡献为 \(2^{s-t}\),其中 \(t\) 为线性基里向量数量。
接下来,考虑设 \(f_i\) 为恰好 \(i\) 个连通块的方案数, \(g_i\) 为不少于 \(i\) 个连通块的方案数,得到:
作斯特林繁衍,得到:
所求即为:
然后直接枚举算就行了,复杂度 \(O(Bell_n\times \operatorname{poly}(n,s))\),可以接受。
AC Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)(1e15)
#define fir first
#define sec second
const int N=10+9,M=60+9;
const int MOD=1e9+7,base=251;
const double eps=1e-8;
inline void chkmax(int &x,int y){x=x<y?y:x;}
inline void chkmin(int &x,int y){x=x<y?x:y;}
inline int lowbit(int x){return x&(-x);}
int qpow(int a,int b,int p){
int ret=1;
while(b){
if(b&1) ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
int n,s,a[M],g[N],ans;
int trs[N][N],idx;
string o[M];
int h[M],cnt;
void init(){
for(int i=0;i<=50;++i) h[i]=0;
cnt=0;
}
void ins(int x){
for(int i=50;i>=0;--i){
if(!((x>>i)&1)) continue;
if(h[i]){x^=h[i]; continue;}
h[i]=x; ++cnt; return ;
}
}
int scc,st;
vector<int> bel[N];
void calc(){
st=0;
for(int i=1;i<=scc;++i){
for(int j=i+1;j<=scc;++j){
for(auto u:bel[i]) for(auto v:bel[j])
st|=(1ll<<trs[u][v]);
}
}
init();
for(int i=1;i<=s;++i){
int now=a[i]&st;
ins(now);
}
g[scc]+=(1ll<<(s-cnt));
}
void dfs(int u){
if(u>n){
calc();
return ;
}
for(int i=1;i<=scc;++i){
bel[i].push_back(u);
dfs(u+1);
bel[i].pop_back();
}
++scc;
bel[scc].push_back(u);
dfs(u+1);
bel[scc].pop_back();
--scc;
}
int fac[N];
void Mian(){
cin>>s;
for(int i=1;i<=s;++i) cin>>o[i];
for(int i=1;i<=10;++i)
if(i*(i-1)==2*o[1].size()){n=i; break;}
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
trs[i][j]=trs[j][i]=idx++;
for(int k=1;k<=s;++k){
int pos=0;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(o[k][pos++]-'0') a[k]|=(1ll<<trs[i][j]);
}
dfs(1);
fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i;
for(int i=1;i<=n;++i){
if(i&1) ans+=fac[i-1]*g[i];
else ans-=fac[i-1]*g[i];
}
cout<<ans;
}
void Mianclr(){
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int T=1; //cin>>T;
while(T--){
Mian();
Mianclr();
}
}
值得注意的是,设 \(f_i\) 为恰好 \(i\) 个连通块的方案数, \(g_i\) 为不少于 \(i\) 个连通块的方案数,得到的 \(f_1=\sum_{i=1}^{n} (-1)^{i-1} (i-1)! g_i\) 的式子在连通图计数中是具有普适性的。或者说几个式子都是普适的就对了。
虽然暂时还没做到下一个这样的题。

浙公网安备 33010602011771号