IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【冲刺NOI】DP专训 -> 正文阅读

[数据结构与算法]【冲刺NOI】DP专训

前言

五道我不会做的DP黑题。
链接里都有题意,我就不用再在文章里写了

UOJ607-跳蚤电话

我们考察一下每一棵子树是怎么构建起来的。

由于操作等价于长叶子和把一些树边伸长,所以已经分出的枝杈是无法改变的,那么对于一棵子树的树根,在两个及以上儿子子树中出现节点之前,它自己就必须出现。

除了有这个限制之外,这题就是一个普通的节点顺次出现的方案数的问题。所以我们可以树形DP,那么每个节点要么在子树内第一个出现,要么在某一个儿子子树出现一些节点后出现,枚举一下然后用组合数计算即可。

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
using namespace std;
const int MAXN=200005;
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}

const ll MOD=998244353;
ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
ll fac[MAXN],inv[MAXN];
int init(int n){
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
	inv[n]=ksm(fac[n],MOD-2,MOD);
	for(int i=n-1;i>1;i--)inv[i]=inv[i+1]*(i+1)%MOD;
	return 114514;
}
int cbddl=init(MAXN-4);
ll C(int n,int m){
	if(m>n||m<0)return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
ll IC(int n,int m){
	if(m>n||m<0)return 0;
	return inv[n]*fac[m]%MOD*fac[n-m]%MOD;
}
struct edge{
	int v,to;edge(){}
	edge(int V,int T){v=V,to=T;}
}e[MAXN<<1];
int EN,G[MAXN];
void addedge(int u,int v){
	e[++EN]=edge(v,G[u]),G[u]=EN;
	e[++EN]=edge(u,G[v]),G[v]=EN;
}
int n,siz[MAXN];
ll f[MAXN];
void dfs(int x,int fa){
	siz[x]=0;ll ans=1;
	for(int i=G[x],v;i;i=e[i].to){
		if((v=e[i].v)==fa)continue;
		dfs(v,x),siz[x]+=siz[v],(ans*=C(siz[x],siz[v])*f[v]%MOD)%=MOD;
	}f[x]=ans;
	if(x^1){
		for(int i=G[x],v;i;i=e[i].to){
			if((v=e[i].v)==fa)continue;
			(f[x]+=ans*IC(siz[x],siz[v])%MOD*C(siz[x],siz[v]-1))%=MOD;
		}
	}siz[x]++;
}
int main()
{
	n=read();
	for(int i=1;i<n;i++)addedge(read(),read());
	dfs(1,0);
	print(f[1]);
	return 0;
}

UOJ181-密码锁

第一条结论:把有向图缩点后可以得到一条有向的链。

这个你可以考虑调整法,因为原图是个完全图,所以一定可以用恰好 n ? 1 n-1 n?1 条强连通分量外的边把分量连成一条有向链。

那么此时的强连通分量个数就等于链上的边数+1。此时一条链上的边,它的意义其实等价于某种把所有点分成 S S S 集合和 T T T 集合(不为空)的方法,使得跨集合的边中只有 S → T S\rightarrow T ST 方向的边。

于是我们可以枚举集合 S , T S,T S,T,算出这种分割合法的概率,然后就可以求和算出缩点后链上边数的期望。

显然直接 O ( 2 n ) O(2^n) O(2n) 枚举是行不通的,但是这题保证了两个方向概率不等的边的数量不超过19,也就是说这些边连成的连通块大小最大为20。我们可以枚举每一个这样的连通块,然后枚举它的子集做状压DP,最后用类似背包的方法把连通块的答案合并起来即可。由于其它边两个方向概率相等,所以可以直接算。

总复杂度 O ( 2 m + 1 m + n 2 ) O(2^{m+1}m+n^2) O(2m+1m+n2)

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
using namespace std;
const int MAXN=2333;
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}

const ll MOD=998244353,iv2=(MOD+1)>>1;
ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}

int n,m,w[233][233],cnt[1<<20];
ll mi[2333],iv,dp[233],f[233],g[1<<20],ans=1;
bool vis[233];
void dfs(int x,vector<int>&V){
	V.push_back(x),vis[x]=1;
	for(int v=1;v<=n;v++)if(!vis[v]&&w[x][v]!=iv2)dfs(v,V);
}
int main()
{
	n=read(),m=read(),iv=ksm(10000,MOD-2,MOD);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j]=iv2;
	mi[0]=1;
	for(int i=1;i<=2333-5;i++)mi[i]=mi[i-1]*iv2%MOD;
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		w[u][v]=read()*iv%MOD,w[v][u]=(MOD+1-w[u][v])%MOD;
	}
	for(int i=1;i<(1<<(m+1));i++)cnt[i]=cnt[i>>1]+(i&1);
	int sum=0;
	dp[0]=1;
	for(int x=1;x<=n;x++)if(!vis[x]){
		vector<int>a;
		dfs(x,a);
		int k=a.size();
		for(int i=0;i<=k;i++)f[i]=0;
		for(int s=0;s<(1<<k);s++)g[s]=1;
		for(int i=0;i<k;i++)
			for(int j=0;j<k;j++)if((i^j)&&w[a[i]][a[j]]!=iv2)
				for(int s=1;s<(1<<k);s++)if(((s>>i)&1)&&(~(s>>j)&1))
					(g[s]*=(w[a[i]][a[j]]<<1))%=MOD;
		for(int s=0;s<(1<<k);s++)
			(f[cnt[s]]+=g[s]*mi[cnt[s]*(k-cnt[s])])%=MOD;
		for(int i=sum+k;i>=0;i--){
			(dp[i]*=f[0]*mi[i*k]%MOD)%=MOD;
			for(int j=min(i,k);j>0;j--)if(i-j<=sum)
				(dp[i]+=dp[i-j]*f[j]%MOD*mi[j*(sum-i+j)]%MOD*mi[(i-j)*(k-j)])%=MOD;
		}sum+=k;
	}
	for(int i=1;i<n;i++)(ans+=dp[i])%=MOD;
	print(ans*ksm(10000,n*(n-1),MOD)%MOD);
	return 0;
}

UOJ370-滑稽树上滑稽果

看到部分分的提示我们可以发现,最优解一定是一条链,每个节点滑稽值不大于它的父亲。

然后我们贪心地想,如果一个节点的滑稽值等于它的父亲,那么把它从链中抽出去放到链尾一定不劣。所以节点的滑稽值相当于要先不断变小,直到变为所有 a i a_i ai? 的按位与和,然后剩下的滑稽值全部等于这个值。

在变小的那部分节点中,儿子节点的滑稽值一定是父亲节点的滑稽值的(位运算)子集。由于滑稽值与上某个 a i a_i ai? 过后再与一个相同的值一定不优,所以我们假定 a i a_i ai? 能够重复用,那么就可以用FWT预处理一下然后 O ( 1 ) O(1) O(1) 判断某个滑稽值能否一步变为另一个值。

然后就可以用枚举子集的方法DP了。原本DP的形式为 f [ i ] [ j ] f[i][j] f[i][j] 表示链上第 i i i 个点滑稽值为 j j j i i i 后面的点的滑稽值为所有 a i a_i ai? 的按位与和)时的最小滑稽值总和,由于滑稽值单减所以可以用倒着枚举 j j j 的方式把第一维去掉。

总复杂度就是每个值状态枚举一遍子集的复杂度,为 O ( 3 log ? n ) = O ( n log ? 2 3 ) ≈ O ( n n ) O(3^{\log n})=O(n^{\log_23})≈O(n\sqrt{n}) O(3logn)=O(nlog2?3)O(nn ?)

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
using namespace std;
const int MAXN=1<<18;
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}

int n,lim=(1<<18)-1,sum=lim;
ll f[MAXN+5];
bool g[MAXN+5];
ll MIN(ll x,ll y){return x<y?x:y;}
#define min MIN
int main()
{
	n=read();
	for(int i=0;i<=lim;i++)f[i]=INF;
	for(int i=1,x;i<=n;i++)x=read(),f[x]=x,sum&=x,g[x]=1;
	for(int i=0;i<=lim;i++)if(f[i]<INF)f[i]+=sum*(n-1ll);
	for(int k=1;k<=lim;k<<=1)
		for(int i=0;i<=lim;i++)if(i&k)g[i]|=g[i^k];
	for(int s=lim;s>0;s--)if(f[s]<INF)
		for(int t=s;t>0;){
			t=(t-1)&s;
			if(g[lim^s^t])f[t]=min(f[t],f[s]-sum+t);
		}
	print(f[sum]);//只有sum处的值是合法且最优的
	return 0;
}

CF582D-Number of Binominal Coefficients

我们设 f ( n k ) f{n\choose k} f(kn?) 表示 ( n k ) {n\choose k} (kn?) 质因数分解后 P P P 处的次数,那么有

f ( n k ) = ∑ i = 1 + ∞ ? n P i ? ? ? k P i ? ? ? n ? k P i ? f{n\choose k}=\sum_{i=1}^{+\infty}\lfloor\frac{n}{P^i}\rfloor-\lfloor\frac{k}{P^i}\rfloor-\lfloor\frac{n-k}{P^i}\rfloor f(kn?)=i=1+??Pin????Pik????Pin?k??
我们把 n n n k k k 都转化成 P P P 进制后可以发现,这个式子其实就等于 k k k n ? k n-k n?k 做加法时产生进位的次数。

所以我们把 A A A 转化成 P P P 进制,然后从低位到高位做一个简单的数位DP即可。想怎么做都行,只要复杂度不超过长度的平方。

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
using namespace std;
const int MAXN=100005;
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}

const ll MOD=1e9+7,iv2=(MOD+1)>>1;
ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}

ll P,k,a[23333],f[2][23333][2][2],ans;//滚动数组,所以空间可以随便开
int n;
char in[23333];
ll pr(ll x){
	if(x<0)return 0;
	if(x<P)return ((x+2)*(x+1)>>1)%MOD;
	x=min(x,(P<<1)-2);
	return (((P+1)*P+(P+(P<<1)-2-x)*(x-P+1))>>1)%MOD;
}
ll CG(ll l,ll r){
	if(l>r||r<0)return 0;
	l=max(l,0ll),r=max(r,-1ll);
	return pr(r)-pr(l-1)+MOD;
}

int main()
{
	P=read(),k=read(),n=1;
	scanf("%s",in);
	for(int id=0,lim=strlen(in);id<lim;id++){
		int c=in[id]^48;
		for(int i=0;i<n;i++)a[i]*=10;
		a[0]+=c;
		for(int i=0;i<n;i++)
			if(a[i]>=P)a[i+1]+=a[i]/P,a[i]%=P,n=max(n,i+2);
	}
	f[1][0][0][0]=1;
	for(int id=0;id<=n;id++){
		bool e=id&1,t=e^1;
		for(int i=0;i<=id+1;i++)
			f[e][i][0][0]=f[e][i][0][1]=f[e][i][1][0]=f[e][i][1][1]=0;
		for(int i=0;i<=id;i++)
			for(int u=0;u<2;u++)
				for(int v=0;v<2;v++){
					ll d=f[t][i][u][v];
					if(!d)continue;
					(f[e][i][0][0]+=d*CG(0,a[id]-v-1))%=MOD;
					(f[e][i][u][0]+=d*CG(a[id]-v,a[id]-v))%=MOD;
					(f[e][i][1][0]+=d*CG(a[id]-v+1,P-v-1))%=MOD;
					(f[e][i+1][0][1]+=d*CG(P-v,P+a[id]-v-1))%=MOD;
					(f[e][i+1][u][1]+=d*CG(P+a[id]-v,P+a[id]-v))%=MOD;
					(f[e][i+1][1][1]+=d*CG(P+a[id]-v+1,(P<<1)-2))%=MOD;
				}
	}
	for(int i=k;i<=n;i++)(ans+=f[n&1][i][0][0])%=MOD;
	print(ans);
	return 0;
}

AGC028D-Chords

我们可以放心地直接破环成链,因为这样不会改变连通性。然后注意到连通块只会包含不会交叉,于是我们可以很容易地往区间DP上面想。

朴素的区间DP可以解决无限制的连边,但是加上限制不仅复杂度变大而且还会假。原始的DP是一个简单的线性变换,所有东西都是用DP求的,所以可以猜到正解肯定要把某些东西直接求。

如果是 n n n 个点之间随便连边的话,可以推出当 n n n 为奇数时方案数为0,为偶数时方案数为 1 ? 3 ? 5 ? . . . ? ( n ? 1 ) 1\cdot3\cdot5\cdot...\cdot(n-1) 1?3?5?...?(n?1)。这个时候我们可以很容易推广到有限制的情况,因为除了已经连边的点以外,其他点之间显然是随便连的,所以算出任何一个点集内点的连边方案数。

我们考虑计算每个连通块的贡献。假设这个连通块恰好包含在区间 [ l , r ] [l,r] [l,r] 内,那么我们只需要满足 l l l r r r 连通即可。显然如果不连通的话,由于 l l l 处于区间边界,它所在连通块不会被区间内的其它连通块包含,所以我们如果容斥来算贡献的话会方便很多。

f [ i ] [ j ] f[i][j] f[i][j] 表示 i , j i,j i,j 连通且区间 [ i , j ] [i,j] [i,j] 内外不连通的方案数,那么有
f [ i ] [ j ] = g ( i , j ) ? ∑ k = i j ? 1 f [ i ] [ k ] ? g ( k + 1 , j ) f[i][j]=g(i,j)-\sum_{k=i}^{j-1}f[i][k]\cdot g(k+1,j) f[i][j]=g(i,j)?k=ij?1?f[i][k]?g(k+1,j)
其中 g ( i , j ) g(i,j) g(i,j) 表示区间 [ i , j ] [i,j] [i,j] 内的点随意连边(去掉已经连了的点)的方案数。

这个DP总时间 O ( n 3 ) O(n^3) O(n3),最后再统计一下贡献即可。

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
using namespace std;
const int MAXN=2333;//这可真是又臭又浪费空间
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}

const ll MOD=1e9+7,iv2=(MOD+1)>>1;
ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}

int n,k,mat[MAXN],a[MAXN][MAXN],b[MAXN][MAXN];
ll f[MAXN][MAXN],g[MAXN],ans;
int main()
{
	n=read()<<1,k=read();
	for(int i=1,u,v;i<=k;i++)u=read(),v=read(),mat[u]=v,mat[v]=u;
	g[0]=1;
	for(int i=1;i<=n;i++)g[i<<1]=g[(i-1)<<1]*((i<<1)-1)%MOD;
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++){
			a[i][j]=a[i][j-1],b[i][j]=b[i][j-1];
			if(mat[j]>0){
				b[i][j]++;
				if(mat[j]<j&&mat[j]>=i)a[i][j]+=2;
			}
		}
	for(int i=0;i<=n;i++)f[i+1][i]=1;
	for(int i=n;i>0;i--)
		for(int j=i+1;j<=n;j+=2)if(a[i][j]==b[i][j]){
			f[i][j]=g[j-i+1-b[i][j]];
			for(int k=i+1;k<j;k+=2)
				(f[i][j]+=MOD-f[i][k]*g[j-k-b[k+1][j]]%MOD)%=MOD;
			(ans+=f[i][j]*g[n-(k<<1)-j+i-1+b[i][j]])%=MOD;
		}
	print(ans);
	return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 08:20:57  更:2022-05-08 08:22:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:25:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码