1. 程式人生 > 實用技巧 >洛谷dp

洛谷dp

P1018 乘積最大

P1018 乘積最大
一串數字,加一些乘號使其乘積最大
很顯然是裸的區間dp,然後60分。。。。。因為沒有高精

int a[100];
int dp[100][100];
int getnum(int x,int y)
{
	int ans=0;
	for(int i=x;i<=y;i++)
	{
		ans=ans*10+a[i];
	}
	return ans;
}
main(void)
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		scanf("%1d",&a[i]);
	}
	for(int i=1;i<=n;i++)dp[i][0]=getnum(1,i);
	for(int i=1;i<=n;i++)
	for(int j=k;j>=1;j--)
	for(int p=i-1;p>=1;p--)
	{
		dp[i][j]=max(dp[i][j],dp[i-p][j-1]*getnum(i-p+1,i));
	}

	cout<<dp[n][k];
}



P1057 傳球遊戲

P1057 傳球遊戲
兩個屬性,傳球號碼,傳球次數,找出前後的關係即可

int n,M;
int dp[35][35]; 
main(void)
{
	cin>>n>>M;
	dp[0][0]=1;
	for(int m=1;m<=M;m++)
	for(int i=0;i<n;i++)
	{
		dp[i][m]=dp[(i-1+n)%n][m-1]+dp[(i+1+n)%n][m-1];
		//printf("i:%d m:%d=%d\n",i,m,dp[i][m]);
	}
	cout<<dp[0][M];
}




CF414B Mashmokh and ACM

CF414B Mashmokh and ACM
如果一個數列中,後一個數都能被前面一個數整除,那麼就叫這個數列為好數列。輸入n,k,求數列中最大元素為n,數列長度為k的好數列的種數(對1000000007取模)
找前面的整除比較難找,因此轉化為遞推後邊的乘法

const int p=1000000007;
int dp[2005][2005];
main(void)
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)dp[i][1]=1;
	for(int i=1;i<=n;i++)//最後一個數
	for(int j=1;j<=k;j++)//數列的個數
	{
		for(int ch=1;ch<=n/i;ch++)//列舉可以乘的數,後邊的++
		{
			dp[i*ch][j+1]+=dp[i][j]%p;
			dp[i*ch][j+1]%=p;
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)//把以所有i結尾的長度為k的加起來
	{
		ans+=dp[i][k]%p;
	}
	cout<<ans%p;
}




P1077 擺花(***)

P1077 擺花
題目描述
小明的花店新開張,為了吸引顧客,他想在花店的門口擺上一排花,共m盆。通過調查顧客的喜好,小明列出了顧客最喜歡的n種花,從1到n標號。為了在門口展出更多種花,規定第ii種花不能超過a[i]盆,擺花時同一種花放在一起,且不同種類的花需按標號的從小到大的順序依次擺列。
試程式設計計算,一共有多少種不同的擺花方案。

隱藏的揹包問題,需要自己找到價值和容量。
定義物品的種類和數列為i,a[i],每一個的價值為1,容量為m,求的的最大價值的方案數就是容量為m的方案數


int n,m;
const int p=1000007;
int a[200];
int aa[100000];
int cnt=0;
int dp[200][200];
main(void)
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		dp[i][0]=1;
	}
	dp[0][0]=1;
	//前i盆,一共擺了j盆 
	for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		for(int k=0;k<=min(j,a[i]);k++)
		dp[i][j]+=dp[i-1][j-k]%p;
		dp[i][j]%=p;
//		printf("i:%d-j:%d-dp:%d\n",i,j,dp[i][j]);
	}
	cout<<dp[n][m]%p;
}




P1586 四方定理(***)

P1586 四方定理


與上一題類似,也可以轉化為揹包問題,但是多重揹包,可定義每個平方數的價值為i,兩個容量4,和要求的n。這樣通過定義價值,把價值和滿容量聯絡起來,可以方便的求得滿容量(即最大價值)的方案數,就是要求的結果。

int a[200];
int dp[32769][5];
int f[32769];
main(void)
{
	int t;
	cin>>t;
	int n=0;
	for(int i=1;i<=t;i++)
	{
		cin>>a[i];
		n=max(a[i],n);
	}
	dp[0][0]=1;
	for(int i=1;i<=181;i++)
	for(int j=i*i;j<=n;j++)
	for(int k=1;k<=4;k++)
	{
		dp[j][k]+=dp[j-i*i][k-1];
	}		 	 
	for(int i=1;i<=t;i++)
	{
		int ans=0;
		for(int j=1;j<=4;j++)
		{
			ans+=dp[a[i]][j];
		}
		printf("%d\n",ans);
	}
}




矩陣取數

P1005 矩陣取數遊戲

思路:每一行都是獨立的,可以分別處理。
dp[i][j]為剩下[i,j]段時的價值,然後處理每一個剩下一個數的價值

int dp[100][100];
int a[100][100];
main(void)
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		cin>>a[i][j];
	}
	int anss=0;
	for(int k=1;k<=n;k++)
	{
		memset(dp,0,sizeof(dp));
	
		for(int i=1;i<=m;i++)
		for(int j=m;j>=i;j--)
		{
			if(i==1&&j==m)continue;
			if(i-1==0)dp[i][j]=dp[i][j+1]+a[k][j+1]*(1<<(m-j+i-1));
			else if(j+1==m+1)dp[i][j]=dp[i-1][j]+a[k][i-1]*(1<<(m-j+i-1));
			else dp[i][j]=max(dp[i-1][j]+a[k][i-1]*(1<<(m-j+i-1)),dp[i][j+1]+a[k][j+1]*(1<<(m-j+i-1)));
		//	printf("%d %d %d\n",i,j,dp[i][j]);
		}
		int ans=0;
		for(int i=1;i<=m;i++)
		{
			ans=max(dp[i][i]+a[k][i]*(1<<(m)),ans);
		}
		anss+=ans;
	}
	cout<<anss;
}




刪數

P2426 刪數

與上一個題目類似,但是這次不是一個個取得,因此要把dp[i][j]定義為(i,j)段的最大價值
這兩個題目都不是直接求得了答案,而是通過區間的壓縮到達一個方便判斷的狀態。


int n,a[200],dp[200][200];
main(void)
{
	int n;
	cin>>n;
	_1for(i,n)cin>>a[i];
	for(int i=0;i<=n+1;i++)
	for(int j=n+1;j>=i;j--)
	{
		if(i==0&&j==n+1)continue;
		if(i==j)continue;
		for(int k=1;k<=i;k++)//k----i
		{
			
			int index=abs(a[k]-a[i])*(i-k+1);
			if(k==i)index=a[k];
			dp[i][j]=max(dp[i][j],dp[k-1][j]+index);
		}
		for(int k=n;k>=j;k--)//j----k
		{
			int index=abs(a[j]-a[k])*(k-j+1);
			if(k==j)index=a[k];
			dp[i][j]=max(dp[i][j],dp[i][k+1]+index);
		}
		//printf("%d-%d-%d\n",i,j,dp[i][j]);
	}
	int ans=0;
	for(int i=0;i<=n;i++)
	{
		ans=max(dp[i][i+1],ans);
	}
	cout<<ans;
}




加分二叉樹

P1040 加分二叉樹

按照dp的思路,這是列舉中點,是一個裸的區間dp。按照dfs的思路,每次列舉中點分治

dfs


int root[500][500];
int mid[1000];
int m[500][500];
int dfs(int l,int r)
{
	if(m[l][r])return m[l][r];
	int ans=0;
	if(l>r)return 1;
	if(l==r)
	{
		root[l][r]=l;
		m[l][r]=mid[l];
		return mid[l];
	}
	for(int i=l;i<=r;i++)
	{
		int t=mid[i]+dfs(l,i-1)*dfs(i+1,r);
		if(t>ans)
		{
			root[l][r]=i;	
			ans=t;
			m[l][r]=ans;
		}
	}
	return ans;
}
void print(int l,int r)
{
	if(l>r)
	return ;
	printf("%d ",root[l][r]);
	print(l,root[l][r]-1);
	print(root[l][r]+1,r); 
}
main(void)
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>mid[i];
	}
	cout<<dfs(1,n)<<endl;
	print(1,n);
}




dp

int n,v[500],f[500][500],root[500][500];
void print(int l,int r)
{
	if(l>r)return ;
	if(l==r)
	{
		printf("%d ",l);return ;
	} 
	printf("%d ",root[l][r]);
	print(l,root[l][r]-1);
	print(root[l][r]+1,r); 
}
main(void)
{
	cin>>n;
	_1for(i,n)cin>>v[i];
	for(int i=1;i<=n;i++)
	{
		f[i][i]=v[i];
		f[i][i-1]=1;
	}
	for(int i=n;i>=1;i--)
	{
		for(int j=i+1;j<=n;j++)
		{
			for(int k=i;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];
					root[i][j]=k;
				}
			}
		}
	}
	cout<<f[1][n]<<endl;
	print(1,n);
}

樹形dp

上一個題感覺像一個假的樹形dp,接下來介紹幾種常見的樹形dp。參考

1.最大獨立子集
最大獨立子集的定義是,對於一個樹形結構,所有的孩子和他們的父親存在排斥,也就是如果選取了某個節點,那麼會導致不能選取這個節點的所有孩子節點。

沒有上司的舞會


int dp[6005][2];//dp[i][0]是不要i的最值,dp[i][1]是要i的最值
vector<int >g[6005];
int a[6005];
int m[6005];
int find(int now,int pre)
{
	int fa,son;
	int len=g[now].size();
	dp[now][1]=a[now];
	dp[now][0]=0;
	for(int i=0;i<len;i++)
	{
		if(g[now][i]==pre)continue;//避免重複
		son=find(g[now][i],now);//得到兒子的dp值,兒子其實是g[now][i],沒有return也可以
		dp[now][1]+=dp[son][0];
		dp[now][0]+=max(dp[son][0],dp[son][1]);
	}	
	return now;
}
main(void)
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	int x,y;
	for(int i=1;i<=n-1;i++)
	{
		cin>>y>>x;
		g[x].push_back(y);//雙向建邊,但是要注意不能往復遍歷,因此函式要記錄上一個的遍歷
		g[y].push_back(x);
	}
	int f=find(1,0);//從1開始遍歷
	printf("%d",max(dp[f][0],dp[f][1]));//因為dp[f][]是最後遍歷的(先是兒子),所以輸出這個即可
}

拓撲排序,注意兩點,從下屬遞推到上司,因為最後的上司只有一個。還有就是把握拓撲排序的思想,每次讓入度為0的點入隊。

int n,r[6005],x,y,ind[6005];
int dp[6005][2];
vector<int>g[6005];
main(void)
{
	cin>>n;
	_1for(i,n)scanf("%d",&r[i]);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		ind[y]++;
		dp[x][1]=r[x];
		dp[y][1]=r[y];
	}
	int ans=0;
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		if(!ind[i])
		{
			q.push(i);
			ans=max(ans,dp[i][1]);
		}
	}
	while(!q.empty())
	{
		int index=q.front();
		q.pop();
		for(int i=0;i<g[index].size();i++)
		{
			int indexto=g[index][i];
			ind[indexto]--;
			dp[indexto][1]+=dp[index][0];
			dp[indexto][0]+=max(dp[index][1],dp[index][0]);	
			ans=max(ans,max(dp[indexto][1],	dp[indexto][0]));	
			if(!ind[indexto])q.push(indexto);
		}
	}
	cout<<ans; 
}

P1122 最大子樹和

與上一題類似,但是這次不要兒子,孫子也不能要了。
這兩者都可以用拓撲結構遞推來做

int dp[16001][2];
int a[16001];
vector<int >g[16001];
void ss(int now,int pre)
{
	dp[now][1]+=a[now];
	int len=g[now].size();
	for(int i=0;i<len;i++)
	{
		int son=g[now][i];
		if(son==pre)continue;
		ss(son,now);
		dp[now][1]+=max(dp[son][0],dp[son][1]);
	}
}
main(void)
{
	int n,x,y;
	cin>>n;
	_1for(i,n)cin>>a[i];
	_1for(i,n-1)
	{
		cin>>x>>y;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	ss(1,0);
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		ans=max(dp[i][1],ans);
	}
	cout<<ans;
}

拓撲,當n-1條邊的最小生成樹時,可以把任意節點當做根節點。

int n,r[16005],x,y,ind[16005];
int dp[16005];
vector<int>g[16005];
main(void)
{
	cin>>n;
	_1for(i,n)scanf("%d",&r[i]);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		ind[y]++;
		dp[x]=r[x];
		dp[y]=r[y];
	}
	int ans=0;
	queue<int> q;
	for(int i=1;i<=n;i++)
	{
		if(!ind[i])
		{
			q.push(i);
			ans=max(ans,dp[i]);
		}
	}
	while(!q.empty())
	{
		int index=q.front();
		q.pop();
		for(int i=0;i<g[index].size();i++)
		{
			int indexto=g[index][i];
			ind[indexto]--;
			dp[indexto]+=max(0,dp[index]);	
			ans=max(ans,dp[indexto]);	
			if(!ind[indexto])q.push(indexto);
		}
	}
	cout<<ans; 
}

dp的字首和優化

P2513 [HAOI2009]逆序對數列
題解

我的思路是dp[i][j]插入一個數字以後dp[i+1][j+k]增加,t了,寫出表示式可以發現有一個求和的過程,可以將縮短

const int p=10000;
int dp[1000][100001];
main(void)
{
	int n,k;
	cin>>n>>k;
	dp[1][0]=1;
	for(int i=1;i<n;i++)
	for(int j=0;j<=i*(i-1)/2;j++)
	{
		for(int k=0;k<=i;k++)
		dp[i+1][j+k]+=dp[i][j]%p;	
	}
	cout<<dp[n][k]%p;
}

正解


const int p=10000;
int f[1001][1001];
main(void)
{
	int n,k;
	cin>>n>>k;
	f[1][0]=1;
	for(int i=2;i<=n;i++)
	{
		int sum=0;
		for(int j=0;j<=k;j++)
		{
			sum+=f[i-1][j]%p;
			f[i][j]=sum%p;
			if(j>=i-1)
			{
				sum-=f[i-1][j-i+1]%p;
				sum=(sum+p)%p;
			}
		}
	}
	cout<<f[n][k]%p;
}