2026.2

NarrowRectangles

考虑设 \(dp_{i,j}\) 表示考虑前 \(i\) 个矩形,把第 \(i\) 个矩形左端点移到 \(j\) 的最小代价。
\(len_i\) 为第 \(i\) 个矩形长度,不难得到:

\[dp_{i,j}=\min_{j-len_{i-1}\leq k\leq j+len_i} dp_{i-1,k} + \left | j-l_i \right | \]

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\) 时刻引爆的最小调整代价,有:

\[f_{u,j}=\sum_{v \in son_u} \min_{0 \leq k \leq j}\left \{ f_{v,k}+|j-w(u,v)-k| \right \} = \]

考察一下这个 min 里面是在干什么,令 \(f'_{v,k}\) 是加上后面那个绝对值之后的新函数,\(L,R\) 为原函数斜率为 0 的那一段的左右端点,记 \(w=w(u,v)\),分类讨论一下:

\[f'_{v,k}=\begin{cases}f_{v,k}+w & k \leq L \\f_{v,L}+(L-k+w) & k-w \leq L < k <R \\f_{v,L} & L \leq k-w \leq R \\f_{v,R}+(k-w-R) & R < k-w \end{cases} \]

然后考虑这样一个 \(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+...$$

\[=P_S\sum_{i=1}^{+\infty } i \times (1-P_S)^{i-1}=P_S \times \frac{1}{P_S^2}=\frac{1}{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\) 个连通块的方案数,得到:

\[g_k=\sum_{i=k}^{n} \begin{Bmatrix}i\\k\end{Bmatrix}\times f_i \]

作斯特林繁衍,得到:

\[f_k=\sum_{i=k}^{n} (-1)^{i-k} \begin{bmatrix}i\\k\end{bmatrix}g_i \]

所求即为:

\[f_1=\sum_{i=1}^{n} (-1)^{i-1} (i-1)! g_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\) 的式子在连通图计数中是具有普适性的。或者说几个式子都是普适的就对了。

虽然暂时还没做到下一个这样的题。

posted @ 2026-02-04 16:28  Mi2uk1  阅读(2)  评论(0)    收藏  举报