2023 qbxt 笔记整理


洛谷P4460

n<20,试试状压

\(dp[i][j]\) 表示状态为i,最后一个点为j(当前在点j)。

枚举当前点为i,要转移的点为k

转移:$ dp[i|(1<<k-1)][k]+=dp[i][j] $

还需要判断一下三点连线在不在同一条直线上。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read() {
	int x=0,f=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f?-x:x;
}
void print(int x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) print(x/10);
	putchar(x%10+48);
}
const int Mod=1e8+7;
int n,m,dp[1<<20][20],ans;
int f[1<<20];
int map1[51][51];
int x[51],y[51];
bool ss(int a,int b,int c) {
	return ((x[a]-x[b])*(y[b]-y[c])==(x[b]-x[c])*(y[a]-y[b]));
}
signed main(){
    n=read(); 
    for (int i=0;i<n;++i) {
    	x[i]=read(); y[i]=read();
	}
	for (int i=1;i<(1<<n);++i) {
		f[i]=f[i>>1]+(i&1);
	}
	for (int i=0;i<n;++i) {
		for (int j=0;j<n;++j) {
			if (i==j) continue;
			for (int k=0;k<n;++k) {
				if (k==i||k==j) continue;
				if ((((x[k]-x[i])*(x[k]-x[j])<0)||((y[k]-y[i])*(y[k]-y[j])<0))&&ss(i,k,j)) {
				    map1[i][j]|=(1<<k);
				}
 			}
		}
	} 
	for (int i=0;i<n;++i) {
		dp[1<<i][i]=1;
	} 
	for (int i=1;i<(1<<n);++i) {
	    for (int j=0;j<n;++j) {
	    	if(dp[i][j]&&((1<<j)&i)) {
	    		if (f[i]>=4) ans=(ans+dp[i][j])%Mod;
	    	    for (int k=0;k<n;++k) {
	    	    	if (!((1<<k)&i)&&(map1[j][k]&i)==map1[j][k]) {
	    	    		dp[i|(1<<k)][k]=(dp[i|(1<<k)][k]+dp[i][j])%Mod;
					}
				}
			}
		}
	}
	cout<<ans;
	return 0;
}

洛谷P4170 【CQOI2007】涂色

区间DP

设状态:

\(dp[i][j]\)表示区间\(i\)\(j\)涂色的最小次数。 $ s[i] $为目标颜色

初始化:当i==j时,只需要一次就够了,dp[i][i]初始值为1

转移:

\(s[i]==s[j]\)时,第一次涂i或涂j的时候可以顺带把i到j区间的全涂成一个颜色,从i+1或j-1转移过来的时候多涂一格就好。

\(dp[i][j]=min(dp[i][j-1],dp[i+1][j])\)

\(s[i]!=s[j]\)时,可将区间分为两部分涂色,设k点为该区间的断点,枚举k

\(dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])\)

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read() {
	int x=0,f=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f?-x:x;
}
void print(int x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) print(x/10);
	putchar(x%10+48);
}
int n,m,a[1031312],dp[1021][1021];
char ch[1923131];
signed main(){
    cin>>ch;
    int n=strlen(ch);
	memset(dp,63,sizeof(dp));
	for (int i=0;i<n;++i) {
		dp[i][i]=1;
	}
	//cout<<n<<endl;
	for (int l=1;l<n;++l) {
	    for (int i=0;i+l<=n;++i) {
	    	int j=i+l;
	    	if (ch[i]==ch[j]) {
	    	//	cout<<1<<endl;
	    		dp[i][j]=min(dp[i][j-1],dp[i+1][j]);
			}
			else {
				for (int k=i;k<=j-1;++k) {
					dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
				}
			}
		}
	}
	cout<<dp[0][n-1];
	return 0;
}

洛谷P8903

这题与普通的背包题的不同点在于“冰激凌”,可以通过消耗冰激凌获得折扣。

贪心点的想法:假设我们已经确定了最后会买哪几件物品,我们会选择对这几件物品中冰激凌需要最少的一件使用冰激凌,如果这件物品已经无法继续打折了(价格降至0),就再在剩下的物品当中选择冰激凌需要最少的一件使用冰激凌,直到冰激凌耗尽。

思路:

先按照冰激凌需求量进行升序排序,然后正着做01背包,处理出如果当前有a块钱,从后往前到第i个物品所能取得的最大价值。

再从后往前dp,枚举当前的冰激凌数量,算出在此冰激凌数量下所能取得的最大价值。

代码:

#include<bits/stdc++.h>
#define int long long 
using namespace std;
inline int read() {
	int x=0,f=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f?-x:x;
}
void print(int x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) print(x/10);
	putchar(x%10+48);
}
int n,m,dp[5021][5011],k,ans,f[5021][5021];
struct node{
	int p,c,x,cost;
}e[3023131];
bool cmp(node a,node b) {
	return a.x<b.x;
}
signed main(){
    n=read(); m=read(); k=read();
    for (int i=1;i<=n;++i) {
    	e[i].p=read(); e[i].c=read(); e[i].x=read(); 
	    e[i].cost=e[i].c*e[i].x;
	}
    sort(e+1,e+1+n,cmp);
   for (int i=1;i<=n;++i) {
   	    for (int j=0;j<=k;++j) {
   	    	f[i][j]=f[i-1][j];
		}
		for (int j=k;j>=e[i].cost;--j) {
			f[i][j]=max(f[i][j],f[i-1][j-e[i].cost]+e[i].p);
		}
		
   }
   for (int i=n;i>=1;--i) {
   	    for (int j=0;j<=m;++j) dp[i][j]=dp[i+1][j];
   	    for (int j=m;j>=e[i].c;--j) {
   	    	dp[i][j]=max(dp[i][j],dp[i+1][j-e[i].c]+e[i].p);
		}
   }
   for (int i=1;i<=n;++i) {
   	    ans=max(ans,f[i][k]+dp[i+1][m]);
   	    ans=max(ans,f[i-1][k]+dp[i][m]);
   	    for (int j=1;j*e[i].x<=min(k,e[i].cost);++j) {
   	        if (m-e[i].c+j<0) continue;
   	        ans=max(ans,f[i-1][k-j*e[i].x]+dp[i+1][m-e[i].c+j]+e[i].p);
   	        
		}
   }
   cout<<ans;
   return 0;
}

P4316绿豆蛙的归宿

基础的期望DP

该题的图一定无环(一个DAG)。

一个点被经过的概率与这个点的度有关,因为算的是期望,所以还有要考虑贡献(两点距离)。设i点的度为Du[i]。两点距离为w[i,j]。

设状态:

期望DP一般会设该点到终点的距离为状态,而非从起点到该点的距离为状态。

设dp【i】表示从i点到终点的期望距离。

终点dp[n]=0,起点dp[1]为最终结果。

转移:

$ dp[i]= \dfrac{1}{ Du[i] }*\sum dp[j]+w[i,j] $

状态转移可以通过跑拓扑实现。

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read() {
	int x=0,f=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
    for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
	return f?-x:x;
}
void print(int x) {
	if(x<0) putchar('-'),x=-x;
	if(x>9) print(x/10);
	putchar(x%10+48);
}
double dp[1231131];
int n,m,g[1023131];
int head[403131],cnt; 
int Ru[1023133];
struct node{
	int next,to,w;
}e[502131];
void add(int u,int v,int w){
	e[++cnt].next=head[u];
	e[cnt].to=v;
	e[cnt].w=w;
	head[u]=cnt;
}
void toposort() {
	queue<int>q; q.push(n);
	while(!q.empty()) {
		int now=q.front(); q.pop();
		for (int i=head[now];i;i=e[i].next) {
			dp[e[i].to]+=(dp[now]+e[i].w)/g[e[i].to];
			--Ru[e[i].to];
			if (!Ru[e[i].to]) q.push(e[i].to);
		}
	}
	
}
signed main(){
    n=read(); m=read();
    for (int i=1;i<=m;++i) {
    	int x=read(),y=read(),z=read();
		add(y,x,z);
    	Ru[x]++; 
    	g[x]++;
	}
    toposort();
    printf("%.2lf",dp[1]);
	return 0;
}

CF1572C Paint

区间DP

先化简序列,连续的一段颜色缩成一个点(不缩会TLE)

我们最终会把这块木板涂成一种颜色,不难想到最后一次涂的颜色就是最终木板的颜色。

设状态:

设dp[i][j]表示区间\([i,j]\)染成颜色\(c[j]\) 所需要的最小代价。

转移:

\(dp[i][j]=min(dp[i+1][j],dp[i][j-1])+1\)

代码:

#include<bits/stdc++.h>
using namespace std;
int T,n,m,a[3005],b[3005],s[3005],d[3005]; //a,b拿来缩点。s,d存缩点后的位置。 
int dp[3005][3005];
signed main(){
    cin>>T;
    while(T--) { //多测记得清空 
    //	memset(dp,0x3f,sizeof(dp));
    //	memset(d,0,sizeof(d));
    //	memset(s,0,sizeof(s));
    	n=0;
		cin>>m;
    	for (int i=1;i<=m;++i) {
    		cin>>b[i];
    		d[b[i]]=0;
		}
		int i=1;
		while (i<=m) { //缩点 
			int j=i;
			while(j<=m&&b[j]==b[i]) j++;
			a[++n]=b[i];
		//	cout<<a[n]<<endl; 
			i=j;
		}
		for (int i=1;i<=n;++i) {
			for (int j=1;j<=n;++j) {
				dp[i][j]=0x7ffffff;
			}
		}
		for (int i=1;i<=n;++i) { 
			dp[i][i]=dp[i+1][i]=0;
		}
		for (int i=1;i<=n;++i) {
			s[i]=0;
			if(d[a[i]]) s[i]=d[a[i]];
			d[a[i]]=i;
		//	cout<<d[a[i]]<<endl;
		}
		
		for (int len=2;len<=n;++len) {
			for (int i=1;i+len-1<=n;++i) {
				int j=i+len-1;//这里写成+1调了半天 
				dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1; 
			    for (int k=s[j];k>=i;k=s[k]) {
			    	dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
				}
			}
		}
		cout<<dp[1][n]<<'\n';
	}
	return 0;
}

洛谷P3607

区间DP

思路:

两个不下降序列如果要合并的话只需要考虑其两端点的值。a[i]的值在[1,50]内,所以可以选择枚举值域。

设状态:

dp[l][r][L][R]表示在区间[l,r]中,值域为[L,R]的最长不下降子序列长度。

转移:

先看没有翻转情况下的转移:

\(dp[l][r][L][R]=max(dp[l][r][L+1][R],dp[l][r][L][R-1]);\)

\(dp[l][r][L][R]=max(dp[l][r][L][R],dp[l+1][r][L][R]+(a[l]==L));\)

\(dp[l][r][L][R]=max(dp[l][r][L][R],dp[l][r-1][L][R]+(a[r]==R));\)

再考虑翻转情况下的转移:

\(dp[l][r][L][R]=max(dp[l][r][L][R],dp[l+1][r-1][L][R]+(a[l]==R)+(a[r]==L));\)

代码:

#include<bits/stdc++.h>
using namespace std;
int ans,n,m,a[10231],dp[101][101][101][101];
signed main(){
	cin>>n;
	for (int i=1;i<=n;++i) {
		cin>>a[i];
		for (int L=1;L<=a[i];++L) {
			for (int R=a[i];R<=50;++R) {
				dp[i][i][L][R]=1;
			}
		}
	}
	for (int len1=2;len1<=n;++len1) {
		for (int l=1,r=len1;r<=n;++l,++r) {
			for (int len2=1;len2<=50;++len2) {
				for (int L=1,R=len2;R<=50;++L,++R) {
					int sum=max(dp[l][r][L+1][R],dp[l][r][L][R-1]);
					sum=max(sum,dp[l+1][r][L][R]+(L==a[l]));
					sum=max(sum,dp[l][r-1][L][R]+(R==a[r]));
					sum=max(sum,dp[l+1][r-1][L][R]+(L==a[r])+(R==a[l]));
					dp[l][r][L][R]=sum;
					ans=max(ans,sum);
				}
			}
		}
	}
    cout<<ans;
	return 0;
}

以后再记。

posted @ 2023-05-01 20:29  int_Hello_world  阅读(33)  评论(3编辑  收藏  举报