这里会放出一些很有意思的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;
}
//与其算成功的概率,不如将所有情况的可能算出来
//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;
}