平邑集训(补题)

Day 1

A 咕咕

题目描述



解法

DP,设 \(dp_{i,j}\) 表示从 \((1,1)\) 走到 \((n,m)\) 的方案数。

转移的时候,需要按照给定的限制走,如果一个点的(2)(3)限制冲突了,那么就标记一下,经过他的时候绕过他,时间复杂度 \(O(nm)\)

代码

点击查看代码
int n,m,t;
int f[3001][3001];
int dp[3001][3001];

void solve()
{
	cin>>n>>m>>t;
	int i,j;
	while(t--)
	{
		int a,b,c,d;
		cin>>a>>b>>c>>d;
		bool flag = 0;
		if(a==n&&b==m)
			continue;
		if((c==a+1&&d==b)||(d==b+1&&a==c))
			flag = 1;
		if(!flag||f[a][b]==-1)
		{
			f[a][b] = -1;
			continue;
		}
		if(c==a+1)
		{
			if(f[a][b]==2)
				f[a][b] = -1;
			else
				f[a][b] = 1;
		}
		else if(d==b+1)
		{
			if(f[a][b]==1)
				f[a][b] = -1;
			else
				f[a][b] = 2;
		}
	}
	if(f[1][1]==-1)
	{
		cout<<0<<endl;
		return;
	}
	dp[1][1] = 1;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
		{
			if(f[i][j]==-1)
				dp[i][j]=0;
			else
			{
				if(f[i-1][j]!=2)
					dp[i][j] += dp[i-1][j];
				if(f[i][j-1]!=1)
					dp[i][j] += dp[i][j-1];
				dp[i][j] %= mod;
			}
		}
	cout<<dp[n][m]<<endl;
	return;
}

B 找子串

题目描述

解法

  • Subtask 1

每次删掉 \(T\) 后重新暴力再找第一个 \(T\),时间复杂度 \(O(\dfrac{|S|}{|T|}\cdot |S| \cdot |T|)\)

  • Subtask 2

每次删掉 \(T\) 后用 kmp 或哈希找到第一个 \(T\),时间复杂度 \(O(\dfrac{|S|}{|T|}\cdot |S|)\)

  • Subtask 3

每次删掉 \(T\) 后,从删掉位置之前的 \(T\) 个位置开始,用 kmp 或哈希找 \(T\),时间复杂度 \(O(|S|\cdot |T|)\)

  • 满分做法

用 kmp 从 \(S\) 中找 \(T\) 时,对于 \(S\) 中的每一个位置 \(i\),记录以 \(i\) 为结尾的子串,和 \(T\) 最多匹配到哪里,可以用一个数组记录下来,比如 \(match\)

举个例子:若 \(S="gogoododgoodluck",T="good"\).
\(match_0 = 0\),因为 \(S\) 中的 g\(T\) 中的 g 匹配。
\(match_1 = 1\),因为 \(S\) 中的 go\(T\) 中的 go 匹配。
\(match_2 = 0\),因为 \(S\) 中的 g\(T\) 中的 g 匹配。
\(match_3 = 1\),因为 \(S\) 中的 go\(T\) 中的 go 匹配。
\(match_4 = 2\),因为 \(S\) 中的 goo\(T\) 中的 goo 匹配。
\(match_5 = 3\),因为 \(S\) 中的 good\(T\) 中的 good 匹配。

在上面的例子中,如果将第一个 good\(S\) 中删除后,没有必要从头开始找 good,因为以前就知道 \(match_1=1\),因此从 \(S_1\) 处继续 kmp 即可,可以通过栈来实现。

代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 7;
char s[maxn], t[maxn];
char sta[maxn];
int fail[maxn], fail1[maxn], n, m, top;
void init()
{
	for (int i = 2, j = 0; i <= m; i++)
	{
		while (j && t[i] != t[j + 1])
		{
			j = fail[j];
		}
		if (t[i] == t[j + 1])
			j++;
		fail[i] = j;
	}
}
void kmp()
{
	for (int i = 1, j = 0; i <= n; i++)
	{
		sta[++top] = s[i];
		while (j && sta[top] != t[j + 1])
		{
			j = fail[j];
		}
		if (sta[top] == t[j + 1])
			j++;
		fail1[top] = j;
		if (j == m)
		{
			top -= m;
			j = fail1[top];
		}
	}
}
int main()
{
	scanf("%s", s + 1);
	scanf("%s", t + 1);
	n = strlen(s + 1);
	m = strlen(t + 1);
	init();
	kmp();
	for (int i = 1; i <= top; i++)
		printf("%c", sta[i]);
	return 0;
}

C LCS 问题

题目描述

思路

  • 对于前 \(60\%\) 的数据

因为序列长度最多为 \(5000\),所以可以暴力 LCS。考虑 dp,\(f_{i,j}\) 表示 \(a\) 数组前 \(i\) 个数和 \(b\) 数组前 \(i\) 个数的最长公共子序列。

如果 \(a_i=b_j\),那么 \(f_{i,j} = max(f_{i-1,j},f_{i,j-1},f_{i-1,j-1}+1)\)

然后 \(f_{i,j}=max(f_{i-1,j},f_{i,j-1})\)

时间复杂度为 \(O(n^2)\)

  • 满分做法

关键在于保证 \(1~n\)\(n\) 个数在 \(a,b\) 中分别出现 \(5\) 次。枚举 \(i\),同时维护 \(f_j\) 表示 \(a\) 数组前 \(i\) 个数和 \(b\) 数组前 \(j\) 个数的最长公共子序列。同时要求这个最长公共子序列在 \(b\) 中必须以 \(b_j\) 结尾。

一开始对于每个 \(x\),用二维数组存下数值 \(x\)\(b\) 中的所有位置。枚举 \(i\) 时,枚举数值 \(a_i\)\(b\) 中的所有位置 \(k\)。这时候所有的 \(f_j\) 都是 \(a\) 数组中前 \(i\) 个数和 \(b\) 数组中前 \(j\) 个数的最长公共子序列。

我们令 \(g_k = \max_{i=1}^{k-1} f_i+1\),那么 \(g_k\) 也就是 \(a\) 数组中前 \(i\) 个数和 \(b\) 数组中前 \(k\) 个数的最长公共子序列。

对于 \(b_j \ne a_i\)\(j\),显然 \(g_j=f_j\),也就是说 \(f_j\) 不用改动。

那么我们现在就要单点修改,求前缀最大值,用树状数组维护 \(f_j\) 的前缀最大值,时间复杂度 \(O(nlogn)\)

代码

点击查看代码
inline int lowbit(int x)
{
	return x&(-x);
}
inline void change(int x,int y)
{
	for(int i=x;i<=N;i+=lowbit(i))
		f[i]=max(f[i],y);
	return;
}
inline int query(int x)
{
	int res=0;
	for(int i=x;i;i-=lowbit(i))
		res=max(f[i],res);
	return res;
}
for(int i=1;i<=n5;i++)
{
	int x=read();
	c[x][++cnt[x]]=i;
}
for(int i=1;i<=n5;i++)b[i]=read();
for(int i=1;i<=n*5;i++)
	for(int j=5;j;j--)
	{
		int res=query(c[b[i]][j]-1);
		change(c[b[i]][j],res+1);


D 舞步

题目描述

Farmer John 的奶牛们正在炫耀她们的最新舞步!

最初,所有的 \(N\) 头奶牛(\(2≤N≤10^5\))站成一行,奶牛 \(i\) 排在其中第 \(i\) 位。舞步序列给定为 \(K\)\(1≤K≤2\times10^5\))个位置对 \((a_1,b_1),(a_2,b_2),…,(a_K,b_K)\)。在舞蹈的第 \(i=1…K\) 分钟,位置 \(a_i\)\(b_i\) 上的奶牛交换位置。同样的 \(K\) 次交换在第 \(K+1…2K\) 分钟发生,在第 \(2K+1…3K\) 分钟再次发生,以此类推,周期性地持续共 \(M\) 分钟(\(1≤M≤10^{18}\))。换言之,

  • 在第 \(1\) 分钟,位置 \(a_1\)\(b_1\) 上的奶牛交换位置。
  • 在第 \(2\) 分钟,位置 \(a_2\)\(b_2\) 上的奶牛交换位置。
  • ……
  • 在第 \(K\) 分钟,位置 \(a_K\)\(b_K\) 上的奶牛交换位置。
  • 在第 \(K+1\) 分钟,位置 \(a_1\)\(b_1\) 上的奶牛交换位置。
  • 在第 \(K+2\) 分钟,位置 \(a_2\)\(b_2\) 上的奶牛交换位置。
  • 以此类推……

对于每头奶牛,求她在队伍中会占据的不同的位置数量。

注意:本题每个测试点的时间限制为默认限制的两倍。

输入格式

输入的第一行包含 \(N\)\(K\)\(M\)。以下 \(K\) 行分别包含 \((a_1,b_1)…(a_K,b_K)\)\(1≤a_i<b_i≤N\))。

输出格式

输出 \(N\) 行,第 \(i\) 行包含奶牛 \(i\) 可以到达的不同的位置数量。

提示

\(7\) 分钟之后,各个位置上的奶牛为 \([3,4,5,2,1,6]\)

  • 奶牛 \(1\) 可以到达位置 \(\{1,2,3,4,5\}\)
  • 奶牛 \(2\) 可以到达位置 \(\{1,2,3,4\}\)
  • 奶牛 \(3\) 可以到达位置 \(\{1,2,3\}\)
  • 奶牛 \(4\) 可以到达位置 \(\{2,3,4\}\)
  • 奶牛 \(5\) 可以到达位置 \(\{3,4,5\}\)
  • 奶牛 \(6\) 从未移动,所以她没有离开过位置 \(6\)

测试点性质:

  • 测试点 1-5 满足 \(N≤100,K≤200\)
  • 测试点 6-10 满足 \(M=10^{18}\)
  • 测试点 11-20 没有额外限制。

我们先考虑弱化版,即将 周期性地持续共 M 分钟 改为 无限循环

弱化版

考虑经过 \(k\) 分钟后,每头牛会途径某些点到达另一个点,假如 \(A\)\(x\) 位置走到了 \(y\) 位置,\(B\)\(x\) 走到了 \(x\),那么再经过一轮,\(B\) 也会走到 \(y\) 位置。

也就是说 \(B\) 的运动轨迹会和 \(A\) 一样,只不过落后 \(k\) 分钟罢了。同时 \(B\) 经过的点也和 \(A\) 一样,所以像 \(A,B\) 一样的有相同运动轨迹的牛,它们的运动轨迹将会在 \(k\) 分钟之内构成一个环。

而环可以用并查集维护。接下来用 vector 记录每个点经过的点,对于一个环内的牛,用 set 统计每个点经过了多少个点,设为 \(x\),则环内每一个牛能经过的点数也就是 \(x\)

时间复杂度 \(O(n+k)\)

普通版

也就是多了个时间限制。

\(times = \left \lfloor \dfrac{m}{k} \right \rfloor\) 表示一个点开始走会完整走 \(t\) 轮,\(w = m-kt\) 表示走完 \(t\) 轮还需要走 \(w\) 步。

手动模拟一下,发现类似滑动窗口,每次暴力加入 \(w\) 步,计算答案,再删除当前点,把加入 \(w\) 步的点剩下的都加进去,时间复杂度为 \(O(n+k)\)

注意 \(times=0\) 的情况和特判环大小 \(\le times\) 的情况。

(补)

Day 2

A 江桥的均衡区间

题目描述

解法

  • Subtask 1 & Subtask 2

枚举 \(i\),然后往后扩展,每次遇到一个数把他扔进去

  • Subtask 3

枚举区间的起点终点,左右端点为 \(j,i(j<i)\)

\(p1_i-p1_{j-1}=p2_i-p2_{j-1}\),移项,\(p1_i-p2_i=p1_{j-1}-p2_{j-1}\)

代码

posted @ 2024-08-05 19:01  OoXiao_QioO  阅读(5)  评论(0编辑  收藏  举报