省选模拟测试22

期望得分:\(60+100+0 +30 = 160\)

实际得分:\(0+100+0+30=130\)

\(T1\) 打了个区间dp的部分分,但数组开的 \(5000*5000\) 炸空间了。正解好像又被暴力碾过去了。

\(T2\) 考试的时候口胡了个点分树的做法,拍了 \(1000\) 多组数据就过了。

\(T3\) 题面太复杂了,没时间看了。

\(T4\) 推出来了和题解一样的柿子,但只会 \(O(n^2)\) 的暴力展开的做法,想了半天没想出来怎么 \(O(nlogn)\) 算。

T1 基因合成

题意描述

有一个机器可以从一个空串开始,每次进行一下两种操作:

  • 在串的头部或尾部插入一个字符。
  • 将整个串复制一遍,在反序接到原来串的后面。

现在给你一个字符串(只包含 \(ATCG\) ),问你最少让机器人操作多少次可以得到这个字符串。

数据范围:\(T\leq 10,|s|\leq 100000\)

solution

manacher暴力/回文自动机。

正解好像是回文自动机,但我不太会。

这里就提供一个暴力的做法(好像也能过)。

就是枚举整个字符串中的所有回文串,然后递归求解即可。

可以先用 \(manacher\) 先预处理出所有的回文半径,加个剪枝就可以莽过去了。

不知道问什么,同机房大佬的代码跑 \(1s\) 就过了,而我的要开 \(5s+\) 才能过。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 5e5+10;
int T,n,cnt,zx,ans,R[N],r[N];
char s[N],a[N];
void manacher()
{
	int p = 1, mx = 1, len = 2*n+1;
	for(int i = 1; i <= len; i++)
	{
		r[i] = i < mx ? min(mx-i,r[2*p-i]) : 1;
		while(a[i-r[i]] == a[i+r[i]]) r[i]++;
		if(i + r[i] > mx) 
		{
			mx = i + r[i];
			p = i;
		}
	}
}
int slove(int l,int r)
{
    if(l > r) return 0;
    if(l == r) return 1;
    int res = r-l+1, R_[N];
    for(int i = l; i <= r; i++) R_[i] = min(R[i],min(i-l+1,r-i));
    for(int i = l; i <= r; i++)
    {
    	if(r-l+1-2*R_[i] > res) continue;
    	res = min(res,(r-l+1)-2*R_[i]+slove(i-R_[i]+1,i)+(R_[i] > 0));
	}
    return res;
}
int main()
{
    freopen("dna.in","r",stdin);
    freopen("dna.out","w",stdout);
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s",s+1); n = strlen(s+1); ans = n+1;
	a[0] = '$'; a[1] = '#';
	for(int i = 1; i <= n; i++)
	{
		a[2*i] = s[i];
		a[2*i+1] = '#';
	}
	manacher();
	for(int i = 1; i <= 2*n+1; i += 2) R[(i-1)/2] = (r[i]-1)/2;
        printf("%d\n",slove(1,n));
    }
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 染色

题意描述

给你一棵 \(n\) 个节点的数,树的节点从 \(0\) 开始,每个节点可以使白色或者黑色。初始是每个节点的颜色为白色,要求维护下面两种操作:

  • 将节点 \(x\) 染黑。
  • 查询节点 \(x\) 到所有黑点的距离之和。

数据范围: \(n\leq 10^5,m\leq 10^5,0\leq d_i\leq 10^6\)

solution

点分树/线段树。

首先先建出整棵树的点分树,然后由于点分树的高度是 \(\log n\) 的,每次查询和修改直接暴力跳父亲即可。

对点分树上每个点维护一个 \(num[i],w[i]\) 分别表示点分树上 \(i\) 的子树中黑点的个数,以及 \(i\) 的子树中黑点到 \(i\) 的距离之和。

每次修改维护一下这两个数组,查询的时候容斥一下就可以得到答案。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e5+10;
int n,m,tot,maxn,root,opt,x;
int F[N],a[N],dis[N],head[N],sum[N],num[N],w[N],siz[N],max_siz[N],fa[N];
bool vis[N],col[N];
struct node
{
    int to,net,w;
}e[N<<1];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
void add(int x,int y,int w)
{
    e[++tot].to = y;
    e[tot].w = w;
    e[tot].net = head[x];
    head[x] = tot;
}
void get_root(int x,int fa)
{
    siz[x] = 1;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa || vis[to]) continue;
        dis[to] = dis[x] + e[i].w;
        get_root(to,x);
        siz[x] += siz[to];
        max_siz[x] = max(max_siz[x],siz[to]);
    }
    max_siz[x] = max(max_siz[x],maxn-siz[x]);
    if(max_siz[x] < max_siz[root]) root = x;
}
void slove(int x)
{
    vis[x] = 1;
    for(int i = head[x]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(vis[to]) continue;
        maxn = siz[to]; max_siz[0] = n; root = 0; dis[to] = 0;
        get_root(to,x); fa[root] = x; w[root] = dis[root] + e[i].w; slove(root);
    }
}
void modify(int x)
{
    if(col[x]) return;
    int res = 0; col[x] = 1;
    for(; x; x = fa[x])
    {
        sum[x] += res;
        num[x]++;
        res += w[x];
    }
}
int query(int x)
{
    int res = 0, last = 0, tmp = 0;
    for(; x; x = fa[x])
    {
        res += sum[x]-sum[last]-w[last]*num[last] + tmp * (num[x]-num[last]);
        tmp += w[x];
        last = x;
    }
    return res;
}
signed main()
{
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
    n = read(); m = read();
    for(int i = 2; i <= n; i++) F[i] = read() + 1;
    for(int i = 2; i <= n; i++) a[i] = read();
    for(int i = 2; i <= n; i++) add(F[i],i,a[i]), add(i,F[i],a[i]);
    maxn = max_siz[0] = n; root = 0;
    get_root(1,0); slove(root);
    for(int i = 1; i <= m; i++)
    {
        opt = read(); x = read() + 1;
        if(opt == 1) modify(x);
        else printf("%lld\n",query(x));
    }
    fclose(stdin); fclose(stdout);
    return 0;
}

T3 圈地游戏

题意描述

给定一个 \(n × m\) 的棋盘,有障碍、陷阱和宝藏,每个宝藏有权值
要求用折线围出一个多边形,其内部不含有陷阱,最终获得权值为围住的宝藏权值之和减去多边形周长
陷阱和宝藏数量之和不超过 \(8\)

数据范围:\(n,m\leq 20\)

solution

动态规划。

看了半天题解,没看懂 \(dp\) 顺序是怎么来的,所以直接搬题解就好了

题目中已经告诉我们怎么判断一个格子是否在多边形内部了,注意这个判断条件只跟奇偶性有关。

可以以此作为状态进行状态压缩动态规划,不妨给每个格子选一条射线,在转移的过程中更新有宝藏和陷阱的格子那条射线穿过边的奇偶性状态。

\(f[i][j][s]\) 表示当前在 \((i,j)\) 格子,宝藏和陷阱的射线奇偶性状态为 \(S\) 时,多边形周长最小为多少。

但是转移的顺序不好确定,不过因为我们是要周长尽可能小,而且转移一次只会让多边形周长增大1,可以BFS顺序转移,枚举每个宝藏是否被圈入多边形就可以求出答案了。

时间复杂度:\(O(2^nn^2)\)

T4 打怪兽

题意描述

你在玩一款打怪兽的小游戏。

怪兽有 \(m\) 点血,你会攻击怪兽 \(n\) 次,第 \(i\) 次攻击有 \(p_i\) 的概率是怪兽的血量减少 \(1\)

怪兽的血量如果变为 \(0\), 就不会在减少了。

求在每次攻击后,怪兽血量的期望,对 \(998244353\) 取模。

数据范围:\(1\leq n,m\leq 10^5,0\leq a_i,b_i\leq 998244352\)

solution

期望+分治FFT。

\(f_i(x)\) 表示第 \(i\) 次攻击之后,怪兽的减少的血量的生成函数。

显然有: \(f_i(x) = \displaystyle\sum_{j=1}^{i} (p_ix+(1-p_i))\)

则有每次攻击的答案为 \(\displaystyle\sum_{i=0}^{n} [x^i] f_i(x) \times (n-i)\)

\(B(x) = \displaystyle\sum_{i=0}^{n} (n-i)x^i\)

那么答案可以表示为 \(\displaystyle\sum_{i=0}^{n} [x^i]f_i(x)\times [x^i]B(x)\)

考虑用分治来解决这个问题。

  • 递归左边的时候:\(B'(x) = B(x)\)
  • 递归右边的时候,\(B'(x)\)\(B(x)\) 减法卷积 \(\displaystyle \sum_{j=l}^{mid} (p_jx+(1-p_j))\) ,分治 \(FFT\) 预处理一下即可。

注意:\(B(x)\) 只需要保留前 \(r-l+1\) 项的系数。

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

//咕咕咕咕
posted @ 2021-03-31 20:02  genshy  阅读(95)  评论(0编辑  收藏  举报