杂题选做 Ⅰ

上升序列

有两个长度为 \(n\) 的单调不降序列 \(a,b\),你可以对 \(a\) 进行不超过 \(m\) 次操作:
• 选择一个下标 \(i\) 和一个整数 \(x\),把 \(a_i\) 变成 \(a_i+x\)。这里 𝑥 可以是负数。
操作一次的代价为 \(x^2\)。并且你需要保证每次操作完 \(a_i\) 还是单调不降的。
你要把 \(a\) 变成 \(b\),问最小的代价之和。答案对 \(998244353\) 取模。
如果无解,输出 -1

\(n,m \leq 10^5, a_i,b_i \leq 10^9\),保证 \(a,b\) 单调不降。

每次操作完要求单调不降的限制相当于没有,因为如果 \(a_i\) 当前操作不合法,总可以先操作不合法的那一侧,再操作 \(a_i\)。由于序列单调不降,这样递归下去操作不会成环。

\(f(i,j)\) 表示把 \(i\) 分成 \(j\) 个数 \(x_1,\dots,x_j\)\(\sum x_j^2\) 的最小值。不难发现 \(f(i,j)\) 关于 \(j\) 是下凸的,所以用优先队列维护一个最大的 \(\Delta = f(i,j)-f(i,j+1)\) 就行了。

复杂度 \(O((n+m) \log n)\)

竞赛图

给你一个 \(n\) 个点的竞赛图,求有多少个点集 \(S\) 满足 \(S\) 的诱导子图是强连通的。
其中竞赛图是一个有向图,满足任意两个不同的点之间均存在且仅存在一条有向边相连。

\(T \leq 10\) 组测试数据,\(n \leq 24\)

考虑将每个 scc 缩点后形成的 DAG,由于图是竞赛图,所以拓扑序是唯一的。
那么直接做就是 \(O(T \cdot n2^n)\) 的,无法通过。

考虑到任意两点之间有且仅有一条有向边相连,所以若一个强连通的点集 \(S\) 中的所有点都能走到另一个点 \(k\),则 \(S+k\) 一定不合法。反过来,每个不合法的点集 \(S\) 也一定存在合法的一个子集 \(T\),满足 \(T\)\(S-T\) 间的边都是从 \(T\) 指向 \(S-T\) 的。
\(O(2^n)\) 预处理出每个点集 \(S\) 中每个点出边集合的交 \(R\),若 \(S\) 合法,则 \(\forall T \in R, S|T\) 一定不合法。

据此不难 \(O(2^n)\) 递推计算每个点集的合法性。
复杂度 \(O(T \cdot 2^n)\)

宝宝拼字母

定义两个字符串 \(A,B\) 是相似的当且仅当可以把 \(A\) 中某个长度不超过 \(K\) 的子串(可以为空串)替换为另一个长度不超过 \(K\) 的串得到 \(B\)
给定 \(N\) 个仅包含英文大写字母的串,求出有多少对字符串是相似的。

\(N,K,\sum |S| \leq 5\times 10^5\)

考虑两个字符串 \(A,B\),不妨设 \(|A| \leq |B|\),则它们相似的充要条件是 \(\operatorname{LCP}(A,B)+\operatorname{LCS}(A,B)+K \geq |B|\)

假设我们已经枚举了一个较长的串 \(B\) 的一个后缀 \(R\),那么和 \(B\) 相似的串 \(A\) 必然也有后缀 \(R\),且倒数第 \(|R|+1\) 个字母和 \(B\) 对应不同(防止算重),同时 \(\operatorname{LCP}(A,B) \leq |B|-|R|-K\)。那么我们考虑在两个串的 \(\operatorname{LCP}\) 处统计贡献。首先把所有串正反插入两个 Trie,遍历后缀 Trie 时把当前节点上的串按照大小排序,从小往大遍历即可。问题转化为前缀 Trie 上的单点加和求子树和,不难用 BIT 维护。

复杂度 \(O(\sum |S|\log n)\)

宝宝选数字

给定两个长为 \(n\) 的数组 \(a,b\)。你需要按照任意顺序选择 \(k\) 个下标 \(i_1,\dots,i_k\),使得:

\[\sum_{j=1}^{k} a_{i_j}-\sum_{j=1}^{k-1}|b_{i_j}-b_{i_{j+1}}| \]

最大。特别的,当 \(k=1\) 时后面的求和为 \(0\)

\(n,k \leq 2 \times 10^5, a_i,b_i \leq 10^9\)

首先按照 \(b\) 排序,记 \(f(l,r)\) 表示 \([l,r]\) 中最大的 \(k\)\(a_i\) 之和加上 \(-b_r+b_l\),不难发现 \(f\) 满足四边形不等式。
所以若我们记 \(g_i=\max_{1 \leq j \leq i} f(j,i)\),则 \(g\) 具有决策单调性。

考虑分治解决,则我们需要一个支持快速删除一个数,恢复上一次操作和查询前 \(k\) 大的数据结构,而这链表可以做到:

int val[N],lim,sum; 
struct node{
    int x,y,id;
} a[N]; 
void ins(int x){
	x=a[x].id; L[R[x]]=R[L[x]]=x;
	if(x>lim) sum+=val[x]-val[lim],lim=R[lim];
} 
void del(int x){
	x=a[x].id; R[L[x]]=R[x]; L[R[x]]=L[x];
	if(x>=lim) lim=L[lim],sum+=val[lim]-val[x]; 
} 

复杂度 \(O(n\log n)\)

三分图

定义一张无向图被称为三分图,当且仅当存在至少一种将图中每个顶点染成红、黄、蓝三色之一的方案,使得任意一条边的两个端点颜色不同。
定义一个大小为 \(n\) 的排列 \(p\) 构造的无向图 \(G(p)\)\(\forall 1\leq i<j \leq n\),若 \(p_i>p_j\),则 \(i,j\) 之间有一条边。
给你两个大小为 \(n\) 的排列 \(p,q\),判断有多少个排列 \(k\) 满足:

  1. \(w\) 的字典序 \(\geq p\)\(\leq q\)
  2. \(G(w)\) 是三分图。

多组询问,\(n,T \leq 300\),答案对 \(998244353\) 取模。

发现排列 \(p\) 中若存在 \(a<b<c<d\) 满足 \(p_a>p_b>p_c>p_d\),则 \(G(p)\) 一定不是三分图。
故第二条限制转化为 \(\operatorname{LDS}(p) \leq 3\),由 Dilworth 定理,这等价于可以将排列划分为三个上升子序列。

先考虑怎么判断这个东西,我们可以维护三个上升子序列的末尾元素 \(c_1,c_2,c_3\),插入 \(p_k\) 时,找到最小的 \(c_p\) 满足 \(c_p < k\),然后将 \(c_p\) 修改为 \(k\)。若无法插入 \(p_k\),则不合法。
考虑 dp,设当前三个上升子序列的末尾元素为 \(c_1,c_2,c_3\),则此时 \(<c_1\) 的数一定都已经被插入过了,我们记 \(x_1=(c_1,c_2)\) 中剩多少个数没填 ,\(x2=(c_2,c_3)\) 中剩多少个数没填,\(x3=(c_3,n]\) 中剩多少个数没填,后继状态只依赖于 \(x_1,x_2,x_3\)。令 \(f_{x_1,x_2,x_3}\) 表示这种状态下的合法排列数量,则有转移:

\[\left\{\begin{matrix} x_1>0,f_{x_1,x_2,x_3} \longleftarrow f_{x_1-1,x_2,x_3}\\ x_2>0,f_{x_1,x_2,x_3} \longleftarrow f_{x_1+k-1,x_2-k,x_3},k \leq x_2 \\ x_3>0,f_{x_1,x_2,x_3} \longleftarrow f_{x_1,x_2+k-1,x_3-k},k \leq x_3 \end{matrix}\right.\]

不好优化,改为 dp \(g_{x_1+x_2+x_3,x_2+x_3,x_3}\),则对于 \(g_{a,b,c}\)

\[\left\{\begin{matrix} a-b>0,g_{a,b,c} \longleftarrow g_{a-1,b,c}\\ b-c>0,g_{a,b,c} \longleftarrow g_{a-1,c\sim b-1,c} \\ c>0,g_{a,b,c} \longleftarrow g_{a-1,b-1,0\sim c-1} \end{matrix}\right.\]

可以前缀和优化到 \(O(n^3)\)
而对于字典序的限制,我们可以差分转化为计数字典序 \(\leq p\) 的方案数。套路地枚举所求排列和 \(p\) 的第一个不同位置,同时贪心维护 \(c_1,c_2,c_3\),查表 \(g\) 获取方案数累加即可。

非常厉害,总复杂度 \(O(n^3+Tn^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\) 的式子在连通图计数中是具有普适性的。或者说几个式子都是普适的就对了。

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

呃啊,都写得好长啊,而且感觉还没把思路说的很清楚,后面写的精简点吧。

Farthest City

\(d_n\) 最远启发我们按照距离分层 \(dp\)
\(dp_{i,j}\) 表示考虑了 \(i\) 个点,其中最后一层点有 \(j\) 个,不难得到转移

\[f_{i,j}=2^{\binom{j}{2}} \sum_{k=1}^{i-j} f_{i-j,k} \times \binom{n-1-(i-j)}{j} \times (2^k-1)^j \]

答案就是 \(\sum_{i=1}^{n-1} f_{n-1,i} \times (2^i-1)\)

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=500+9,M=60+9;
const int MOD=998244353,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,m,f[N][N]; 
int C[N][N],ans,pw2[N*N],coef[N][N];
void Mian(){
	cin>>n>>m;
	for(int i=0;i<=n;++i){
		for(int j=0;j<=i;++j){
			if(!j) C[i][j]=1;
			else C[i][j]=(C[i-1][j]+C[i-1][j-1])%m;  
		}
	} 
	pw2[0]=1;
	for(int i=1;i<=n*n;++i) pw2[i]=pw2[i-1]*2%m;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			coef[i][j]=qpow(pw2[i]-1+m,j,m);
	f[1][1]=1; 
	for(int i=2;i<=n;++i){
		for(int j=1;j<i;++j){
			for(int k=1;k<=i-j;++k){
				f[i][j]+=f[i-j][k]*C[n-1-(i-j)][j]%m*coef[k][j]%m;
				f[i][j]%=m;
			}
			f[i][j]=f[i][j]*pw2[C[j][2]]%m;
		}
	}
	for(int i=1;i<=n-1;++i){
		ans+=f[n-1][i]*(qpow(2,i,m)-1+m)%m;
		ans%=m; 
	}
	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(); 
	}
}

Inversion Sum

直接不太好搞,转一下期望就随便算了。
\(dp_{i,j}\) 表示最终 \(a_i<a_j\) 的概率,视每个操作有 \(\frac{1}{2}\) 概率执行,有转移:

\[\left\{\begin{matrix}f_{x,y}=f_{y,x}=\frac{f_{x,y}+f_{y,x}}{2}\\f_{i,y}=f_{i,x}=\frac{f_{i,y}+f_{i,x}}{2}\\f_{x,i}=f_{y,i}=\frac{f_{x,i}+f_{y,i}}{2} \end{matrix}\right.\]

复杂度 \(O(nq)\)

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=3000+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,q,a[N],f[N][N],ans; 
void Mian(){
	cin>>n>>q;
	for(int i=1;i<=n;++i) cin>>a[i];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)	
			f[i][j]=(a[i]<a[j]?1:0); 
	int INV2=qpow(2,MOD-2,MOD);
	for(int i=1;i<=q;++i){
		int x,y; cin>>x>>y;
		f[x][y]=f[y][x]=(f[x][y]+f[y][x])%MOD*INV2%MOD;
		for(int j=1;j<=n;++j) if(j!=x && j!=y){
			f[x][j]=f[y][j]=(f[x][j]+f[y][j])%MOD*INV2%MOD;
			f[j][x]=f[j][y]=(f[j][x]+f[j][y])%MOD*INV2%MOD;
		}
	}
	for(int i=1;i<=n;++i)
		for(int j=1;j<i;++j)
			(ans+=f[i][j])%=MOD;
	ans=ans*qpow(2,q,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(); 
	}
}

kangaroo

神秘。

连续段 dp。题意转化后为固定首尾计数每个数都小于/大于左右两个的排列数量。设 \(dp_{i,j}\) 为考虑填入了 \(1,..,i\) 时有 \(j\) 个满足要求的连续段,有转移:

  • 单独起一段,\(dp_{i,j}=(j-[i>s]-[i>t])\times dp_{i-1,j-1}\)
  • 加到某一段首尾,由于此时填的数一定比任意一个数大,后面填的数又比当前数大,故一定会在加的这个数上出现不合法情况,此转移不存在。
  • 合并两个段,\(dp_{i,j}=j\times dp_{i-1,j+1}\)

对于 \(s,t\) 单独处理,为 \(dp_{i,j}=dp_{i-1,j}+dp_{i-1,j-1}\),即接到当前首/尾段后或者在首尾新开一段,答案就是 \(f_{n,1}\)
复杂度 \(O(n^2)\)

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=2e3+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,t,f[N][N];
void Mian(){
	cin>>n>>s>>t;
	f[0][0]=1;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(i==s || i==t){
				f[i][j]=(f[i-1][j]+f[i-1][j-1])%MOD;
				continue;
			}
			f[i][j]+=(j-(i>s?1:0)-(i>t?1:0))*f[i-1][j-1]%MOD;
			f[i][j]+=j*f[i-1][j+1]%MOD;
			f[i][j]%=MOD; 
		}
	}
	cout<<f[n][1];
}
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T=1; //cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}

Skills

icpc 济南这场怎么那么智慧。

先考虑一个简单 dp,设 \(dp_{i,p,j,k}\) 表示第 \(i\) 天学习了技能 \(p\),剩下两个技能按照标号大小排列分别有 \(j,k\) 天没学了,\(j,k\)\(0\) 说明从未学过该技能。转移是简单的,复杂度 \(O(n^3)\),过不了。

注意到如果学了一个技能,那么我们一定不会让这个技能的熟练度降到 \(0\),又由于降熟练度的速度是在平方级别的,所以 \(j,k\) 的取值最大只到 \(\sqrt{V}\) 级别,复杂度来到 \(O(nV)\),可以通过。

滚动数组记得清空,,,

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=1000+9,M=1e4+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,a[N][3],f[2][3][209][209],ans;
void Mian(){
	cin>>n;
	for(int i=1;i<=n;++i)	
		for(int j=0;j<3;++j) 
			cin>>a[i][j];
	for(int i=0;i<3;++i) 
		for(int j=0;j<=200;++j) for(int k=0;k<=200;++k)	
			f[0][i][j][k]=f[1][i][j][k]=-INF;
	f[0][0][0][0]=f[0][1][0][0]=f[0][2][0][0]=0;
	for(int u=0;u<n;++u){
		int gt=u&1; 
		for(int j=0;j<=200;++j) for(int k=0;k<=200;++k){
			chkmax(f[gt^1][0][j?j+1:0][k?k+1:0],f[gt][0][j][k]+a[u+1][0]-(j?j+1:0)-(k?k+1:0));
			chkmax(f[gt^1][1][1][k?k+1:0],f[gt][0][j][k]+a[u+1][1]-1-(k?k+1:0));
			chkmax(f[gt^1][2][1][j?j+1:0],f[gt][0][j][k]+a[u+1][2]-1-(j?j+1:0));
			
			chkmax(f[gt^1][0][1][k?k+1:0],f[gt][1][j][k]+a[u+1][0]-1-(k?k+1:0));
			chkmax(f[gt^1][1][j?j+1:0][k?k+1:0],f[gt][1][j][k]+a[u+1][1]-(j?j+1:0)-(k?k+1:0));
			chkmax(f[gt^1][2][j?j+1:0][1],f[gt][1][j][k]+a[u+1][2]-(j?j+1:0)-1);
		
			chkmax(f[gt^1][0][k?k+1:0][1],f[gt][2][j][k]+a[u+1][0]-(k?k+1:0)-1);
			chkmax(f[gt^1][1][j?j+1:0][1],f[gt][2][j][k]+a[u+1][1]-(j?j+1:0)-1);
			chkmax(f[gt^1][2][j?j+1:0][k?k+1:0],f[gt][2][j][k]+a[u+1][2]-(j?j+1:0)-(k?k+1:0));
		}	
		for(int j=0;j<=200;++j) 
			for(int k=0;k<=200;++k)
				for(int p=0;p<3;++p)
					f[gt][p][j][k]=-INF;
	}
	ans=0;	
	for(int j=0;j<=200;++j)
		for(int k=0;k<=200;++k)
			for(int p=0;p<3;++p)
				chkmax(ans,f[n&1][p][j][k]);
	cout<<ans<<'\n';
}
void Mianclr(){
	
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int T; cin>>T;
    while(T--){
    	Mian();
		Mianclr(); 
	}
}
posted @ 2025-11-21 23:46  Mi2uk1  阅读(14)  评论(0)    收藏  举报