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;
}
以后再记。