CSP-S 模拟赛十一总结

T1

题面:

特别水的一道题。

你只需要枚举一下起点,然后看看最远能延伸到哪,然后算一下个数就行了。

代码自己写。

T2

题面:

题目上的约束条件转化一下实际上就是要求一个最长的子序列满足相邻两项中前一项的值与后一项的下标的异或值要小于后一项的值与前一项的下标的异或值。对于这种题,不难想打用 DP 来解决。

因此我们设 fif_ifi 表示以第 iii 个数为结尾能组成的最长的满足条件的子序列。于是我们可以写出一个暴力转移:

for(int i=0;i<n;i++)
{
	f[i]=1;
	for(int j=0;j<i;j++)
	{
		if((a[i]^j)>(a[j]^i))
		{
			f[i]=max(f[i],f[j]+1);
		}
	}
}

但是我们会发现这个的时间复杂度是 O(n2)O(n^2)O(n2) 的,明显只有 50pts。因此考虑优化。

看到二进制下的运算,不难想到把 iiiaia_iai 都变成二进制,我们来看看二进制下有什么特点。

因为 ai≤200a_i\le200ai200,所以 aia_iai 在二进制下最多 888 位,也就是说 ai⊕ja_i\oplus jaij 最多只会改变 jjj 的后八位,而前面的一直不变,所以如果 jjj 在二进制下的前面几位就已经小于 iii 的前几位了,那 ai⊕ja_i\oplus jaij 就一定小于 aj⊕ia_j\oplus iaji 了,因此 jjj 的范围实际上只有 [i−i mod 256,i][i-i\bmod256,i][iimod256,i] 这个区间,然后枚举一下就行了。

时间复杂度:O(256n)O(256n)O(256n)

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int n,a[300006],f[300006];
signed main()
{
//	freopen("grievous.in","r",stdin);
//	freopen("grievous.out","w",stdout);
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
	for(int i=0;i<n;i++)
	{
		f[i]=1;
		int k=i-(i&255);
		for(int j=0;j<256&&j+k<i;j++)
		{
			if((a[i]^(j+k))>(a[j+k]^i))
			{
				f[i]=max(f[i],f[j+k]+1);
			}
		}
	}
	int ans=0;
	for(int i=0;i<n;i++)
	{
		ans=max(ans,f[i]);
	}
	cout<<ans;
	return 0;
}

T3

题面:

其实我觉得这题挺难的。

首先如果 ai=bja_i=b_jai=bj,那么 iii 就有可能会到 jjj 这个位置上去,因此我们可以从 aia_iaibjb_jbj 连边(若有相同的 aia_iai 按不同的点算)。因为每个数只能变成一个数,又只能被一个数变成,所以每个点只会有一个入读、一个出度,所以最终整张图一定会变成很多个环。

现在让我们来证明一些结论:

  • 结论一:把一个环上的任意两个点所能到达的点交换后,这个环会变成两个环。
    证明:若一个环是按照 g1→g2→g3→⋯→gm→g1g_1\to g_2\to g_3\to\dots\to g_m\to g_1g1g2g3gmg1 的顺序构成的,如果被交换的是 iiijjji<ji\lt ji<j),那么原本的环就会变成这样:g1→g2→g3→⋯→gi−1→gj→⋯→gm→g1g_1\to g_2\to g_3\to\dots\to g_{i-1}\to g_j\to\dots\to g_m\to g_1g1g2g3gi1gjgmg1gi→gi+1→⋯→gj−1→gig_i\to g_{i+1}\to\dots\to g_{j-1}\to g_igigi+1gj1gi
  • 结论二:把两个环上的任意两点所能到达的点交换后,这两个环会合并成一个环。
    证明:若这两个环是 g1→g2→⋯→gm→g1g_1\to g_2\to\dots\to g_m\to g_1g1g2gmg1h1→h2→⋯→hk→h1h_1\to h_2\to\dots\to h_k\to h_1h1h2hkh1,若把 gig_igihjh_jhj 交换了,那么原图就会变成这样:g1→g2→⋯→gi−1→hj→hj+1→⋯→hj−1→gi→gi+1→⋯→gm→g1g_1\to g_2\to\dots\to g_{i-1}\to h_j\to h_{j+1}\to\dots\to h_{j-1}\to g_i\to g_{i+1}\to\dots\to g_m\to g_1g1g2gi1hjhj+1hj1gigi+1gmg1
  • 结论三:一个环上的所有点要到正确的位置的最小交换次数是环长减一。
    证明:使用数学归纳法证明。
    F(G)F(G)F(G) 表示一个环 GGG 上所有点要到正确位置的最小交换次数,S(G)S(G)S(G) 表示环 GGG 的环长。
    显然当 S(G)=1S(G)=1S(G)=1 时,F(G)=0F(G)=0F(G)=0S(G)=2S(G)=2S(G)=2 时,F(G)=1F(G)=1F(G)=1
    若对于 ∀S(G)≤k\forall S(G)\le kS(G)kF(G)=S(G)−1F(G)=S(G)-1F(G)=S(G)1,则当 S(G)=k+1S(G)=k+1S(G)=k+1 时,显然可以通过交换环上的两个点把原本的环变成两个小环 G1,G2G_1,G_2G1,G2,明显 S(G1),S(G2)<S(G),S(G1)+S(G2)=S(G)S(G_1),S(G_2)\lt S(G),S(G_1)+S(G_2)=S(G)S(G1),S(G2)<S(G),S(G1)+S(G2)=S(G),所以 F(G1)=S(G1)−1,F(G2)=S(G2)−1F(G_1)=S(G_1)-1,F(G_2)=S(G_2)-1F(G1)=S(G1)1,F(G2)=S(G2)1,所以 F(G)=F(G1)+F(G2)+1=S(G)−1F(G)=F(G_1)+F(G_2)+1=S(G)-1F(G)=F(G1)+F(G2)+1=S(G)1(加一是加上拆环时交换的那一步),因此得证。
  • 结论四:若一张图的节点数为 S(G)S(G)S(G),环的数量为 N(G)N(G)N(G),则这张图上所有点回到正确位置的最小交换次数为 S(G)−N(G)S(G)-N(G)S(G)N(G)
    证明:由结论三可知:每个环 GiG_iGi 的最小交换次数是 S(Gi)−1S(G_i)-1S(Gi)1,因此总的最小交换次数就是 ∑S(Gi)−1=S(G)−N(G)\sum S(G_i)-1=S(G)-N(G)S(Gi)1=S(G)N(G),得证。

知道结论四后,这道题就非常好做了。因为我们要最小交换次数最大化,所以就是要这张图内的环的个数尽可能的少。因为相同的数不会放在同一个环上,所以我们只需要把那些不相同的数尽可能的放在同一个环上就行了。

要放在同一个环上,我们只需要让这些数都错位而且正好能形成一个环就行了,那最简单的方法就是让第一个数换到最后一个位置上,然后其他数都往前挪一个单位,这样正好能形成一个环。

然后代码就很好写了。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int n,a[200006],cnt[200006];
vector<int>v[200006];
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		v[++cnt[a[i]]].push_back(i);
	}
	for(int i=1;i<=n;i++)
	{
		sort(v[i].begin(),v[i].end(),[&](int x,int y)
		{
			return a[x]<a[y];
		});
		for(int j=1;j<v[i].size();j++)
		{
			swap(a[v[i][j-1]],a[v[i][j]]);
		}
	}
	for(int i=1;i<=n;i++)
	{
		cout<<a[i]<<" ";
	}
	return 0;
}

T4

题面:

这题其实比 T2 还好想。

首先看到这种题明显设 DP。设 fi,jf_{i,j}fi,j 表示前 iii 个中选了 jjj 个切满足条件的最大总和。

你可以试着转移一下这个 DP,但是你很快就会发现这个 DP 没法转移,因为你不知道当前这个点有没有被选过,这时有两种方案:

  1. 增加限制条件。
  2. 增加定义的维度。

我选的是第二种方法:设 f0/1,i,jf_{0/1,i,j}f0/1,i,j 表示前 iii 个中选了 jjj 个且当前点没被选过/被选过的最大总和。于是你就能很愉快的转移这个 DP 了:

f1,i,j=max⁡{f0,i−1,j−1,f1,i−1,j−1}+aif0,i,j=max⁡l=max⁡{1,i−k+1}i−1{f1,l,j} f_{1,i,j}=\max\{f_{0,i-1,j-1},f_{1,i-1,j-1}\}+a_i \\ f_{0,i,j}=\max_{l=\max\{1,i-k+1\}}^{i-1}\{f_{1,l,j}\} f1,i,j=max{f0,i1,j1,f1,i1,j1}+aif0,i,j=l=max{1,ik+1}maxi1{f1,l,j}

写出来你会发现时间复杂度是 O(nk2)O(nk^2)O(nk2) 的,明显过不了。

仔细观察第二个转移方程,你会发现里面的那一块其实是一个滑动窗口!所以使用滑动窗口优化 DP,时间复杂度 O(nk)O(nk)O(nk)

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int n,k,m,a[5006],f[2][5006][5006];
deque<int>dq[5006];
signed main()
{
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	if(k*((m-1)*2+1)<n)
	{
		cout<<-1;
		return 0;
	}
	memset(f,-0x3f,sizeof(f));
	f[0][0][0]=0;
	for(int i=1;i<=n;i++)
	{
		if(i<m)f[0][i][0]=0;
		for(int j=1;j<=min(k,i);j++)
		{
			f[1][i][j]=max({f[1][i][j],f[1][i-1][j-1]+a[i],f[0][i-1][j-1]+a[i]});
			while(!dq[j].empty()&&dq[j].front()<max(1ll,i-m+1))
			{
				dq[j].pop_front();
			}
			if(!dq[j].empty())
			{
				f[0][i][j]=max(f[0][i][j],f[1][dq[j].front()][j]);
			}
			while(!dq[j].empty()&&f[1][dq[j].back()][j]<f[1][i][j])
			{
				dq[j].pop_back();
			}
			dq[j].push_back(i);
		}
	}
	cout<<max(f[0][n][k],f[1][n][k]);
	return 0;
}

总结

  • T1:100/100(正常操作)。
  • T2:50/100(脑抽以为 n≤2×105n\le2\times10^5n2×105,然后完美 RE 了)。
  • T3:0/0(其实没太看懂题)。
  • T4:90/100(考试的时候没注意到当前这个点是否一定被选上这个问题,然后写了个伪 DP,不过能过九个点也很好了)。
  • 总分:240/300(挂了 60pts,其中 T2 最不应该)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值