做题小结 dp训练6

第一个


这道题 我衷心觉得难的 考察递推

首先说下题目 要求你最终放慢 就是每列都要有 每一行也是 否则
那不叫满

考虑如何放置的问题 首先假设我们已经放了x个了 那么x可以由什么推来呢 x你可以理解成现在是边长为x的正方形 那么我们可以由x-1的正方形推过来 就是此时放对角线y=x 推来

然后就是难点了 还能怎么推
还有就是不放对角线 那放这两个棋子可导致直接少了两行两列 对吧
相当于从fx-2的答案推过来 然后我们又要想到此时我们有多少可以这样放的位置
首先一定要明确好这个f数组的含义 表示n边长正方形全部填满的方案数
于是就有fi-2*多少呢


我们只能在最外围那除了第一个不能放 只能放2*n-1个 你可能会问 为什么下面的3 2 这种为什么不放 注意我们这里f数组的定义 是放满n正方形! 所以只能对最外有贡献 这也是此题的难点和突破点 想清楚这个就能做出来

于是 就可以开心码了

void solve()
{
	cin>>n>>k;
        f[0]=1;
	for(int i=1;i<=n;i++){
		f[i]=f[i-1];
		if(i>=2)f[i]=(f[i]+(f[i-2]*(2*i-2))%mod)%mod;
	}
	for(int i=1;i<=k;i++){
		
		int x,y;cin>>x>>y;
		 if(x==y)n--;
		else n-=2;
	}
	cout<<f[n]<<endl;
	
	
}

MEX好题 下一题

这题可以用最短路 去写 ONlogn的写法 也可以用ON^2的写法 也可以On的写法 神题一道了

这个题是很经典的

首先我们讲述下 思路 观察到数据范围很小
mex的概念不再细讲 求所有操作后的最小值 某一步操作谁不清楚 不确定
这种情况 一般都是这样的for循环

for(int i=1;i<=n;i++){for(int j=1;j<i;j++) do}

考虑dp方程 操作的代价可以推导出来 对于一个数 此时的mex假设是x 操作他的次数应该是cnt-1 则代价是cnt-1*x+这个数 最后一次操作mex变成这个数了

于是 dp就完整推出来了

	dp[mex]=0;
	for(int i=mex;i>=0;i--){
		for(int j=0;j<i;j++){
			dp[j]=min(dp[j],dp[i]+(ma[j]-1)*i+j);
		}
	}

讲下优化吧 写到这里 你一定会发现 转移的过程有太多无用的状态了
比如说 次数比你少 值也比你小 肯定先删除前者对吧
所以我们要找到这个值 从他开始 然后证明下这个复杂度吧

最坏的情况就是说 n=16 你会发现 最坏的情况就是

值从小到大排序 但是次数从大到小排序 这样的话公差为1
那有多少个数呢 一个求和公式 (n+1)*n/2=16 n是根号级别的

然后两个循环就是On的 于是优化就出来了 至少怎么优化 用栈的思想就可以了

循环从小到大 然后用cnt作为条件

给出我的写法 个人认为题解更好

stack<int>s;
	s.push(0);
	for(int i=1;i<=mex;i++){
	if(cnt[i]>=cnt[s.top()])continue;
	 if(cnt[s.top()]>cnt[i])s.pop();	 
	     s.push(i);	
	}
	 mex=s.top();

题解写法

    a[++top]=0;
    for(int i=1;i<=mex;i++)
    	if(cnt[i]<cnt[a[top]])
    		a[++top]=i;

最后给出最短路的写法 Onlogn 神奇吧 还能最短路

我以前一直纠结于跑最短路一定要连边 没边不能跑最短路 这个题给了我例子

并不是最短路一定要连边题做得少了

我们找到mex 放进队列里 对于他访问的每一个值都连一条边 用min保存好 这样就可以更新了 每一个点都能跑比他小的点


 map<int,int>ma;    
	cin>>n;	
    for(int i=1;i<=n;i++){
		cin>>a[i];
		ma[a[i]]++;
	}
	int mex=0;
	while(ma[mex])mex++;
	priority_queue<node>q;
	for(int i=0;i<=mex;i++)dis[i]=1e10;
	q.push({mex,0});
	dis[mex]=0;
	while(q.size()){
		int u=q.top().u;
         q.pop();
		for(int  i=u-1;i>=0;i--){
			if(dis[i]>dis[u]+(ma[i]-1)*u+i){
				dis[i]=dis[u]+(ma[i]-1)*u+i;
				q.push({i,dis[i]});
			}
		}
	}	
	cout<<dis[0]<<endl;

最后一个题

这题不可以背包 我一开始想了半天完全背包 就是存不下 我当时想不用把他的体积一定用2^i-1 用i-1代替 比如所5就是2^5的 但是呢 这个5要怎么转移?跟背包一样写法 明确不显示2^5=32 不可以从2^3 + 2^2转移过来 于是卡壳了 写完了跑完才知道错 我也是服了

这题也充分展现了cf的人类智慧

对于一个物品的价格 他很明显可以由前面的买两个得到

如果两个的价格大于他 那就更新他的价格

然后我们要意识到可以L会远远大于这个n的 先对L拆分嘛 对于他含有1的部分 是肯定要买的 具体价格 就是前面这样定 就行了

cin>>n>>l;
	cin>>a[1];
	for(int i=2;i<=n;i++){
		cin>>a[i];
		a[i]=min(a[i-1]*2,a[i]);
	}
	int cnt=1;
	while(l){
	num[cnt++]=l%2;l/=2; 
	}
	cnt--;

	for(int i=n+1;i<=cnt;i++){
		a[i]=a[i-1]*2;
	}
	int ans=0;
	for(int i=1;i<=max(n,cnt);i++){
		ans=min(ans,a[i]);
		if(num[i])ans+=a[i];
	}
	cout<<ans<<endl;

讲下细节

	while(l){
	num[cnt++]=l%2;l/=2; 
	}
	cnt--;

这一步写的非常好 求一个数的二进制 其实也是基础吧 2024.9.1更新摆烂玩金铲铲开始复健ing

	ans=min(ans,a[i]);

这一步很重要 我们知道的二进制的话某位可以前面所有位相加-1 
所以某一时刻ans>a[i]我们就可以更新掉 

好了 写完了 中间摆烂两天 一直等手表((( 心不在焉 加油!
8.17 早

posted @   想念不动声色  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示