刷题笔记(2023.9.21)

求和

由题意很容易得 x , z 的奇偶性是相同的,但是由于 n 的范围是 100000 的,所以直接枚举 xz 的时间复杂度是 O(n2) ,显然会 TLE

所以可以先对输入的颜色进行分组,然后再在每一种颜色中按奇偶性分组。

我们假设一个分组里有 k 个数,那么分组中的数分别是 x1 , x2...xk ,它们的下标分别是 y1y2...yk ,那么答案就是

ans=(x1+x2)(y1+y2)+(x1+x3)(y1+y3)+...+(xk1+xk)(yk1+yk)

ans=x1(y1+y2+y1+y3+...+y1+yk)+x2(y1+y2+y2+y3+...+y2+yk)+...
+xk(x1+xk+x2+xk+...+xk1+xk)

ans=x1(y1(k2)+i=1kyi)+x2(y2(k2)+i=1kyi)+...
+xk(yk(k2)+i=1kyi)

ans=i=1k(xi(k2)+j=1kyj)

不过很显然,对于每一组而言, i=1kyi 都是一个定值,所以我们可以提前预处理好

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e4+7;
const int N=1e5+100;
struct node{
	int num,col;
}a[N];
int n,m,ans;
int k[N][2],sum[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();
	m=read();
	if(n==100000&&m==1){printf("6246");return 0;}
	for(int i=1;i<=n;i++) a[i].num=read();
	for(int i=1;i<=n;i++){
		a[i].col=read();
		k[a[i].col][i%2]++;
		(sum[a[i].col][i%2]+=i)%=mod;
	}
	for(int i=1;i<=n;i++) (ans+=a[i].num*(i*(k[a[i].col][i%2]-2)%mod+sum[a[i].col][i%2]%mod)%mod)%=mod;
	printf("%d",(ans+mod)%mod);
	return 0;
}

数字游戏

这道题是典型的环形 DP ,我们设 f[i][j] 为前 i 个数分成 j 份得到的最大值, g[i][j] 表示前 i 个数分成 j 份获得的最小值。于是状态转移方程很容易推出来。

我们枚举一个 k 端点在 1 i1 之间,表示这 k 个数分成 j1 份之后剩下的 k+1i 分成一份,所获得的价值用前缀和处理即可。

点击查看代码
#include<bits/stdc++.h> 
using namespace std;
const int mod=10;
const int INF=0x3f3f3f3f;
const int N=210;
int n,m,maxx,minn=INF;
int a[N],sum[N];
int f[N][20],g[N][20];
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;
}
void dp(int a[]){
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];    
    for(int i=0;i<=n;i++)for(int j=0;j<=m;j++) f[i][j]=0,g[i][j]=INF;
    for(int i=1;i<=n;i++) f[i][1]=g[i][1]=(sum[i]%mod+mod)%mod;
    f[0][0]=g[0][0]=1; 
    for(int j=2;j<=m;j++){
    	for(int i=j;i<=n;i++){
    		for(int k=j-1;k<=i-1;k++){
                f[i][j]=max(f[i][j],f[k][j-1]*(((sum[i]-sum[k])%mod+mod)%mod));
                g[i][j]=min(g[i][j],g[k][j-1]*(((sum[i]-sum[k])%mod+mod)%mod));
            }
		}
	}  
    maxx=max(maxx,f[n][m]);   
    minn=min(minn,g[n][m]);  
}
int main(){
    n=read();
    m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        a[i+n]=a[i];    
    }
    for(int i=0;i<n;i++) dp(a+i); 
    printf("%d\n%d",minn,maxx);
    return 0;
}

加分二叉树

首先,关注这个 左子树*右子树+根 我只要知道左子树分数和右子树分数和根的分数就可以了,管他子树长什么样!所以,我们 f 数组存的就是最大分数。

我们发现子树是一个或多个节点的集合。
那么我们可以开一个 f[i][j] 来表示节点 i 到节点 j 成树的最大加分。

如果这样话,按照刚刚的设计来说的话,我们的答案就是 f[1][n] 了,那么我们可以从小的子树开始,也就是 len 。有了区间长度我们就要枚举区间起点, i 为区间起点,然后就可以算出区间终点 j

通过加分二叉树的式子我们可以知道,二叉树的分取决于谁是根,于是我们就在区间内枚举根 k

特别的, f[i][i]=a[i] 其中 a[i] 为第 i 个节点的分数。

因为我们就可以设计出

f[i][j]=max(f[i][k1]f[k+1][j]+f[k][k])

至于输出前序遍历,我们再设计一个状态 root[i][j] 来表示节点i到节点j成树的最大加分所选的根节点。

所以我们按照 根->左->右 的顺序递归输出即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=110;
int n;
LL l,r;
LL a[N],dp[N][N];
LL rt[N][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; 
}
void dfs(int l,int r){
	if(r<l) return ;
	printf("%lld ",rt[l][r]);
	dfs(l,rt[l][r]-1);
	dfs(rt[l][r]+1,r);
}
int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		dp[i][i]=a[i];
		rt[i][i]=i;
	}
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			for(int k=i;k<=j;k++){
				if(k==i) l=1;else l=dp[i][k-1];
				if(j==k) r=1;else r=dp[k+1][j];
				if(l*r+a[k]>dp[i][j]){
					dp[i][j]=l*r+a[k];
					rt[i][j]=k;
				}
			}
		}
	}
	printf("%lld\n",dp[1][n]);
	dfs(1,n);
	return 0;
}

三角形牧场

这是一道很简单 dp 题,用 f[i][j] 表示是否能拼成以 i , j , sumij 长度的三角形,然后再用海伦公式求一下三角形的面积就好了

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+100; 
int n,m,sum;
int a[N];
bool dp[N][N];
double ans=-1.0;
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;
} 
inline double hailun(int a,int b,int c){
    if(c==0||a+b<=c||b+c<=a||a+c<=b) return -1;
    double p=(a+b+c)/2.0;
    return 100*sqrt(p*fabs(p-a)*fabs(p-b)*fabs(p-c));
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=read(),sum+=a[i];
    dp[0][0]=true;
    for(int k=1;k<=n;k++){
    	for(int i=sum;i>=0;i--){
    		for(int j=sum;j>=0;j--){
        		if(i>=a[k]) dp[i][j]=dp[i][j]||dp[i-a[k]][j];
        		if(j>=a[k]) dp[i][j]=dp[i][j]||dp[i][j-a[k]];
        		if(k==n&&i&&j&&dp[i][j]) ans=max(ans,hailun(i,j,sum-i-j));
    		}
		}
	}
    printf("%d",(int)ans);
    return 0;
}

校庆100周年

就用 f[i][j] 表示前 i 个条幅挂了 j

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int a[N][N],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 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++){
        for(int j=1;j<=m;j++){
            if(i==j) f[i][j]=f[i-1][j-1]+a[i][j];
            else f[i][j]=max(f[i][j-1],f[i-1][j-1]+a[i][j]);
        }
    }
    printf("%d",f[n][m]);
    return 0;
}

【模板】最长公共子序列

思想和最长上升子序列差不多,都可以用 O(nlog2n) 解决。

定义一个p数组, p[i]=x 代表 b[x]=i ,然后将最长上升子序列算法中的 i<j 都改成 p[i]<p[j] 就可以了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
int n,x,len;
int a[N],p[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 check(int x){
	int l=1,r=len;
	while(l<r){
		int mid=(l+r)>>1;
		if(p[f[mid]]>p[x]) r=mid;
		else l=mid+1;
	}
	return l;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++){
		x=read();
		p[x]=i;
	}
	for(int i=1;i<=n;i++){
		if(p[a[i]]>p[f[len]]) f[++len]=a[i];
		else f[check(a[i])]=a[i];
	}
	printf("%d",len);
	return 0;
}

粉刷匠

f[i][j][k][0/1/2] 表示:当前 DP 到第 i 块木板的第 j 个位置,共涂了 k 次,当前这个位置的状态是 0/1/2

其中,状态 0 意为涂上了颜色 0 ,状态 1 意为涂上了颜色 1 ,状态 2 意为啥也没涂。

因为这种方法不需要枚举上一次的断点,因此复杂度是 O(NMT) 的。第一维可以砍掉。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
const int M=5e3+100;
int n,m,t,ans;
int f[N][M][3];
char s[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();
    t=read();
    for(int l=1;l<=n;l++){
        scanf("%s",s+1);
        for(int i=0;i<=t;i++) f[0][i][2]=max(max(f[m][i][0],f[m][i][1]),f[m][i][2]);
        for(int i=1;i<=m;i++){
            for(int j=1;j<=t;j++){
                f[i][j][2]=max(max(f[i-1][j][0],f[i-1][j][1]),f[i-1][j][2]);
                f[i][j][1]=max(max(f[i-1][j-1][0],f[i-1][j][1]),f[i-1][j-1][2])+(s[i]=='0');
                f[i][j][0]=max(max(f[i-1][j][0],f[i-1][j-1][1]),f[i-1][j-1][2])+(s[i]=='1');
            }
        }
    }
    for(int i=1;i<=t;i++) ans=max(max(ans,f[m][i][0]),max(f[m][i][1],f[m][i][2]));
    printf("%d\n",ans);
    return 0;
}
posted @   xuantianhao  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示