zoukankan      html  css  js  c++  java
  • CSP-S2020 DP专项训练

    前言

    ( ext{CPS-S2020}) 已然临近,而 ( ext{DP}) 作为联赛中的常考内容,是必不可少的复习要点,现根据教练和个人刷题,整理部分好题如下(其实基本上是直接搬……)。

    CF515C Drazil and Factorial

    题目大意

    定义 (F(x)=sum_{i=0}^{10^i<=x} ((x/10^i) mod 10) !)
    给定一个十进制数 (a),共由 (n) 个数字组成。要找到最大正数 (x) ,满足以下两个条件:

    1. (x) 不包含任何数字 (0) 和数字 (1)
    2. (F(x)=F(a))

    题解

    感觉很高级,然后发现是个打表题:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=25;
    int n,a[N];
    vector<int> bag[N];
    vector<int> res;
    int main()
    {
    	bag[2].push_back(2);
    	bag[3].push_back(3);
    	bag[4].push_back(2);
    	bag[4].push_back(2);
    	bag[4].push_back(3);
    	bag[5].push_back(5);
    	bag[6].push_back(3);
    	bag[6].push_back(5);
    	bag[7].push_back(7);
    	bag[8].push_back(2);
    	bag[8].push_back(2);
    	bag[8].push_back(2);
    	bag[8].push_back(7);
    	bag[9].push_back(2);
    	bag[9].push_back(3);
    	bag[9].push_back(3);
    	bag[9].push_back(7);
    	cin>>n;
    	for(int i=1;i<=n;++i)
    	{
    		scanf("%1d",&a[i]);
    		for(int j=0;j<(int)bag[a[i]].size();++j)
    		res.push_back(bag[a[i]][j]);
    	}
    	sort(res.begin(),res.end());
    	for(int i=(int)res.size()-1;i>=0;--i) printf("%d",res[i]);
    	printf("
    ");
    	return 0;
    }
    

    然后瞎搞一下就做完了。

    CF840C On the Bench

    题目大意

    给定一个序列 (a(a_ile 10^9)) ,长度为 (n(nle 300))

    试求有多少 (1)(n) 的排列 (p_i) ,满足对于任意的 (2le ile n)(a_{p_{i-1}} imes a_{p_i}) 不为完全平方数,答案对 (10^9+7) 取模。

    题解

    要先进行一次很巧妙的简化,不难想到,如果我们把每一个数内部的平方因子都除去,最后是不会对答案造成影响的,此时如果要形成完全平方数,只能是两个相等的数相乘。于是我们就将问题转化为了有多少种排列,相邻的数不能相等。这是一个板子题(板子我都不会)。

    然后有两种思考方向,排列组合或者是老老实实 ( ext{DP}) ,我们选择后者(前者暂时不会,会补上的)。

    定义 (f_{i,j,k}) 表示对于第 (i) 个数,前面有 (j) 组相邻的数相同, (j) 组中又有 (k) 组等于 (a_i) 。那么考虑将 (a_i) 放入,有 (3) 种情况,我们不妨设在 (i) 之前,已经有 (same) 个与 (a_i) 相等的数放入其中了:

    1. 产生新的相邻的数,位置数为 (2same-k)
    2. 断开相邻的数,位置数为 (j-k)
    3. 什么都没发生,位置数为 (i-(2same-k)-(j-k))

    用上述方法转移即可。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int N=305;
    const int MOD=1e9+7;
    int n,a[N];
    int f[N][N][N];
    signed main()
    {
    	cin>>n;
    	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
    	for(int i=1;i<=n;++i)
    	{
    		for(int j=2;j*j<=a[i];++j)
    		while(a[i]%(j*j)==0) a[i]/=j*j;
    	}
    	sort(a+1,a+1+n);
    	int tmp=1;f[1][0][0]=1;
    	for(int i=2;i<=n;++i)
    	{
    		if(a[i]!=a[i-1])
    		{
    			for(int j=0;j<i-1;++j)
    			{
    				for(int k=1;k<=min(j,tmp-1);++k)
    				f[i-1][j][0]+=f[i-1][j][k],f[i-1][j][k]=0,f[i-1][j][0]%=MOD;
    			}
    			tmp=0;
    		}
    		// printf("%d %d
    ",i,tmp);
    		for(int j=0;j<i;++j)
    		{
    			for(int k=0;k<=min(j,tmp);++k)
    			{
    				if(j&&k) f[i][j][k]+=f[i-1][j-1][k-1]*(tmp*2-k+1)%MOD,f[i][j][k]%=MOD;
    				f[i][j][k]+=f[i-1][j+1][k]*(j+1-k)%MOD,f[i][j][k]%=MOD;
    				f[i][j][k]+=f[i-1][j][k]*(i-(j-k)-(tmp*2-k))%MOD,f[i][j][k]%=MOD;
    				// printf("%lld %lld %lld %lld
    ",i,j,k,f[i][j][k]);
    			}
    		}
    		tmp++;
    	}
    	printf("%lld
    ",f[n][0][0]);
    	return 0;
    }
    

    P4766 [CERC2014]Outer space invaders

    题目大意

    你要消灭所有的外星人。对于每一个外星人,你必须在一段区间时间 $left [ l_i,r_i ight ] $ 内将其消灭,同时他距离你 (d_i)

    你有一种武器,你可以在任意时间使用他,消耗 (r_i) 的能量消灭此时距离你为 (r_i) 以内的外星人,问消灭所有的外星人的最少代价。

    题解

    这里他很巧妙的转化了问题。不难想到,如果是要消灭所有的外星人,必定是需要消除最大的一个的。如果我们选择在一个时间 (t_i) 消灭了当前最远的外星人,那么整一个时间轴会被分成 (3) 部分,时间轴完全位于 (t_i) 左右两边的外星人,和跨过 (t_i) 的外星人,而又因为我们消灭的外星人是当前区间最大的,所以跨过的这一部分外星人就会被消灭。

    这样左右两部分又变成了和一开始条件相当的区间,可以分治,直到当前区间内没有外星人了。

    写法有递归递推两种,都可以的。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=305,MAXN=1e4+5;
    int t,n;
    struct Alien{int op,ed,dis;}a[N];
    int uni[N<<1],mp[MAXN],len=0;
    int f[N<<1][N<<1];
    void solve()
    {
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i].op,&a[i].ed,&a[i].dis);
    	len=0,memset(mp,0,sizeof(mp));
    	for(int i=1;i<=n;++i) uni[++len]=a[i].op,uni[++len]=a[i].ed;
    	sort(uni+1,uni+1+len),len=unique(uni+1,uni+1+len)-uni-1;
    	for(int i=1;i<=len;++i) mp[uni[i]]=i;
    	for(int i=1;i<=n;++i) a[i].op=mp[a[i].op],a[i].ed=mp[a[i].ed];
    	memset(f,0,sizeof(f));
    	for(int i=1;i<=len;++i)
    	{
    		for(int l=1,r=i;r<=len;++l,++r)
    		{
    			int now=-1;
    			for(int j=1;j<=n;++j)
    			if(l<=a[j].op&&a[j].ed<=r&&(now==-1||a[now].dis<a[j].dis)) now=j;
    			if(now==-1) continue;
    			f[l][r]=1e9+7;
    			for(int k=a[now].op;k<=a[now].ed;++k) f[l][r]=min(f[l][r],f[l][k-1]+f[k+1][r]+a[now].dis);
    		}
    	}
    	printf("%d
    ",f[1][len]);
    	return ;
    }
    int main()
    {
    	cin>>t;
    	while(t--) solve();
    	return 0;
    }
    

    CF264C Choosing Balls

    题目大意

    你可以选若干个物品,若这次选择的物品与上次选的 (c_i) 相同那么这个的贡献就是 (a imes v_i) 否则是 (b imes v_i) 。要使得利益最大化。

    题解

    我们不难考虑到用 (f_i) 表示以颜色 (i) 为结尾的,到当前的最大利益。然后转移方程就很好写了。

    [f_{c_i}=max(f_{c_i}+a imes v_i,max_{j e c_i}f_j+b imes v_i) ]

    然后又不难想到 (f_i) 是可以用线段树维护的,但是发现还是过不了。

    仔细思考一下,发现 (f_i) 是单调递增的,所以我们只需要维护所有 (f_i) 的最大值和次大值就可以了。算是个套路。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    const int N=1e5+5;
    inline void cmax(int &a,int b){if(a<b)a=b;}
    inline void cmin(int &a,int b){if(a>b)a=b;}
    int n,m;
    struct Ball{int col,val;}a[N];
    int f[N];
    int x,y,maxn1,maxn2;
    signed main()
    {
    	cin>>n>>m;
    	// printf("
    --------------
    ");
    	for(int i=1;i<=n;++i) scanf("%lld",&a[i].val);
    	for(int i=1;i<=n;++i) scanf("%lld",&a[i].col);
    	while(m--)
    	{
    		scanf("%lld%lld",&x,&y);
    		for(int i=1;i<=n;++i) f[i]=-1e18-7;
    		maxn1=a[1].col,maxn2=0;
    		f[a[1].col]=a[1].val*y;
    		for(int i=2;i<=n;++i)
    		{
    			if(maxn1==a[i].col) cmax(f[maxn1],max(f[maxn1]+a[i].val*x,f[maxn2]+a[i].val*y));
    			else cmax(f[a[i].col],max(f[a[i].col]+a[i].val*x,f[maxn1]+a[i].val*y));
    			cmax(f[a[i].col],a[i].val*y);
    			if(a[i].col==maxn1) continue;
    			if(f[a[i].col]>=f[maxn1]) maxn2=maxn1,maxn1=a[i].col;
    			else if(f[a[i].col]>f[maxn2]) maxn2=a[i].col;
    			// printf("%lld %lld
    ",f[maxn1],f[maxn2]);
    		}
    		printf("%lld
    ",max(0ll,f[maxn1]));
    	}
    }
    

    AT2000 [AGC002F] Leftmost Ball

    也是一道套路好题。

    题目大意

    给你 (n) 种颜色的球,每个球有 (k) 个,把这 (n imes k) 个球排成一排,把每一种颜色的最左边出现的球涂成白色(初始球不包含白色),求有多少种不同的颜色序列,答案对 (10^9+7) 取模。

    题解

    因为最后回答的是颜色序列,所以每个相同颜色的小球相同。我们不妨思考最后的合法序列有怎样的特征。

    枚举一下可以发现,对于最终序列的任何一个位置,其前缀的白球数量一定不比其前缀的颜色种类(除白色)数少。所以我们不妨设 (f_{i,j}) 表示到第 (i) 个白球,且已经放完了 (j) 种颜色的方案数。

    因为我们需要满足不重不漏,所以我们不妨规定每一次放入白球和每一种颜色第一个球的时候,必定放在当前的第一个空位,可以证明这样的操作可以包括所有答案同时不遗漏答案。由此,状态转移方程易得为:

    [f_{i,j}=f_{i-1,j}+f_{i,j-1} imes (n-j+1) imes C_{n imes k-i-(j-1) imes (k-1)-1}^{k-2} ]

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define Lint long long
    const int N=2e3+5,K=2e3+5;
    const Lint MOD=1e9+7;
    int n,k;
    Lint fac[N*K];
    Lint ksm(Lint x,int k){Lint res=1;for(;k;k>>=1,x=x*x%MOD)if(k&1)res=res*x%MOD;return res;}
    Lint cal(int n,int m){return fac[n]*ksm(fac[m],MOD-2)%MOD*ksm(fac[n-m],MOD-2)%MOD;}
    Lint f[N][N];
    int main()
    {
    	cin>>n>>k;
    	if(k==1){printf("1
    ");return 0;}
    	fac[0]=1;
    	for(int i=1;i<=n*k;++i) fac[i]=fac[i-1]*i%MOD;
    	f[0][0]=1;
    	for(int i=1;i<=n;++i)
    	{
    		for(int j=0;j<=i;++j)
    		{
    			f[i][j]=f[i-1][j];
    			if(j) f[i][j]+=f[i][j-1]*(n-j+1)%MOD*cal(n*k-i-(j-1)*(k-1)-1,k-2)%MOD,f[i][j]%=MOD;
    		}
    	}
    	printf("%lld
    ",f[n][n]);
    	return 0;
    }
    

    AT4513 [AGC030D] Inversion Sum

    超级有意思的转换,感觉自己根本想不到啊。

    题目大意

    给你一个长度为 (n) 的数列,然后给你 (q) 个操作,你可以选择操作或者不操作,问所有情况下逆序对的总和。

    题解

    一眼感觉毫无思路。然后考虑移动之后的逆序对个数的变化贡献,发现难以维护两者的大小关系,但是发现我们可以计算概率,即 (i)(j) 大的概率。

    因为概率乘上总次数,就是每一个情况的次数,我们对于两个位置 (i)(j) ,我们只需要知道最后有多少种情况是 (i)(j) 大的,所以我们不妨设 (f_{i,j}) 表示当前(可能已经进行了几次交换) (a_i)(a_j) 大的概率,然后对于每一种交换,我们只需要找到与其相关的几个 (f_{i,j}) 进行更新就可以了,复杂度 (O(n(n+q))) ,可以接受。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define Lint long long
    const int N=3e3+5,M=3e3+5;
    const Lint MOD=1e9+7;
    int n,m,a[N];
    struct Opt{int x,y;}b[M];
    Lint ksm(Lint x,int k){Lint res=1;for(;k;k>>=1,x=x*x%MOD)if(k&1)res=res*x%MOD;return res;}
    Lint f[N][N];
    Lint inv=ksm(2,MOD-2);
    Lint All,res=0;
    int main()
    {
    	cin>>n>>m,All=ksm(2ll,m);
    	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    	for(int i=1;i<=m;++i) scanf("%d%d",&b[i].x,&b[i].y);
    	for(int i=1;i<=n;++i)
    	{
    		for(int j=1;j<=n;++j)
    		f[i][j]=(a[i]>a[j]);
    	}
    	for(int i=1;i<=m;++i)
    	{
    		for(int j=1;j<=n;++j)
    		{
    			if(b[i].x==j||b[i].y==j) continue;
    			Lint tmp1=f[b[i].x][j],tmp2=f[b[i].y][j];
    			f[b[i].x][j]=(tmp1+tmp2)*inv%MOD;
    			f[b[i].y][j]=(tmp1+tmp2)*inv%MOD;
    		}
    		for(int j=1;j<=n;++j)
    		{
    			if(b[i].x==j||b[i].y==j) continue;
    			Lint tmp1=f[j][b[i].x],tmp2=f[j][b[i].y];
    			f[j][b[i].x]=(tmp1+tmp2)*inv%MOD;
    			f[j][b[i].y]=(tmp1+tmp2)*inv%MOD;
    		}
    		Lint tmp1=f[b[i].x][b[i].y],tmp2=f[b[i].y][b[i].x];
    		f[b[i].x][b[i].y]=f[b[i].y][b[i].x]=(tmp1+tmp2)*inv%MOD;
    	}
    	for(int i=1;i<=n;++i)
    	{
    		for(int j=i+1;j<=n;++j)
    		res+=f[i][j]*All%MOD,res%=MOD;
    	}
    	printf("%lld
    ",res);
    }
    
  • 相关阅读:
    delphi 浮点 精度
    delphi 线程 TTask
    IOS 阻止 锁屏
    grideh SelectedRows Bookmark
    TBluetoothLE.OnDisconnectDevice
    TBluetoothLEDevice.UpdateOnReconnect
    判断字符串为空 为null
    delphi IOS 简单类型转换
    setKeepAliveTimeout
    IOS 后台之长时间任务 beginBackgroundTaskWithExpirationHandler 申请后台十分钟 600秒
  • 原文地址:https://www.cnblogs.com/Point-King/p/13890126.html
Copyright © 2011-2022 走看看