dp专题咯

这里会放出一些很有意思的dp或者作者不会的,反正我觉得dp这东西,做多才有经验的嘛。所以可以看题解呀:
来来来;P2758 编辑距离这题嘛其实看出来是dp但也很难转移,不过其实多想一想就好了。还是用一下别人的题解:http://blog.csdn.net/CQBZLYTina/article/details/75043128?locationNum=9&fps=1
其实就是从上一个状态转移上来的状态的不同嘛,若是删除,那就把i-1位变成 j 位子咯,若是插入,其实就是把第j位子删除嘛,那就和删除一样啦,若是修改,那不就是将i-1位变成j-1位?那所有状态都推出来了啦,

转移方式有三种,可以为删除: f[i-1][j]+1,为添加: f[i][j-1]+1 ,为修改f[i-1]f[j-1];
代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[2001][2001];
char a[10001],b[10001];
int main()
{
	scanf("%s%s",a,b);
	int len1=strlen(a),len2=strlen(b);
	for(int i=1;i<=len1;i++) f[i][0]=i;//初始化,全部删除 
    for(int i=1;i<=len2;i++) f[0][i]=i;//嗯 
	for(int i=1;i<=len1;i++)
	{
		for(int j=1;j<=len2;j++) 
		{
			if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1];
			else f[i][j]=min(f[i-1][j-1],min(f[i][j-1],f[i-1][j]))+1;
		}
	}
	printf("%d",f[len1][len2]);
	return 0;
 } 

还有这一道 P1040 [NOIP2003 提高组] 加分二叉树(https://www.luogu.com.cn/problem/P1040),就挺离谱啊就题目都没想明白,一看题解不过如此,哈哈哈哈哈哈哈笑死了:
这个题可以用动态规划或者记忆化搜索来做。因为如果要求加分最大的话,必须要求它的儿子结点加分最大,所以就有了最优子阶段。我们可以枚举根来更新最大值。中序遍历有个特点,在中序遍历这个序列上,某个点左边的序列一定是这个点的左子树,右边的序列,一定在这个点的右子树。
root[i,j]表示[i,j]这段序列的根,递归输出先序遍历。注意初始化,f[i][i]=v[i],当序列只有I一个元素时,f[i][i]等于这个点本身的权值,当l==r-1时,此时是空树设为1。
所见代码来啦!

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m;
int a[1000];
int f[50][50];
int r[101][101];
void dfs(int x,int y)
{
	if(x>y) return ;
	printf("%lld ",r[x][y]);
	if(x==y) return ;
	dfs(x,r[x][y]-1);dfs(r[x][y]+1,y);
	return ;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&f[i][i]),f[i][i-1]=1,r[i][i]=i;
	for(int len=1;len<n;len++)
	{
		for(int i=1;i+len<=n;i++)
		{
			int j=i+len;
			f[i][j]=f[i+1][j]+f[i][i];r[i][j]=i;
			for(int k=i+1;k<j;k++)
			{
				if(f[i][j]<f[i][k-1]*f[k+1][j]+f[k][k])
				{
					f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k];
					r[i][j]=k;
				}
			}
		}
	}
	printf("%lld\n",f[1][n]);
	dfs(1,n);
	return 0;
}

下一个是P3554 [POI2013]LUK-Triumphal arch,这个是二分加树上dp。思想是二分答案,然后判断要提前帮儿子染多少的颜色,注意要舍去小于0的儿子的值。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0,last[310001],son[310001],f[310001];
struct pp
{
	int x,y,next;
};pp p[610001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;	
}
int getson(int x,int fa)
{
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		son[x]++;getson(y,x);
	}
}
void dfs(int x,int fa,int lim)
{
	f[x]=son[x]-lim;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x,lim);
		if(f[y]>0) f[x]+=f[y];
	}
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);
	}
	getson(1,1);
	int l=son[1],r=0,ans;
	for(int i=1;i<=n;i++) r=max(r,son[i]);
	while(l<=r)
	{
		int mid=(l+r)/2;dfs(1,1,mid);
		if(f[1]<=0) r=mid-1,ans=mid;
		else l=mid+1;
	}
	printf("%d",ans);
	return 0;
}

今天比赛有两个dp不过第一个是有关边权的排排序就能做了就不放了。对于这种具有单向性的题我们都可以尝试排序来搞定,第二题是在这里插入图片描述
这个不知道叫什么的dp,时间复杂度是n^2的,枚举每一个位置,考虑从前面一位转移,怎么办呢,考虑最后一位插入j(j<=i,因为到目前为止一共只有i位),在这里引入一个 sum[j],表示为前一轮状态下,最后一位小于等于 j 的情况的和。那么转移的话一共会有三种情况,若前面强制小于自己,那么就转移sum[j-1],若强制大于自己sum[i-1]-sum[j-1],若是无要求就sum[i-1]。代码:

#include<bits/stdc++.h>
using namespace std;
int n,m1,m2,mod=1000000007;
int p[1000001],f[100001],sum[100001];
int main()
{
//	freopen("candy.in","r",stdin);
//	freopen("candy.out","w",stdout);
	memset(p,0,sizeof(p));
	scanf("%d%d%d",&n,&m1,&m2);
	for(int i=1;i<=m1;i++) 
	{
		int x;scanf("%d",&x);
		p[++x]=1,p[x+1]=2;
	}
	for(int i=1;i<=m2;i++) 
	{
		int x;scanf("%d",&x);
		p[++x]=2,p[x+1]=1;
	}
	sum[0]=0;f[1]=sum[1]=1;
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=i;j++)
		{
			if(p[i]==0) f[j]=sum[i-1];
			else if(p[i]==1) f[j]=(sum[i-1]-sum[j-1]+mod)%mod;
			else f[j]=sum[j-1];
		}
		for(int j=1;j<=i;j++) sum[j]=(sum[j-1]+f[j])%mod;
	}
	printf("%d",sum[n]);
	return 0;
}

周末电脑报废了,所以有些东西没有写上来刷了几道dp的题目
Stones博弈论套dp,记住要枚举位置,不能枚举集合中的数,因为是转移状态,从前面转移出来

#include<bits/stdc++.h>
using namespace std;
int n,m,k,a[100001];
bool v[100001];
int main()
{
	memset(v,false,sizeof(v));
	scanf("%d%d",&n,&k); 
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=k;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(a[j]<=i&&v[i-a[j]]==false) v[i]=true;
		}
	}
	if(v[k]) printf("First\n");
	else printf("Second\n");
	return 0;	
}

Sushi期望dp,具体转移看别人推的。

//动态规划优化的初等方法无非就那么几种,合并状态就是最重要的思想之一
#include<bits/stdc++.h> 
using namespace std;
int n,m,a[4];
double f[331][331][331];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		int x;scanf("%d",&x);
		a[x]++;
	}
for(int k=0;k<=n;++k){
		for(int j=0;j<=n;++j){
			for(int i=0;i<=n;++i){
				if(i||j||k){
					if(i)f[i][j][k]+=f[i-1][j][k]*i/(i+j+k);
        			if(j)f[i][j][k]+=f[i+1][j-1][k]*j/(i+j+k);
    	    		if(k)f[i][j][k]+=f[i][j+1][k-1]*k/(i+j+k);
	        		f[i][j][k]+=(double)n/(i+j+k);
         		}
			}
		}
	}
	printf("%.15lf\n",f[a[1]][a[2]][a[3]]);
	return 0;
}

Coins

//与其算成功的概率,不如将所有情况的可能算出来 
//f[i][j]指的是在第i次的时候有j枚朝上 
//那这样就可以从这东西转移啦,明显可以滚动优化
//dp的思想便是发现一个问题与上一个问题之间的关系。 
#include<bits/stdc++.h>
using namespace std;
int n,m;
double a[10001],f[3001][3001],ans=0;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
	f[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		f[i][0]=f[i-1][0]*(1-a[i]);
		for(int j=1;j<=i;j++) f[i][j]=f[i-1][j-1]*a[i]+f[i-1][j]*(1-a[i]);
	}
	for(int i=1;i<=n;i++)
	{
		if(i>n-i) ans+=f[n][i];
	}
	printf("%.10lf",ans);
	return 0;
}

LCS这一题的话,是一个很典型的题目, f[i][j]=max(f[i-1][j],f[i][j-1]);以这种方式向前转移,就是自己与前面的最佳状态比较,若没记错,倍增里面的题也有用到这种思路的。

#include<bits/stdc++.h>
using namespace std;
int n,m,len1,len2;
char s1[3021],s2[3021],ans[3021];
int f[3001][3001];
int main()
{
	scanf("%s%s",s1+1,s2+1);
	len1=strlen(s1+1);len2=strlen(s2+1);
	for(int i=1;i<=len1;i++)
	{
		for(int j=1;j<=len2;j++) 
		{
			f[i][j]=max(f[i-1][j],f[i][j-1]);
			if(s1[i]==s2[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1); 
		}
	}
	int i=len1,j=len2;//printf("*");
	while(f[i][j]!=0)
	{
		if(s1[i]==s2[j]) ans[f[i][j]]=s1[i],i--,j--;
		else 
		{
			if(f[i-1][j]==f[i][j]) i--;
			else j--;
		}
	}
	printf("%s",ans+1);
	return 0;
}

Knapsack 2这个是新型背包,也不算吧,只不过思路新些

//转换思维,当价值取i时让重量最小
//最后枚举价值取重量即可,所以我们枚举价值(10^6) 
#include<bits/stdc++.h>
using namespace std;
int n,m,maxx,v[100001],w[100001],f[2000001];
int main()
{
	memset(f,63,sizeof(f));f[0]=0;
	scanf("%d%d",&n,&m);maxx=n*1000;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&w[i],&v[i]);
		for(int j=maxx;j>=v[i];j--) f[j]=min(f[j],f[j-v[i]]+w[i]);
	}
	for(int i=maxx;i>=0;i--)
	{
		if(f[i]<=m) 
		{
			printf("%d",i);
			return 0;
		}
	}
	return 0;
}

Deque区间dp,考虑转移,分类讨论。

//考虑做一个区间dp,枚举区间的长度,再枚举左端点,计算出右端点
//那么讨论发现,若取走的数有奇数个,那么是后手取,若是偶数个,那是先手取
//然后直接转移便可 具体讨论不告诉你。 
#include<bits/stdc++.h>
using namespace std;
int n,m,a[4001],f[4001][4001];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int len=1;len<=n;len++)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1; 
			if((n-len)%2==1) f[i][j]=min(f[i+1][j]-a[i],f[i][j-1]-a[j]);
			else f[i][j]=max(f[i+1][j]+a[i],f[i][j-1]+a[j]);
		}	
	} 
	printf("%d",f[1][n]);
	return 0;
}

也是区间dpSlimes,考虑每一个区间如何转移,那么是(左+右+合并代价)前两者可以从前面转移,后者可以计算得出。

#include<bits/stdc++.h>
using namespace std;
long long n,m,f[1001][1001],a[10001];
int main()
{
	memset(f,63,sizeof(f));
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),f[i][i]=0;
	for(int len=2;len<=n;len++)
	{
		for(int i=1;i+len-1<=n;i++)
		{
			int j=i+len-1;
			for(int k=i;k<j;k++) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);//枚举断点
			for(int k=i;k<=j;k++) f[i][j]+=a[k];//加上合并的代价 
		}
	}
	printf("%lld",f[1][n]);
	return 0;
}

Candies这一题

//dp首先考虑枚举位置及那个位置小朋友得到的糖 ,再考虑如何从前面转移,呃那么就要枚举前面的小朋友的位置,时间O(n*n*k)
//考虑优化,不妨用前缀和,记sum[i][j]=f[i][k](1 <= k <= j) 那么便是(nk)的转移了 
//听别人说还有一个向后转移用差分 
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,mod=1e9+7;
int a[200001],sum[200][200001],f[200][200001];
signed main()
{
	scanf("%lld%lld",&n,&m);
	f[0][0]=sum[0][0]=1;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=m;i++) sum[0][i]+=sum[0][i-1];
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			int l=max(0ll,j-a[i]),r=j;
			f[i][j]=((f[i][j]+sum[i-1][r])%mod-sum[i-1][l-1]+mod)%mod;
		} 	
		sum[i][0]=1;
		for(int j=1;j<=m;j++) sum[i][j]=(sum[i][j-1]+f[i][j]+mod)%mod;
	}
	printf("%lld",f[n][m]);
	return 0;
}

Independent Set神奇的树形dp。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,last[200001],f[200001][3],mod=1e9+7;//0表示的是黑色,1表示的白色
//说真的还是白色受我喜欢些吧!变态吧! 
struct pp
{
	int x,y,next;
};pp p[400001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void dfs(int x,int fa)
{
	f[x][0]=f[x][1]=1;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfs(y,x);f[x][0]*=f[y][1];f[x][1]*=(f[y][0]+f[y][1]);
		f[x][0]%=mod;f[x][1]%=mod;	 
	}
	return ;
}
signed main()
{
	memset(last,-1,sizeof(last));
	scanf("%lld",&n);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		ins(x,y);ins(y,x);
	}
	dfs(1,1);
	printf("%lld",(f[1][0]+f[1][1])%mod);
	return 0;
} 

Flowers,呃不算难,拿树状数组维护。从前一个最大值的位置转移至目前这个。

//很强的树状数组思路,每次找到前面的最大值后,加上自己的值后插入自己的位置。
//那么其实就可以达到一个类似于前缀和的效果 
#include<bits/stdc++.h>
#define int long long 
#define lowbit(x) x&(-x)
using namespace std;
int n,m,h[210000],f[410000],ans=0;
void cg(int x,int k)
{
	for(;x<=n;x+=lowbit(x)) f[x]=max(f[x],k);
	return ;
}
int findsum(int x)
{
	int rt=0;
	for(x;x>=1;x-=lowbit(x)) rt=max(f[x],rt);
	return rt;
} 
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&h[i]);
	for(int i=1;i<=n;i++) 
	{
		int x,k;scanf("%lld",&x);
		k=findsum(h[i]-1)+x;ans=max(ans,k);
		cg(h[i],k);
	}
	printf("%lld",ans);
	return 0;
}

Intervals那个考虑转移,从不同的状态转移过来,那么就是。(最好看看题解)
在这里插入图片描述
这样但是O(n^3)的转移。
在这里插入图片描述
具体如何维护呢对。把 dp 数组扔到线段树上啊,然后直接像取最大值,区间加一样维护就行了,再具体的看代码吧。还有就是我们按右端点来计数,

//先做成 O(n^3)的dp,在第i个位置,前一个0在第j个位置,发现有两种状态的转移
//具体看上面,然后我们可以用滚动数组压一维,但转移还是不能接受,怎么办呢。
//把这东西扔到线段树上,维护区间的最大值,以及搞一个区间加,每次查询其实就是1到n的最大值啦 
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0;
struct node
{
	int l,r,lc,rc,maxx,lazy;
};node e[4000001];
struct node1
{
	int l,r,val;
};node1 a[2000001];
bool cmp(const node1 &x,const node1 &y)
{
	return x.r<y.r;
}
int bt(int x,int y)
{
	int now=++len;
	int l=x,r=y,lc=-1,rc=-1,maxx=0;
	if(x==y) ;
	else 
	{
		int mid=(l+r)/2;
		lc=bt(l,mid);rc=bt(mid+1,r);
	}
	e[now]={l,r,lc,rc,maxx,0};
	return now;
}
void pushdown(int now)
{
	if(e[now].lazy==0) return ;
	int lc=e[now].lc,rc=e[now].rc,k=e[now].lazy;e[now].lazy=0;
	e[lc].lazy+=k;e[rc].lazy+=k;
	e[lc].maxx+=k;e[rc].maxx+=k;
	return ;
}
void add(int now,int x,int y,int k)
{
	int l=e[now].l,r=e[now].r;
	if(l==x&&y==r) e[now].maxx+=k,e[now].lazy+=k;
	else 
	{
		int lc=e[now].lc,rc=e[now].rc,mid=(l+r)/2;pushdown(now);
		if(x>=mid+1) add(rc,x,y,k);
		else if(y<=mid) add(lc,x,y,k);
		else add(lc,x,mid,k),add(rc,mid+1,y,k);
		e[now].maxx=max(e[lc].maxx,e[rc].maxx); 
	}	
	return ; 
} 
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&a[i].l,&a[i].r,&a[i].val);
	int root=bt(1,n);sort(a+1,a+m+1,cmp);
	for(int i=1,now=1;i<=n;i++)
	{
		add(root,i,i,max(0ll,e[root].maxx));
		while(a[now].r==i) add(root,a[now].l,i,a[now].val),now++; 
	} 
	printf("%lld",max(0ll,e[root].maxx));
	return 0;
}

同样一道dp转移题,采用的是前缀和优化Permutation,转移方式和前面的前缀和转移也都差不多

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,f[3010][3010],sum[3010],mod=1e9+7;
char s[10001];
main()
{
	scanf("%lld%s",&n,s+2);f[1][1]=1;
	for(int i=2;i<=n;i++)
	{
		memset(sum,0,sizeof(sum));
		for(int j=1;j<=i-1;j++) sum[j]=sum[j-1]+f[i-1][j];
		for(int j=1;j<=i;j++)
		{
			if(s[i]=='<') f[i][j]=(f[i][j]+sum[j-1]-sum[0])%mod;
			else f[i][j]=(f[i][j]+sum[i-1]-sum[j-1])%mod; 
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans=(ans+f[n][i])%mod;
	printf("%lld",ans);
	return 0;
}

Subtree考虑树形dp,但是发现维护困难,(n^2),考虑换根。嗯。

//对于一个点now,你需要分两类,即 now 子树里(无now的)和除了 now 的子树(含 now);
//这样说太麻烦,感性理解为取黑色节点时向上走呢,还是向下走,最终两种情况的和乘起来即可,当然取法还是有说法的,不表。
//考虑维护,枚举每一个节点的话会时超,想到换根,那通过什么来转移呢。用前缀和,但是仅仅是和是无法转移的,
//因为对于每一个子节点,要乘上在他前面的所有兄弟才成
//那么就还有多一个的后缀积,然后取模的话其实用不着逆元,直接搞就行
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,len=0,last[3000001],mod;
struct pp
{
	int x,y,next;
};pp p[3000001];
int qx[3000001],hx[3000001],f[3000001],ans[3000001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;	
}
int getsum(int i,int fa,int val)
{
	if(i==-1) return 1;
	int y=p[i].y;qx[y]=val;
	if(y==fa) return hx[y]=getsum(p[i].next,fa,val)%mod;
	hx[y]=getsum(p[i].next,fa,(val*(f[y]+1))%mod);
	return (hx[y]*(f[y]+1))%mod;
}
void dfsdp(int x,int fa)
{
	f[x]=1;
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		dfsdp(y,x);f[x]=(f[x]*(f[y]+1))%mod;
	}
	getsum(last[x],fa,1);
	return ;
}
void hg(int x,int fa,int ff)
{
	if(x!=1) 
	{
		ff=((ff+1)*(qx[x]*hx[x]%mod)%mod)%mod;
		ans[x]=(f[x]*(ff+1))%mod;		
	}
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		hg(y,x,ff);
	}
	return ;
 } 
signed main()
{
	memset(last,-1,sizeof(last));
	scanf("%lld%lld",&n,&mod);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%lld%lld",&x,&y);
		ins(x,y);ins(y,x); 
	}
	dfsdp(1,1);ans[1]=f[1]%mod;
	hg(1,1,0);
	for(int i=1;i<=n;i++) printf("%lld\n",ans[i]%mod);
	return 0;
}

一道贪心+dpTower在这里插入图片描述
这里给出别人的贪心证明

//明显的01背包,问题在于如何维护 ,推导一下,先后顺序的话应该是 s[x]-w[y]>s[y]-w[x]
//为何?因为在此时先放x一定优于先放y,那么直接以此为关键字跑一次就行 
#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,f[200001],V=2e4,ans;
struct node
{
	int s,w,val;
};node a[200001];
bool cmp(const node &x,const node &y)
{
	return x.s+x.w<y.s+y.w;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i].w,&a[i].s,&a[i].val);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		for(int j=min(a[i].s+a[i].w,V);j>=a[i].w;j--) f[j]=max(f[j],f[j-a[i].w]+a[i].val); 
	} 
	for(int i=0;i<=V;i++) ans=max(ans,f[i]);
	printf("%lld",ans);
	return 0;
}

一道不知名dp题:Grid 2在这里插入图片描述
)这一题的话其实看出来就不难,提几个点,第一个是从(1,1)到(ai,bi)这一个点一共要走ai+bi-2步,所以它的贡献是在1到ai,bi这些点中选择ai+bi-2那么多个点(好累,写的不好看别人的去)。
在这里插入图片描述

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,k,mod=1e9+7;
int fact[200005],inv[200005],finv[200005],f[3005];//fact阶乘,inv逆元,finv阶乘的逆元
struct node
{
	int x,y;
};node a[1000001];
bool cmp(const node &x,const node &y)
{
	if(x.x!=y.x) return x.x<y.x;
	return x.y<y.y;
}
int C(int x,int y)
{
	return fact[x]*finv[y]%mod*finv[x-y]%mod;//分开两次取模,我快 乐死了 
}
main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	fact[0]=fact[1]=inv[1]=finv[0]=finv[1]=1ll;
	for(int i=2;i<=n+m;i++) 
	{
		fact[i]=fact[i-1]*i%mod;
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
		finv[i]=finv[i-1]*inv[i]%mod;
	}
	for(int i=1;i<=k;i++) scanf("%lld%lld",&a[i].x,&a[i].y),a[i].x--,a[i].y--;
	sort(a+1,a+k+1,cmp);a[++k].x=n-1;a[k].y=m-1;
	for(int i=2;i<=k;i++)
	{
		for(int j=1;j<i;j++) 
		{
			if(a[j].x<=a[i].x&&a[j].y<=a[i].y) 
			{
				f[i]=(f[i]+(C(a[j].x+a[j].y,a[j].x)+mod-f[j])%mod*C(a[i].x-a[j].x+a[i].y-a[j].y,a[i].x-a[j].x)%mod)%mod;//做
			}
		}
	} 
	printf("%lld\n",(C(n+m-2,n-1)-f[k]+mod)%mod);
	return 0;
}

P6475 [NOI Online #2 入门组] 建设城市,呃啊,一个不错的dp,讨论xy在同侧或者异侧的情况。放别人的题解:
在这里插入图片描述

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n,m,mod=998244353,ans=0;
int fc[2550001],inv[2550001];
int ksm(int x,int k)
{
	int rt=1;
	while(k)
	{
		if(k&1) rt=rt*x%mod;
		x=x*x%mod;k>>=1;
	}
	return rt;
}
int C(int x,int y)
{
	return fc[x+y-1]*inv[y]%mod*inv[x-1]%mod;	
}
signed main()
{
	int x,y;scanf("%lld%lld%lld%lld",&m,&n,&x,&y);fc[0]=1;
	for(int i=1;i<=m+n;i++) fc[i]=fc[i-1]*i%mod;
	inv[n+m]=ksm(fc[n+m],mod-2);	
	for(int i=m+n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
	if(x<=n&&y>n) 
	{
		for(int i=1;i<=m;i++) ans=(ans+C(i,x-1)*C(m-i+1,n-x)%mod*C(m-i+1,y-n-1)%mod*C(i,2*n-y))%mod;
	}
	else ans=C(m,n)*C(m,n+x-y)%mod;
	printf("%lld",ans);
	return 0;
}

Matching我来补题啦。状压dp耶,好欸你看n<=21,那么一眼看出压法的话是向前枚举。

#include<bits/stdc++.h>
using namespace std;
int n,m,f[1<<21+1],mod=1e9+7,a[52][52],nn;
int gettot(int x)
{
	int tot=0;
	while(x) tot+=(x&1),x>>=1;
	return tot;
}
int main()
{
	scanf("%d",&n);nn=(1<<n)-1;
	for(int i=0;i<n;i++) 
	{
		for(int j=0;j<n;j++) scanf("%d",&a[i][j]);
	}
	f[0]=1;
	for(int i=0;i<=nn;i++)
	{
		int sum=gettot(i);
		for(int j=0;j<n;j++)
		{	 
			if(!(i&(1<<j))&&a[sum][j]) (f[i|(1<<j)]+=f[i])%=mod;
		}
	}
	printf("%d",f[nn]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值