6.13模拟赛题解

前面是题解,后面是垃圾话。

T1 P1541 [NOIP2010 提高组] 乌龟棋

没脑子直接设 \(f_{p,i,j,k,w}\),为走到 \(p\),还剩 \(1,2,3,4\) 牌各 \(i,j,k,w\) 张,\(9\cdot 10^8\),发现到一个点只要三种牌的数量确定,最后一种也确定了,所以直接设 \(f_{p,i,j,k}\) 表示三种牌的就行,大力 DP 即可。

T2 P1776 宝物筛选(多重背包)

多重背包板子,赛时忘了二进制咋拆了。

a[i]=read();
sum+=a[i]*i;
int t=1;
while(a[i]>=t){
	v[++n]=i*t;
	a[i]-=t;t*=2;
}
if(a[i])v[++n]=i*a[i];

也能单调队列优化多重背包(还不会打,哈哈);

T3 P4954 [USACO09OPEN] Tower of Hay G

首先正着做很麻烦,得知道前几层的状态,正难则反,相当于我们想要每一层的宽度都大于等于上一层的。
这时可能会有贪心的思路,但这样并不对,因为这会导致我们前面的层太宽,后面不能放了,其实想明白后挺一眼不能贪的。如

6
9 8 2 1 5 5 

倒序后是5 5 1 2 8 9,贪心是5|5|1 2 8 9,但应该是5|5 1 2|8|9
对于一定的层数,自然是考虑底层越窄越好,设 \(f_i\) 表示到 \(i\)最多能叠多少层,\(g_i\) 表示此时底层最小是多少。
\(f_i=f_j+1,g_i=sum_i-sum_j\ (g_j\le sum_i-sum_j)\),发现 \(f_i\) 是单调不减的,因为我们总能将它放到上一层,导致层数不变,又因为想让 \(g_i\) 尽可能的小,所以从 \(i\) 往前的第一个满足条件的 \(j\) 就是最优转移。
条件 \(g_j\le sum_i-sum_j\) 等价于 \(g_j+sum_j\le sum_i\)\(sum_i\),所以当 \(j\) 满足条件时,它以后一定可以满足条件。
所以拿单调队列维护 \(g_j+sum_j\) 降序,\(j\) 升序,转移即可,时间复杂度 \(\mathcal{O}(n)\)

#include<bits/stdc++.h>
#define Ishar_zdl
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1e5+10;
int n,a[N],f[N],w[N],sum[N],q[N],head,tail;
signed main(){
	#ifdef Ishar_zdl
		freopen("in.in","r",stdin);freopen("out.out","w",stdout);
    #endif
	std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
	n=read();
	for(int i=n;i;--i)a[i]=read();
	for(int i=1;i<=n;++i)a[i]+=a[i-1];
	q[0]=0;int pos=0;//pos维护最后一个转移点。
	for(int i=1;i<=n;++i){
		while(head<=tail&&w[q[head]]+a[q[head]]<=a[i]){
			pos=q[head];head++;
		}
		f[i]=f[pos]+1,w[i]=a[i]-a[pos];
		while(head<=tail&&w[i]+a[i]<=w[q[tail]]+a[q[tail]]){
			--tail;
		}q[++tail]=i;
	}
	return std::cout<<f[n]<<'\n',0;
}

T4 围栏障碍训练场(acwing329)

发现正着很难确定咋走,设 \(f_{i,0/1}\) 表示从第 \(i\) 层左右端点到终点的最短路程。
比较困难的是,我们不知道应该从哪里转移,把它想象成小球的话,我们不知道它会落在哪,所以我们使用线段树查询第一个当前坐标在它的范围内的层数。时间复杂度 \(\mathcal{O}(n\log n)\)
赛时写的记忆化搜索,开了 O2 后跑飞快,时间复杂度上限 \(\mathcal{O}(n^2)\),这题数据就给 \(3\times 10^4\),没被卡挺遗憾的。

#include<bits/stdc++.h>//记忆化搜索
#define int long long
inline int read(){
	char ch=getchar();int x=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+(ch^48);
	return x*f;
}
const int N=3e4+10;
int n,s,f[N][2],l[N],r[N],tot=0;
bool vis[N][2];
inline int dfs(int i,int pd){
	if(i==n)return 0;
	if(vis[i][pd])return f[i][pd];
	int now=2e18;
	if(pd==0){
		//这两个找的循环可以拿线段树优化
		for(int k=i+1;k<=n;++k){
			if((l[i]>=l[k]&&l[i]<=r[k])||k==n){
				now=std::min(now,dfs(k,0)+abs(l[i]-l[k]));
				now=std::min(now,dfs(k,1)+abs(l[i]-r[k]));
				break;
			}
		}
	}else{
		for(int k=i+1;k<=n;++k){
			if((r[i]>=l[k]&&r[i]<=r[k])||k==n){
				now=std::min(now,dfs(k,0)+abs(r[i]-l[k]));
				now=std::min(now,dfs(k,1)+abs(r[i]-r[k]));
				break;
			}
		}
	}
	vis[i][pd]=1;
	f[i][pd]=now;
	return now;
}
signed main(){
	freopen("d.in","r",stdin);freopen("d.out","w",stdout);
	std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
	n=read(),s=read();
	for(int i=n;i;--i)l[i]=read(),r[i]=read();
	++n;
	l[n]=r[n]=0;l[0]=r[0]=s;
	std::cout<<dfs(0,0)<<'\n';
}
点击查看垃圾话
自己学的没有忘得快,简单写不对,难的想不出,天天都在焦虑。已经过去一年了,感觉在 OI 这条路上已经迷失方向了,或者说忘了自己为什么打 OI 了。
posted @ 2024-06-13 21:40  Ishar-zdl  阅读(46)  评论(0)    收藏  举报