刷题笔记(2023.9.22)

路灯2

一眼区间 dp ,定义一个三维数组

f[i][j][0] 表示 ij 区间中最后关第 i 盏灯。

f[i][j][1] 表示 ij 区间中最后关第 j 盏灯。

然后可以推出状态转移方程为

int A=f[i+1][j][0]+(p[i+1]-p[i])*(sum[n]-sum[j]+sum[i]);
int B=f[i+1][j][1]+(p[j]-p[i])*(sum[n]-sum[j]+sum[i]);
int C=f[i][j-1][0]+(p[j]-p[i])*(sum[n]-sum[j-1]+sum[i-1]);
int D=f[i][j-1][1]+(p[j]-p[j-1])*(sum[n]-sum[j-1]+sum[i-1]);
f[i][j][0]=min(A,B);
f[i][j][1]=min(C,D);

其中 sum 数组为消耗总量的前缀和,可以降低时间复杂度。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=55;
int n,c,x;
int p[N],sum[N],f[N][N][2];
inline int read(){
    char c=getchar();int f=1,x=0;
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f; 
}
int main(){
    n=read();
    c=read();
    for(int i=1;i<=n;i++){
        p[i]=read();
        x=read();
        sum[i]=sum[i-1]+x;
    }
    memset(f,INF,sizeof(f));
    f[c][c][0]=f[c][c][1]=0;
    for(int l=2;l<=n;l++){
        for(int i=1;i+l-1<=n;i++){
            int j=i+l-1;
            int A=f[i+1][j][0]+(p[i+1]-p[i])*(sum[n]-sum[j]+sum[i]);
            int B=f[i+1][j][1]+(p[j]-p[i])*(sum[n]-sum[j]+sum[i]);
            int C=f[i][j-1][0]+(p[j]-p[i])*(sum[n]-sum[j-1]+sum[i-1]);
            int D=f[i][j-1][1]+(p[j]-p[j-1])*(sum[n]-sum[j-1]+sum[i-1]);
            f[i][j][0]=min(A,B);
            f[i][j][1]=min(C,D);
        }
    }
    printf("%d",min(f[1][n][0],f[1][n][1]));
    return 0;
}

搭建双塔

f[i][j][k] 表示能否用前 i 块水晶搭出高度为 j ,另一高度为 k 的塔,能为 1 ,否则为 0

那么

f[i][j][k]=f[i1][j][k]||f[i1][jh[i]][k]||f[i1][j][kh[i]]

其中第一个为不选,第二个为选的第一座塔,第三个为选的第三座塔。

最后再查找 f[n][i][i] 是否可行。

在实际操作中,第一维因为是顺序动规,所以可以直接删去,降成两维。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
const int M=1e4+100;
int n,sum,ans;
int h[N]; 
bool f[M][M];
inline int read(){
    char c=getchar();int f=1,x=0;
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f; 
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) h[i]=read(),sum+=h[i];
    sum/=2;
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=sum;j>=0;j--){
            for(int k=sum;k>=0;k--){
                bool f1=f[j][k],f2=0,f3=0;
                if(j>=h[i]) f2=f[j-h[i]][k];
                if(k>=h[i]) f3=f[j][k-h[i]];
                f[j][k]=f1||f2||f3;
            }
        }
    }
    for(int i=1;i<=sum;i++) if(f[i][i]) ans=i;
    if(ans) printf("%d",ans);
    else printf("Impossible");
    return 0;
}

暗黑破坏神

很明显的动态规划, f[i][j] 代表在第 i 种魔法花了 j 钱所获得的最大效果值。

状态转移方程就是

f[i][j]=max(f[i][j],f[i1][jka[i].c]+a[i].w[k])

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
struct node{
    int c,p;
    int w[N*5];
}a[N];
int n,m;
int f[N][N*5];
inline int read(){
    char c=getchar();int f=1,x=0;
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int main(){
    n=read();
    m=read();
    for(int i=1;i<=n;i++){
        a[i].c=read();
        a[i].p=read();
        for(int j=1;j<=a[i].p;j++) a[i].w[j]=read(); 
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            f[i][j]=f[i-1][j];
            for(int k=0;k<=a[i].p;k++){
                if(k*a[i].c>j) break;
                f[i][j]=max(f[i][j],f[i-1][j-k*a[i].c]+a[i].w[k]);
            }
        }
    }
    printf("%d",f[n][m]);
    return 0;
}

沙盘游戏

就是一道模板的最小子矩阵和,不用多说了...

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const  int N=310;
int n,m,ans;
int a[N][N],b[N][N],f[N];
inline int read(){
    char c=getchar();int f=1,x=0;
    while (c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while (c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int main(){
    n=read();
    m=read();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            a[i][j]=read();
        }
    }
    for(int i=1;i<=n;i++){
        memset(b,0,sizeof(b));
        for(int j=i;j<=n;j++){
            for(int k=1;k<=m;k++){
                b[j][k]=a[j][k]+b[j-1][k];
            }
            memset(f,0,sizeof(f));
            for(int k=1;k<=m;k++){
                f[k]=max(b[j][k],f[k-1]+b[j][k]);
                ans=max(ans,f[k]);
            }
        }
    }
    printf("%d",ans);
    return 0;
} 

消消乐

原题是:248 G
原题的加强版:262144 P

对于弱化版,可以直接定义 f[l][r] 表示从左端点 l 到右端点 r 其中合并能获得的最大分值。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=350;
int n,ans;
int a[N],dp[N][N];
inline int read(){
	char c=getchar();int f=1,x=0;
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		ans=max(ans,a[i]);
		dp[i][i]=a[i];
	}
	for(int l=2;l<=n;l++){
		for(int i=1;i+l-1<=n;i++){
			int j=i+l-1;
			for(int k=i;k<j;k++){
				if(dp[i][k]!=0&&dp[i][k]==dp[k+1][j]){
					dp[i][j]=max(dp[i][j],dp[i][k]+1);
					ans=max(ans,dp[i][j]);
				}
			}
		}
	}
	printf("%d",ans);
	return 0;
}

但是对于加强版, n 的数据变大了,如果还是这么写的话时间复杂度会超,所以我们要换一种动规思路。

定义 f[i][j] ,里面存的值是左端点为j ,能合并出 i 这个数字的右端点的位置。

所以状态转移方程就是:

f[i][j]=f[i1][f[i1][j]]

点击查看代码
#include<bits/stdc++.h> 
using namespace std;
const int N=3e5+100;
int n,ans,x;
int f[60][N];
inline int read(){
	char c=getchar();int f=1,x=0;
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		x=read();
		f[x][i]=i+1;
	}
	for(int i=2;i<=58;i++){
		for(int j=1;j<=n;j++){
			if(!f[i][j]) f[i][j]=f[i-1][f[i-1][j]];
			if(f[i][j]) ans=max(ans,i);
		}
	}
	printf("%d",ans);
	return 0;
}

最长等差子序列

一开始想了一种暴力想法,用 set 记录总共有几种公差,然后枚举公差,每一个公差都做一遍最长上升子序列,可以拿到 55pts

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+100;
const int M=5e3+100;
int n,res;
int a[M],f[N];
set<int> dc;
set<int>::iterator p;
inline int read(){
    char c=getchar();int f=1,x=0;
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int solve(int cha){
    int ans=0;
    for(int i=1;i<=n;i++) f[i]=1;
    for(int i=2;i<=n;i++){
        for(int j=1;j<i;j++){
            if(a[i]-a[j]==cha) f[i]=max(f[i],f[j]+1);
        }
    }
    for(int i=1;i<=n;i++) ans=max(ans,f[i]);
    return ans;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<n;i++)for(int j=i+1;j<=n;j++) dc.insert(a[j]-a[i]);
    for(p=dc.begin();p!=dc.end();p++){
        int cha=*p;
        res=max(res,solve(cha));
    }
    printf("%d",res);
    return 0;
}

正解是定义二维数组, f[i][j] 表示以第 i 个数为结尾,公差为 j 的最大长度。

不过由于数组下标不能为负数,但是题目又说了输入得数都是小于等于 1000 的正整数,所以就将公差为负的加上 1000 就好了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+100;
const int M=5e3+100;
int n,ans;
int a[M],f[M][N];
inline int read(){
    char c=getchar();int f=1,x=0;
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++)for(int j=0;j<=N;j++) f[i][j]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            if(a[i]!=a[j]){
                int cha=a[i]-a[j]+1000;
                f[i][cha]=max(f[i][cha],f[j][cha]+1);
                ans=max(ans,f[i][cha]);
            }
        }
    }
    printf("%d",ans);
    return 0;
}

玩具取名

状压一下。

我们令 f[i][j] 为:区间 [i,j] 的串,能转移到字母的状态

至于转移吗……劈开拼一起即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=210;
int n;
char s[N];
int m[4],tr[4][4],f[N][N];
inline int read(){
	char c=getchar();int f=1,x=0;
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int pd(char x){
    if(x=='W') return 0;
    if(x=='I') return 1;
    if(x=='N') return 2;
    if(x=='G') return 3;
}
void print(){
	if(f[1][n]&(1<<0)) printf("W");
    if(f[1][n]&(1<<1)) printf("I");
    if(f[1][n]&(1<<2)) printf("N");
    if(f[1][n]&(1<<3)) printf("G");
    if(!f[1][n]) printf("The name is wrong!");
}
int main(){
    for(int i=0;i<4;i++) m[i]=read();
    for(int i=0;i<4;i++){
    	for(int j=0;j<m[i];j++){
    		scanf("%s",s);
			tr[pd(s[0])][pd(s[1])]|=(1<<i);
		}
	}
    scanf("%s",s+1);
	n=strlen(s+1);
    for(int i=1;i<=n;i++) f[i][i]=(1<<pd(s[i]));
    for(int l=2;l<=n;l++){
    	for(int i=1,j=i+l-1;j<=n;i++,j++){
    		for(int k=i;k<j;k++){
    			for(int a=0;a<4;a++){
    				for(int b=0;b<4;b++){
        				if(!(f[i][k]&(1<<a))) continue;
        				if(!(f[k+1][j]&(1<<b))) continue;
        				f[i][j]|=tr[a][b];
					}
				}
			}
    	}
	}
    print();
    return 0;
}

任务安排

观察到我们每在第 i 个点前多分出一组,则在 i 后面所有东西的时间都会向后拖mm时刻,共计造成 m(c[n]c[i]) 点费用。

因此我们可以设 f[i] 表示前 i 个位置的最短时间。

则有

f[i]=minj=0i1{f[j]+m(c[n]c[j])+t[i](c[i]c[j])}

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF=0x3f3f3f3f3f3f3f3f;
const LL N=5e3+100; 
int n,s;
LL f[N],t[N],c[N];
inline LL read(){
	char c=getchar();LL f=1,x=0;
	while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}
int main(){
    n=read();
    s=read();
	memset(f,INF,sizeof(f));
	f[0]=0;
    for(int i=1;i<=n;i++){
    	t[i]=read();
    	c[i]=read();
		t[i]+=t[i-1];
		c[i]+=c[i-1];
	}
    for(int i=1;i<=n;i++){
    	for(int j=0;j<i;j++){
    		f[i]=min(f[i],f[j]+s*(c[n]-c[j])+t[i]*(c[i]-c[j]));
		}
	}
    printf("%lld",f[n]);
    return 0;
}
posted @   xuantianhao  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示