2019.08.09【NOIP提高組】模擬 B 組 DP+矩陣乘法快速冪+數論、歐拉篩、DP+數位DP

0 粉刷匠

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

每條木板被分爲 M 個格子。

每個格子要被刷成紅色或藍色。

windy每次粉刷,只能選擇一條木板上一段連續的格子,然後塗上一種顏色。

每個格子最多隻能被粉刷一次。

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

一個格子如果未被粉刷或者被粉刷錯顏色,就算錯誤粉刷。

100%的數據,滿足 1 <= N,M <= 50 ; 0 <= T <= 2500 。

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

每個格子最多隻能被粉刷一次

很好沒有後效性了,秒變水

相當於分組揹包,先在組內DP,再做揹包

對於每條木板:設g[i][j]g[i][j]表示第i個格子粉刷了j次的最大正確粉刷數;
g[i][j]=max(g[ik][j1]+max(red[r]red[rl],blue[r]blue[rl]))g[i][j]=max(g[i-k][j-1]+max(red[r]-red[r-l],blue[r]-blue[r-l]))

對於所有木板:設f[i][j]ijf[i][j]表示選到i塊木板粉刷了j次的最大正確粉刷數
f[i][j]=max(f[i1][jk]+g[i][k])f[i][j]=max(f[i-1][j-k]+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]
其中a矩陣與b矩陣必有一邊等長

矩陣乘法快速冪:將快速冪中的aba^b的數a換成矩陣a,其中a的長寬需要相等,O(n3logb)O(n^3logb)
大部分矩陣乘法的題目都會用到快速冪

矩陣乘法可用於優化動態規劃,這題用到的是矩陣乘法在鄰接矩陣中的一個定義:
定義該鄰接矩陣爲一個圖,a[i][j]表示i到j有邊,ab[i][j]a^b[i][j]表示i在走b次後恰好到達j的方法數
哇好東西吶[無知的鄉下蒟蒻呆滯.jpg]

在這道題中,邊權較小,可以強行拆點然後矩陣乘法快速冪,O(27n3logT)O(27n^3logT)
拆點具體做法:
將編號爲i點拆爲編號爲i * 9~i * 9+8的9個點
對於一條非0的邊( i , j )= k , a[i9+k1][j9]=1a[i*9+k-1][j*9]=1,並將點(i9,j9)(i9+k1,j9)(i*9,j*9)到(i*9+k-1,j*9)的路徑加入矩陣,相當於從點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質因數分解後=p1k1p2k2...pnknp_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}
又有p1k1+p2k2+...+pnkn&lt;=np_1^{k_1}+p_2^{k_2}+...+p_n^{k_n}&lt;=n

就轉化爲一個類似揹包的東西,先篩質數,然後對每個質數的k次方(pik&lt;=np_i^k&lt;=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]表示第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));
}

未經允許,擅自特別喜歡你,不好意思了。——費渡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章