【补题记录】AT441,442

背景:这两场比赛笔者并没有打,这两天补完之后写一篇总结(前三题比较水我就没补,441补了DEF,442补了DF,一共5道题目)

AT_abc441_d

这道题非常水,我们注意到L非常小,而每个点的出度至多为4,所以我们从1号点开始,每个点暴力跑10条边,最坏的情况下每个点的出度均为4,所以时间复杂度只有4^l,完全可以跑过去

我们维护一个vis数组,vis[i]=1说明这个点满足题目约束,然后我们dfs时传一个目前的边权和走的边数,如果走了10步后这个点的边权和在[S,T]内那么它是一个合法的点,注意只要有一次合法判定,这个点就是合法的

具体代码如下,其实也不难实现:

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _for(i,a,b) for(int i = (a) ; i <= (b) ; i ++)
#define for_(i,a,b) for(int i = (a) ; i >= (b) ; i --)
#define ls k << 1
#define rs k << 1 | 1
#define inf 0x3f3f3f3f

int n,m,s,t,l;
const int maxn = 2e5 + 10;
struct node{
	int to,val;
};
vector<node>v[maxn]; 
set<int>st;//储存合法点 
int tag[maxn];
void dfs1(int u,int dep,int val){
	if(dep == l) {
		tag[u] |= (val >= s && val <= t);
		//合法一次就可以 
		return;
	}
	for(auto itr:v[u]){
		dfs1(itr.to,dep + 1,val + itr.val);
	}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n >> m >>l >> s >> t;
	_for(i , 1 , m){
		int x,y,z;
		cin >> x >> y >> z;
		v[x].push_back({y,z});
	}
	dfs1(1,0,0);
	_for(i , 1 , n) {
		if(tag[i]) st.insert(i);//合法 
	}
	for(auto itr:st) cout << itr << ' ';
	return 0;
}

AT_abc441_e [ABC441E] A > B substring

(这题我做的时候是黄色,为啥能升绿)

假设一个区间[l,r]满足A>B,那么会有如下式子

改写成前缀和的形式

移项

所以发现了吗,我们只需要找若干对二元组(l,r)满足这个式子的约束即可,令tmp[i]=suma[i]-sumb[i],我们只需要找到所有(l,r)使得tmp[r]>tmp[l],那么就是一个求逆序对的板子,上树状数组即可

注意l的范围是[0,r-1],不要遗漏

然后这个数tmp[i]可能是负数,我们需要离散化一下,代码有一些细节,具体如下

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _for(i,a,b) for(int i = (a) ; i <= (b) ; i ++)
#define for_(i,a,b) for(int i = (a) ; i >= (b) ; i --)
const int maxn = 2e6 + 190;
string s;
int suma[maxn ],sumb[maxn ],tmp[maxn],t[maxn];
int n,ans;
int iup = 3e5 + 10;
int lowbit(int x){return x & (- x);}
int query(int x){
	int num = 0;
	for(int i = x ; i >= 1 ; i -= lowbit(i)) num += t[i];
	return num;
}
void update(int idx){
	for(int i = idx ; i <= 1000000 ; i += lowbit(i)) t[i] ++;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n;
	cin >> s;
	s = '_' + s;
	_for(i , 1 , n) {
		suma[i] = suma[i - 1];
		sumb[i] = sumb[i - 1];
		if(s[i] == 'A' ) suma[i] ++;
		else if(s[i] == 'B') sumb[i] ++;
		tmp[i] = suma[i] - sumb[i];
	} 
	update(n + 1);//0位置 
	_for(i , 1 , n){
		ans += query(tmp[i] + n);
		update(tmp[i] + n + 1);
	}
	cout << ans;
	return 0;
}

(题外:这个题笔者做的时候是半夜,可能脑子比较昏,我想着把数组开大一点避免离散化的时候RE,又怕MLE,于是我把t数组开了3e6,别的想都没想直接maxn/10,以为n是1e5,我调10分钟也没看出来哪里RE了,最后考虑MLE的时候又一次看了题目,才发现n5e5,遂过)

AT_abc441_f [ABC441F] Must Buy

(密码币的这个破题为啥没有中文翻译)

我们设总体背包中,强制包含一个物品,所得到的最大价值为x,设总体背包中,强制不包含一个物品所得到的最大价值为y,不强制但也不排除这个物品,也就是没有任何选择约束下的01背包可以获得的最大价值是v,我们分析一下

1):物品是A类

那么最优方案中必须包含它,强制选它一定可以构造出最优方案,并且如果没有它最优方案的价值就会变小,所以x>y且x=v

2):物品是B类

那么最优方案中可以包含它,也可以不包含,那么包含它不包含它都可以构造出最优方案,所以x=y=z

3):物品是C类

那就是选了它就无法构造出最优方案,也无需关心x,y的大小关系,x<z就可以判

然后我们不能每个数都维护xi和yi,我们考虑如果不选i的话,要么选[1,i-1]的物品,要么选[i+1,n]的物品,也可以都选,我们只需要维护一个前缀最优和一个后缀最优,然后拼接即可,具体如下

我们维护dp1[i][j]表示物品[1,i]中,重量为j的最大价值,dp2[i][j]表示物品[i,n],重量为j的最大价值

这两个数组的维护就是01背包的模板(那个第一次学完就没用过的二维01背包板子)

然后我们每个物品就可以O(m)的时间内找到xi和yi,对于xi,我们先给背包塞w[i]的重量,得到val[i]的价值,然后我们再枚举j,即分给[1,i-1]的重量,同时就能计算出[i+1,n]的最大价值,即

dp2[m-w[i]-j],然后把这三部分的价值加起来和原本xi取max即可,yi也差不多,代码如下,还是很好写的

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _for(i,a,b) for(int i = (a) ; i <= (b) ; i ++)
#define for_(i,a,b) for(int i = (a) ; i >= (b) ; i --)
const int maxn = 1e3 + 10;
int dp1[maxn][50010];
int dp2[maxn][50010];
int w[maxn],val[maxn];
int n,m;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n >> m;
	_for(i , 1 , n) cin >> w[i] >> val[i];
	_for(i , 1 , n){
		_for(j , 0 , m){
			dp1[i][j] = dp1[i - 1][j];
			if(j >= w[i]) dp1[i][j] = max(dp1[i][j],dp1[i - 1][j - w[i]] + val[i]);
		}
	}
	for_(i , n , 1){
		_for(j , 0 , m){
			dp2[i][j] = dp2[i + 1][j];
			if(j >= w[i]) dp2[i][j] = max(dp2[i][j],dp2[i + 1][j - w[i]] + val[i]);
		}
	}
	int MAX = dp1[n][m];//无限制下最优 
	_for(i , 1 , n){
		int tmp1 = 0,tmp2 = 0;
		//tmp1不包含,tmp2一定包含 
		_for(j , 0 , m){
			tmp1 = max(tmp1,dp1[i - 1][j] + dp2[i + 1][m - j]);
			if(j >= w[i]) tmp2 = max(tmp2,dp1[i - 1][j - w[i]] + val[i] + dp2[i + 1][m - j]);
		}
		if(tmp2 == MAX){
			if(tmp1 == MAX) cout << 'B';
			else cout << 'A';
		}
		else cout << 'C';
	}
	return 0;
}

(这场的G至今没调出来,神秘错误)

442的D是有史以来最水D题,维护一个前缀和即可,我认为没有在这里介绍的必要,E题据同学说是极角排序(我不会),想到维护三角函数值但是至今没调出来(神秘),所以我这里只分享一下F题

AT_abc442_f [ABC442F] Diagonal Separation 2

(这个题我写了题解,点击这里获得更好观感)

这里我就不再赘述了,我简单说一个优化dp的常见套路,就是这种一维转移方向固定(比如i->i-1),另一维的转移是一个区间(比如这个题就是[j,n])这个时候我们就可以考虑使用前缀和或者后缀优化,也是比较常见的优化套路了(其实最开始没调过,换了st表写还是没过,最后发现是mn[n]没有初始赋值)

(感谢您的观看,点个赞再走呗~)

相关推荐
探序基因2 小时前
单细胞Seurat数据结构修改分群信息
数据结构
六义义3 小时前
java基础十二
java·数据结构·算法
四维碎片3 小时前
QSettings + INI 笔记
笔记·qt·算法
Tansmjs3 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
独自破碎E3 小时前
【优先级队列】主持人调度(二)
算法
weixin_445476684 小时前
leetCode每日一题——边反转的最小成本
算法·leetcode·职场和发展
打工的小王4 小时前
LeetCode Hot100(一)二分查找
算法·leetcode·职场和发展
Swift社区4 小时前
LeetCode 385 迷你语法分析器
算法·leetcode·职场和发展
sonadorje4 小时前
svd在图像处理中的应用
算法
挖矿大亨4 小时前
c++中的函数模版
java·c++·算法