【做题记录】CodeForces 做题记录

这篇博客记录了作者在CodeForces上完成的一系列算法题目,包括动态规划、线段覆盖、序列优化等问题,涉及难度从2100到3100不等,适合进阶练习。
摘要由CSDN通过智能技术生成

链接放的是洛谷上的链接,难度就是 CF 上的评分。

<details>

<summary>$\texttt{solution}$</summary>

</details>

CF10D LCIS

难度:\(\tt{2800}\)

求两个串的最长公共上升子序列。\(n\le 500\)

$\texttt{solution}$

严重虚高题,但我是个 saber

求两个串的最长公共子序列,设 \(f_{i,j}\) 表示到 \(s_i,t_j\) 的最大匹配。

求一个串的最长上升子序列,记下最大值为 \(i\) 时的答案。

那么。。合并!!!

\(f_{i,j}\) 表示当前以 \(s_i\) 这个数结尾,到 \(t\) 串第 \(j\) 个位置的最大匹配。

则每次转移可以从 \(k\le i,p<j\) 处转移而来,二维树状数组即可。

CF1579G Minimal Coverage

难度:\(\texttt{2200}\)

给你 \(n\) 条线段,告诉你每条线段的长度。

你需要把它们放在一条无限长的数轴上。放置需满足当前线段的起点是前一个线段的终点,特别的第一个线段的起点为 \(0\)

也就是说,若前一个线段的终点是 \(x\),当前长度为 \(d\),那么这个线段必须放在 \([x-d,x]\) 终点变为 \(x-d\),或 \([x, x + d]\) 终点变为 \(x + d\)

请问放置完后所有线段的最小覆盖长度是多少?

\(1\le t\le 10^4, 1\le n\le 10^4\)

$\texttt{solution}$

第一遍没有做出来的原因只要是主要不知道怎样规定 dp 状态。

考虑设 \(dp(i,j)\) 表示第 \(i\) 条边,当前端点距离左端点为 \(j\) 时的最短扩展距离。

之后转移就变得方便多了。

CF1575L Longest Array Deconstruction

难度:\(\texttt{2100}\)

\(a\) 是一个序列,定义 \(f_a\) 表示满足 \(a_i=i\) 的位置 \(i\) 的数目。

给你一个长度为 \(n\) 的序列 \(a\),可以删除若干个数字,最大化 \(f_a\)

\(1\le n,a_i\le 2\times 10^5\)

$\texttt{solution}$

\(\texttt{solution1:}\)

我们考虑朴素的 \(n^2\) dp 怎么做:

咕咕咕

\(\texttt{solution2:}\)

我们考虑一个数如果要被选择它必须向左移动 \(i-a_i\) 个位置(如果 \(i-a_i<0\) 那么它必然不会被选择)。

如果我们要选择一串位置,那么必须保证他们的 \(i-a_i\) 不下降(当然同时要保证 \(a_i\) 递增)。

这样容易想到 \(\text{LIS}\) 问题,即单调上升(不下降)子序列问题,直接套板子求解即可。

#define Maxn 200005
int n,ans,minn[Maxn];
pa a[Maxn];
int main()
{
	 n=rd();
	 for(int i=1;i<=n;i++) a[i]=pa(rd(),-i);
	 sort(a+1,a+n+1);
	 for(int i=1,pos,x;i<=n;i++)
	 {
	 	 x=-a[i].se-a[i].fi;
	 	 if(x<0) continue;
	 	 pos=upper_bound(minn+1,minn+ans+1,x)-minn;
	 	 minn[pos]=x,ans=max(ans,pos);
	 }
	 printf("%d\n",ans);
	 return 0;
}

CF1582F2 Korney Korneevich and XOR (hard version)

难度:\(\texttt{2400}\)

给一个长度为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\)

要求选出一段子序列 \(a_{p_1},a_{p_2},\dots,a_{p_k}\) 满足 \(p_1<p_2<\dots<p_k\)\(a_{p_1}<a_{p_2}<\dots<a_{p_k}\),设这段子序列中每个元素的异或之和为 \(t\),求出 \(a\) 的所有子序列可能存在的异或和并自小而大输出。

\(n\le 10^6,max{a}\le 5000\)

$\texttt{solution}$

我们考虑到以一个数为结尾的子序列中可能出现的异或和个数不超过 \(8291=2^{13}-1\),所以我们需要对于一个数以及以前的异或和加以讨论。

我们有注意到如果对于 \(i<j,a_i=a_j\) 以它们为结尾的子序列为相同的异或和,那么我们完全可以只考虑 \(i\) 而忽略 \(j\)

因此我们对于同一个数结尾,最多有 \(2^{13}\) 可能的异或,只要我们只处理这些数复杂度就是对的了。

对于一个已知的异或和,我们可以枚举比 \(a_i\) 大的所有数并给它们记录这个异或和(注意如果发现有一个数记录过这个异或和那么不用再往下拉,因为它后面的位置都由上一次记录这个数的时候记录过了)。

最后统计答案即可。

这一题发现 \(n\) 的复杂度必须是线性的,而值域为 \(5000\),引导我们向 \(val^2\) 的方向思考。

要多考虑正解复杂度与优化。

#define Maxn 1000005
#define Maxa 8200
int n,All=8191;
int a[Maxn];
bitset<Maxa> ans[Maxa],vis[Maxa];
vector<int> pre[Maxn];
int main()
{
	 n=rd();
	 for(int i=0;i<=All;i++) pre[i].pb(0);
	 for(int i=1;i<=n;i++)
	 {
	 	 a[i]=rd();
	 	 if(!vis[a[i]][a[i]]) pre[a[i]].pb(a[i]),vis[a[i]][a[i]]=true;
	 }
	 for(int i=1;i<=n;i++)
	 {
	 	 for(int j:pre[a[i]])
	 	 {
	 	 	 ans[a[i]][j]=true;
	 	 	 for(int k=a[i]+1;k<=5000;k++)
	 	 	 {
	 	 	 	 if(vis[k][k^j]) break;
	 	 	 	 vis[k][k^j]=true,pre[k].pb(k^j);
			 }
		 }
		 pre[a[i]].clear();
	 }
	 for(int i=1;i<=5000;i++) ans[0]|=ans[i];
	 printf("%lld\n",ans[0].count());
	 for(int i=0;i<=All;i++) if(ans[0][i]) printf("%d ",i);
	 printf("\n");
	 return 0;
}

CF1601C Optimal Insertion

难度:\(\texttt{2300}\)

给定两个序列 \(a,b\),长度分别为 \(n,m(1\leq n,m\leq 10^6)\)。接下来将 \(b\) 中的所有元素以任意方式插入序列 \(a\) 中任意位置,请找出一种插入方式使结果序列中的逆序对数量最小化,并输出这个最小值。

关于插入:任意方式插入任意位置的示例如下。

例如 \(a=\{1,2,3,4\},b=\{4,5,6\}\),则 \(c=\{4,\underline1,5,\underline2,\underline3,\underline4,6\},\{\underline1,\underline2,6,5,\underline3,4,\underline4\}\dots\) 均为合法的插入方式。但你不能修改 \(a\) 的顺序。

$\texttt{solution}$

首先有一个结论:如果把 \(b\) 排序后加入答案一定不变劣。

如果 \(b\) 没排好序,交换 \(b\) 的两个逆序元素答案不会变劣。

之后考虑分治的方法求解,我们先将 \(b[mid]\) 加入最佳位置中,再分左右加入左边与右边(这样可以减小复杂度,不用每个数去求最小值)。

可以设 \(pos(i)\) 表示将 \(b_i\) 加入 \(a_{pos(i)}\) 以前(特别的,如果加载序列末尾,\(pos=n+1\))。

最后用树状数组求解最终逆序对即可。

#define Maxn 2000005
int n,m,cnt,tot;
int in_tr[Maxn],a[Maxn],b[Maxn],seq[Maxn],pos[Maxn],L[Maxn],R[Maxn];
ll ans;
map<int,int> tr_in;
struct BIT
{
	 int tree[Maxn];
	 inline void init(){ for(int i=1;i<=cnt;i++) tree[i]=0; }
	 inline void add(int x,int k){ while(x<=cnt) tree[x]+=k,x+=x&(-x); }
	 inline int query(int x){ int ret=0; while(x) ret+=tree[x],x-=x&(-x); return ret; }
};
BIT T;
void solve(int l,int r,int nl,int nr)
{
	 if(nl>nr) return;
	 int mid=(nl+nr)>>1,cho=0,minn=inf;
	 L[l]=R[r]=0;
	 for(int i=l+1;i<=r;i++) L[i]=L[i-1]+(a[i-1]>b[mid]);
	 for(int i=r-1;i>=l;i--) R[i]=R[i+1]+(b[mid]>a[i]);
	 for(int i=l;i<=r;i++) if(L[i]+R[i]<minn) minn=L[i]+R[i],cho=i;
	 pos[mid]=cho;
	 solve(l,cho,nl,mid-1),solve(cho,r,mid+1,nr);
}
bool cmp(int x,int y){ return x<y; }
int main()
{
	 int Test=rd();
	 while(Test--)
	 {
	 	 n=rd(),m=rd(),cnt=tot=ans=0;
	 	 for(int i=1;i<=n;i++) a[i]=rd(),in_tr[++cnt]=a[i];
	 	 for(int i=1;i<=m;i++) b[i]=rd(),in_tr[++cnt]=b[i];
	 	 sort(in_tr+1,in_tr+cnt+1,cmp),sort(b+1,b+m+1,cmp);
	 	 cnt=unique(in_tr+1,in_tr+cnt+1)-in_tr-1;
	 	 for(int i=1;i<=cnt;i++) tr_in[in_tr[i]]=i;
	 	 for(int i=1;i<=n;i++) a[i]=tr_in[a[i]];
	 	 for(int i=1;i<=m;i++) b[i]=tr_in[b[i]];
	 	 solve(1,n+1,1,m);
	 	 for(int r=1,l=1;r<=m;r++)
	 	 {
	 	 	 while(l<=n &am
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值