2019.08.09【NOIP提高组】模拟 B 组 DP+矩阵乘法快速幂+数论、欧拉筛、DP+数位DP

21 篇文章 0 订阅
7 篇文章 0 订阅

0 粉刷匠

windy有 N 条木板需要被粉刷。

每条木板被分为 M 个格子。

每个格子要被刷成红色或蓝色。

windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。

每个格子最多只能被粉刷一次。

如果windy只能粉刷 T 次,他最多能正确粉刷多少格子?

一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。

100%的数据,满足 1 <= N,M <= 50 ; 0 <= T <= 2500 。

————————————————————————————————————

每个格子最多只能被粉刷一次

很好没有后效性了,秒变水

相当于分组背包,先在组内DP,再做背包

对于每条木板:设 g [ i ] [ j ] g[i][j] g[i][j]表示第i个格子粉刷了j次的最大正确粉刷数;
g [ i ] [ j ] = m a x ( g [ i − k ] [ j − 1 ] + m a x ( r e d [ r ] − r e d [ r − l ] , b l u e [ r ] − b l u e [ r − l ] ) ) g[i][j]=max(g[i-k][j-1]+max(red[r]-red[r-l],blue[r]-blue[r-l])) g[i][j]=max(g[ik][j1]+max(red[r]red[rl],blue[r]blue[rl]))

对于所有木板:设 f [ i ] [ j ] 表 示 选 到 i 块 木 板 粉 刷 了 j 次 的 最 大 正 确 粉 刷 数 f[i][j]表示选到i块木板粉刷了j次的最大正确粉刷数 f[i][j]ij
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ] + g [ i ] [ k ] ) f[i][j]=max(f[i-1][j-k]+g[i][k]) f[i][j]=max(f[i1][jk]+g[i][k])

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

int n,m,t,s[60];
int h[60][60],f[2600][60];

int main(){
	scanf("%d%d%d",&n,&m,&t);
	for (int i=1;i<=n;i++){
		char ch[60];
		scanf("%s",ch+1);
		memset(h,0,sizeof h);
		for (int j=1;j<=m;j++){
			s[j]=s[j-1]+ch[j]-'0';	
			for (int l=1;l<=min(j,t);l++)
				for (int k=0;k<j;k++)			
				h[j][l]=max(h[j][l],max(h[k][l],h[k][l-1]+max(s[j]-s[k],j-k-s[j]+s[k])));
		}
		for (int j=0;j<=min(t,i*m);j++)
			for (int k=0;k<=min(j,m);k++)
			f[j][i]=max(f[j-k][i-1]+h[m][k],f[j][i]);
	}
	printf("%d",f[t][n]);
}

1 迷路

windy在有向图中迷路了。

该有向图有 N 个节点,windy从节点 0 出发,他必须恰好在 T 时刻到达节点 N-1。

现在给出该有向图,你能告诉windy总共有多少种不同的路径吗?

注意:windy不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。

100%的数据,满足 2 <= N <= 10 ; 1 <= T <= 1000000000 。


你说什么?矩阵乘法?我学过,我不会[菜鸡呆滞.jpg]
什么?矩阵乘法快速幂?什么东西?[咸鱼呆滞.jpg]

矩阵乘法定义:a*b=c———》 c [ i ] [ j ] = a [ i ] [ k ] + b [ k ] [ j ] c[i][j]=a[i][k]+b[k][j] c[i][j]=a[i][k]+b[k][j]
其中a矩阵与b矩阵必有一边等长

矩阵乘法快速幂:将快速幂中的 a b a^b ab的数a换成矩阵a,其中a的长宽需要相等, O ( n 3 l o g b ) O(n^3logb) O(n3logb)
大部分矩阵乘法的题目都会用到快速幂

矩阵乘法可用于优化动态规划,这题用到的是矩阵乘法在邻接矩阵中的一个定义:
定义该邻接矩阵为一个图,a[i][j]表示i到j有边, a b [ i ] [ j ] a^b[i][j] ab[i][j]表示i在走b次后恰好到达j的方法数
哇好东西呐[无知的乡下蒟蒻呆滞.jpg]

在这道题中,边权较小,可以强行拆点然后矩阵乘法快速幂, O ( 27 n 3 l o g T ) O(27n^3logT) O(27n3logT)
拆点具体做法:
将编号为i点拆为编号为i * 9~i * 9+8的9个点
对于一条非0的边( i , j )= k , a [ i ∗ 9 + k − 1 ] [ j ∗ 9 ] = 1 a[i*9+k-1][j*9]=1 a[i9+k1][j9]=1,并将点 ( i ∗ 9 , j ∗ 9 ) 到 ( i ∗ 9 + k − 1 , j ∗ 9 ) (i*9,j*9)到(i*9+k-1,j*9) (i9,j9)(i9+k1,j9)的路径加入矩阵,相当于从点i走到点j经过了k条边权为1的点,总边权等价于原边权k

#include <cstdio>
#include <cstring>

using namespace std;

int n,t;
int a[125][125],b[125][125];

void read(){
	scanf("%d%d",&n,&t);
	for (int i=0;i<n;i++){
		char ch[15];
		scanf("%s",ch);
		for (int j=0;j<n;j++)
		if (ch[j]-'0'>0)
			a[i*9+ch[j]-'0'-1][j*9]=1;
		for (int j=0;j<8;j++)
			a[i*9+j][i*9+j+1]=1;
	}
}

void ksm(int t){
	int c[125][125];
	for (int i=0;i<=n*9;i++)
		for (int j=0;j<=n*9;j++)
			b[i][j]=a[i][j];
	while (t){
		if (t&1==1) {
			memset(c,0,sizeof c);
			for (int i=0;i<=n*9;i++)
				for (int j=0;j<=n*9;j++)
					for (int k=0;k<=n*9;k++)
						c[i][j]=(c[i][j]+a[i][k]*b[k][j])%2009;
			for (int i=0;i<=9*n;i++)
				for (int j=0;j<=9*n;j++)
					b[i][j]=c[i][j];
		} 
		memset(c,0,sizeof c);
		for (int i=0;i<=n*9;i++)
			for (int j=0;j<=n*9;j++)
				for (int k=0;k<=n*9;k++)
					c[i][j]=(c[i][j]+a[i][k]*a[k][j])%2009;
		for (int i=0;i<=9*n;i++)
			for (int j=0;j<=9*n;j++)
				a[i][j]=c[i][j];
		t=t>>1;
	}
}

int main(){
	read();
	ksm(t-1);
	printf("%d",b[0][(n-1)*9]) ;
}

2 游戏

windy学会了一种游戏。 对于1到N这N个数字,都有唯一且不同的1到N的数字与之对应。 最开始windy把数字按顺序1,2,3,……,N写一排在纸上。 然后再在这一排下面写上它们对应的数字。 然后又在新的一排下面写上它们对应的数字。 如此反复,直到序列再次变为1,2,3,……,N。 如: 1 2 3 4 5 6 对应的关系为 1->2 2->3 3->1 4->5 5->4 6->6 windy的操作如下

1 2 3 4 5 6

2 3 1 5 4 6

3 1 2 4 5 6

12 3 5 4 6

2 3 1 4 5 6

3 1 2 5 4 6

1 2 3 4 5 6

这时,我们就有若干排1到N的排列,上例中有7排。 现在windy想知道,对于所有可能的对应关系,有多少种可能的排数。

100%的数据,满足 1 <= N <= 1000 。


没太明白题意?感觉题目描述和输出描述矛盾?
不管,事实上是按照题目描述来的,求可能的排数的种数
题意中有两种数量,排数和排数的种数

随便举一个大一点的n(比如10),再随便列一种对应关系,手动模拟过程,发现对应关系必定构成循环对应
比如1->2, 2->5, 5->1 就是一个大小为3的循环
在这样的一个循环中,循环它的大小n次后将回到正确对应(即每个数的位置是自己)

而一个n可能有多个不同的循环,排数就是这些循环大小的lcm(最小公倍数)
在同一种对应关系中,循环大小的和不超过n,而又因为可以用1来填充,所以可以不到n

排数lcm质因数分解后= p 1 k 1 ∗ p 2 k 2 ∗ . . . ∗ p n k n p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n} p1k1p2k2...pnkn
又有 p 1 k 1 + p 2 k 2 + . . . + p n k n &lt; = n p_1^{k_1}+p_2^{k_2}+...+p_n^{k_n}&lt;=n p1k1+p2k2+...+pnkn<=n

就转化为一个类似背包的东西,先筛质数,然后对每个质数的k次方( p i k &lt; = n p_i^k&lt;=n pik<=n)做背包,每种质数只能选一次

#include <cstdio>
#include <cstring>

using namespace std;

int n;
long long ans;
long long f[1050];
int b[1050],c[1050],cnt;

int main(){
	scanf("%d",&n);
	for (int i=2;i<=n;i++){
		if (b[i]==0){
			b[i]=1;
			c[++cnt]=i;
		}
		for (int j=1;i*c[j]<=n&&j<=cnt;j++){
			b[i*c[j]]=1;
			if (i%c[j]==0) break;			
		}
	}
	f[0]=1;
	for (int i=1;i<=cnt;i++)	
		for (int j=n;j>=c[i];j--)
			for (int k=c[i];k<=j;k*=c[i])
				f[j]+=f[j-k];
	for (int i=0;i<=n;i++)
		ans=(long long)ans+f[i];
	printf("%lld",ans);
}					

3 windy数

windy定义了一种windy数。

不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。

windy想知道,在A和B之间,包括A和B,总共有多少个windy数?

100%的数据,满足 1 <= A <= B <= 2000000000 。


这种l和r之间有多少个xxx的一般转化为1–n有多少的xxx,ans=ans[r]-ans[l]

然后这个满足某某条件的xx数就用数位DP求

f [ i ] [ j ] f[i][j] f[i][j]表示第i个数字,最后一个数字为j有多少个xx数

统计答案时,先把位数小于n的加上,再把位数等于n但是首数字小于n的加上
然后枚举第一个数字、第二个数字~最后一个数字等于n的加上

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int a,b;
int f[20][20];

int read(){
	int x=0;
	char ch;
	ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x;
}

void dp(){
	for (int i=0;i<=9;i++) f[1][i]=1;
	for (int i=2;i<=10;i++)
		for (int j=0;j<=9;j++)
			for (int k=0;k<=9;k++)
				if (abs(j-k)>=2)
				f[i][j]+=f[i-1][k];
}

int work(int x){
	int s[20],ans=0;
	memset(s,0,sizeof s);
	while (x){
		s[++s[0]]=x%10;
		x/=10;
	}
	for (int i=1;i<s[0];i++)
		for (int j=1;j<=9;j++)
			ans+=f[i][j];
	for (int i=1;i<s[s[0]];i++)
		ans+=f[s[0]][i];
	for (int i=s[0]-1;i>=1;i--){
		for (int j=0;j<s[i];j++){
			if (abs(j-s[i+1])>=2) 
				ans+=f[i][j];
		}
		if (abs(s[i+1]-s[i])<2) break;
	}
	return ans;
}

int main(){
	a=read(),b=read();	
	dp();
	printf("%d",work(b+1)-work(a));
}

未经允许,擅自特别喜欢你,不好意思了。——费渡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值