NOI.ac2020省选模拟赛9

比赛链接

A.SAM2

problem

你需要构造一个自动机,使得这个自动机可以接受S的每个后缀(只需要接受S的后缀即可,别的不做要求)。并且让状态数尽量少。如果状态数为\(n\),转移数为\(m\),那么需要满足\(m\le 2n\)

\(|S|\le 10^5\)

solution

首先状态数最少是\(n+1\)

考虑\(SAM\)的构造过程,再给新节点找\(parent\)的时候,如果找到的\(len[q]\)\(len[np]+1\)要大,那么就新建了一个\(nq\)。因为这个题只要求接受所有的后缀即可。所以没有必要新建\(nq\)了,直接让\(q\)作为\(p\)\(parent\)即可。

code

/*
* @Author: wxyww
* @Date:   2020-06-09 11:44:47
* @Last Modified time: 2020-06-09 21:20:21
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 100010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
char s[N];
int tot = 1,tran[N][30],fa[N],ans;
void ins(int x) {
	int p = tot,np = ++tot;
	for(;p && !tran[p][x];p = fa[p]) tran[p][x] = np,++ans;
	if(!p) fa[np] = 1;
	else fa[np] = tran[p][x];
}
int main() {
	scanf("%s",s + 1);
	int n = strlen(s + 1);
	for(int i = 1;i <= n;++i)
		ins(s[i] - 'a');

	printf("%d %d\n",n + 1,ans);

	for(int i = 1;i <= n + 1;++i) {
		for(int j = 0;j < 26;++j) {
			if(tran[i][j]) {
				printf("%d %d %c\n",i,tran[i][j],j + 'a');
			}
		}
	}
	return 0;
}

B.ZYB的赈灾计划

problem

给出一个长度为\(n\)的正整数序列,将其分为两个新序列,新序列中元素的相对位置不变,每个位置的贡献是该位置前缀最大值。找一种划分方法使得所有位置的贡献和最小。

\(n\le 50000\)

solution

写一种\(n^2\)的做法吧,\(n\sqrt{n}\)太恶心了。。。

分成的两个序列a和b中肯定有一个中的最大值一直是原序列中的最大值。不妨设这个序列为b。那么我们只需要记录a中的最大值即可。

\(f_i\)表示a中的最大值为i时的答案。

对于一个新的元素x。共有三种安排方案。

  • 将x放到b序列中,只有当x比a中的最大值大时,我们才会这么做。所以就让所有的\(f_i(i\le x)+=mx_i\)。(\(mx_i\)表示原序列中前i个位置的最大值。
  • 将x放到a序列中并且x作为最大值,这时就是要让\(f_x+=x+min\{f_j\}(j<x)\)
  • 将x放到a序列中并且不作为最大值,这时就是要让\(f_i+=i(i>x)\)

\(n^2dp\)即可。

code

/*
* @Author: wxyww
* @Date:   2020-06-09 08:20:58
* @Last Modified time: 2020-06-09 10:10:31
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;

ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
int n;
namespace BF1 {
	const int N = 50010;
	int mx[N],a[N];
	ll f[N];
	void main() {
		
		for(int i = 1;i <= n;++i) {
			a[i] = read();mx[i] = max(mx[i - 1],a[i]);
			ll mn = 1e15;
			for(int j = 0;j < i;++j) {
				if(a[i] > a[j]) {
					mn = min(mn,f[j]);
					f[j] += mx[i];
				}
				else
					f[j] += a[j];
			}
			f[i] = a[i] + mn;
		}

		ll ans = 1e15;
		for(int i = 1;i <= n;++i)
			ans = min(ans,f[i]);

		for(int i = 1;i <= n;++i) ans -= a[i];

		cout<<ans<<endl;
	}
}
int main() {
	n = read();
	// if(n <= 5000) {
		BF1::main();
		// return 0;
	// }
	return 0;
}

C.重复

problem

如果一个字符串的子序列有\(k\)个,其中第\(i\)个出现次数是\(f_i\),那么这个字符串的重复度就是\(\sum\limits_{i=1}^k f_i^2\)

给出字符串\(S\),求它的重复度。答案对\(M\)取模

\(|S|\le 10000,M\le 10^9\)

solution

取模不及时,直接\(100\rightarrow 20\)(宛若智障)

看到那个平方可以想到一个经典的套路就是转化成求相同的子序列有多少对。

\(f_{ij}\)表示第一个子序列到了i位置,第二个子序列到了j位置,且分别以\(i\)\(j\)结尾的相同子序列对数。

如果\(S_i\neq S_j\),那么就有\(f_{ij}=0\)
如果\(S_i\neq S_j\),就有\(f_{ij}=\sum\limits_{k=0}^{i-1}\sum\limits_{t=0}^{j-1}f_{kt}\)

发现其实是二维前缀和的过程,复杂度\(O(n^2)\)

code

/*
* @Author: wxyww
* @Date:   2020-06-09 08:57:01
* @Last Modified time: 2020-06-09 21:44:09
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 10010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
int n,mod;
char s[N];
namespace BF1 {
	const int N = 10003;
	int sum[N][N];
	int upd(int x,int y) {
		x += y;
		x >= mod ? x -= mod : 0;
		x < 0 ? x += mod : 0;
		return x;
	}
	void main() {
		sum[0][0] = 1;

		for(int i = 1;i <= n;++i) sum[0][i] = sum[i][0] = 1;

		for(int i = 1;i <= n;++i) {
			for(int j = 1;j <= n;++j) {
				sum[i][j] = upd(upd(sum[i - 1][j],sum[i][j - 1]),-sum[i - 1][j - 1]);
				if(s[i] == s[j]) 	{
					sum[i][j] += sum[i - 1][j - 1];
					sum[i][j] >= mod ? sum[i][j] -= mod : 0;
				}
			}
		}

		cout<<(sum[n][n] + mod) % mod<<endl;
	}
}
int main() {
	scanf("%s",s + 1);
	mod = read();
	n = strlen(s + 1);
	BF1::main();
	return 0;
}
posted @ 2020-06-09 21:58  wxyww  阅读(14)  评论(0编辑  收藏  举报