省选模拟测试7

期望得分: \(100+70+30 = 200\)

实际得分:\(70+0+0 = 70\)

\(md\) 今天挂了好多分,难受。

\(T1\) 打了个 \(set\) 的假做法,常数巨大,被卡成了 \(70\) 分。

\(T2\)\(tm\) \(Hash\) 是关键字,\(70\) 分直接被干没了。

\(T3\) \(ynoi\) 的题,打了 \(30\) 分的暴力,但没开 \(\ \ unsigned \ long \ long\) 直接炸了。

T1​ 发微博

题意描述

刚开通的 SH 微博共有 \(n\) 个用户(\(1\sim n\) 标号),在这短短一个月的时间内,用户们活动频繁,共有 \(m\) 条按时间顺序的记录:

! x 表示用户 x 发了一条微博;
+ x y 表示用户 x 和用户 y 成为了好友
− x y 表示用户 x 和用户 y 解除了好友关系

当一个用户发微博的时候,所有他的好友(直接关系)都会看到他的消息。

假设最开始所有人之间都不是好友关系,记录也都是合法的(即 + x y\(x\)\(y\) 一定不是好友,而 − x y\(x\)\(y\) 一定是好友)。

问这 \(m\) 条记录发生之后,每个用户分别看到了多少条消息

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

solution

洛谷原题

可以直接拿 \(set\) 正着做,下面有一个比较好写的做法。

考虑如果一条关系 \(x,y\) 出现的时间为 \(l,r\) 那么 \(x\) 答案就要累加上 \(y\) 在这一段时间内发布的微博数。

正着维护前缀和的话,空间开不下。

考虑倒着做,维护一个 \(sum[x]\) 数组,表示从 \(i-n\) 这一段时间内 \(x\) 发送的微博数。

如果 \(x,y\) 在这一刻成为了好友,就让 ans[x] += sum[y],ans[y]+= sum[x]

反之解除好友就把 ans[x]-=sum[y],ans[y]-=sum[x]

然后这道题就做完了。复杂度 \(O(n)\)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
#define mp make_pair
const int N = 5e5+10;
int n,m,x,y,tot,w[N],sum[N];
struct node
{
	int opt,x,y;
}q[N];
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;
}
int main()
{
    n = read(); m = read();
    for(int i = 1; i <= m; i++)
    {
        char ch; cin>>ch;
        if(ch == '!')
        {
            q[i].x = read();
            q[i].opt = 1;
        }
        if(ch == '+')
        {
        	q[i].x = read();
        	q[i].y = read();
        	q[i].opt = 2;
        }
        if(ch == '-')
        {
        	q[i].x = read();
        	q[i].y = read();
        	q[i].opt = 3;
        }
    }
    for(int i = m; i >= 1; i--)
    {
    	if(q[i].opt == 1) sum[q[i].x]++;
    	else if(q[i].opt == 2) w[q[i].x] += sum[q[i].y], w[q[i].y] += sum[q[i].x];
    	else if(q[i].opt == 3) w[q[i].x] -= sum[q[i].y], w[q[i].y] -= sum[q[i].x];
	}
    for(int i = 1; i <= n; i++) printf("%d ",w[i]);
    printf("\n");
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 字符串

题意描述

\(ysy\) 不想让家长看自己的聊天记录,所以 \(ysy\) 就想出了一套密码以及一种加密方式。

​ 加密方式:现在给出一段英文,我们把每一个单词翻转,并将其所有的大写字母都变为小写字母,最后在把这些单词收尾相接得到一个字符串,这样我们就加密完了。举个例子 \(ab \ Aes \ Ksd\):加密后变为 \(baseadsk\)

​ 坐在电脑屏幕另一侧的你对于 \(ysy\) 这样的行为十分恼怒,因为看不懂,但是好在 \(ysy\) 给你了他所有可能说的单词,现在你需要运用编程能力将 \(ysy\) 说的话解密。

数据范围:字符串长度 \(n\leq 10000\), 单词个数 \(m\leq 5000\), 所有单词长度 \(len\leq 1000\)

solution

首先有 \(70\) 分的 \(O(n^2)\) 做法,就是设 \(f[i]\) 表示前 \(i\) 个字符能否拼接成。

转移时枚举每个单词,如果 \(s(j+1,i)\) 和单词相同,则 \(f[i] |= f[j]\)

同时记录一下决策点,来输出方案。

判断两个字符串是否相同,用 \(Hash\) 来判断即可。

其实我们是没必要枚举每个单词的,因为单词的长度是小于 \(1000\) 的,所以我们只需要枚举 \(s(1,i)\) 长度小于 \(1000\) 的后缀即可。

众所周知 map[x] 非常慢,所以建议使用 map.find(x)

复杂度: \(O(n1000logm)\)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
#define ull unsigned long long
const int N = 1e5+10;
const int base = 23333;
const int p = 998244353;
int n,m,cnt;
int pos[N],f[N],pre[N],len[N];
ull has[N];
char s[N],b[100010][1010];
map<ull,int> id;
int main()
{
	freopen("char.in","r",stdin);
	freopen("char.out","w",stdout);
    scanf("%d",&n); scanf("%s",s+1);
    scanf("%d",&m); 
    for(int i = 1; i <= m; i++)
    {
        scanf("%s",b[i]+1);
        int k = strlen(b[i]+1);
	ull tmp = 0;
        for(int j = k; j >= 1; j--) 
	{
		if(b[i][j] < 'a') tmp = tmp * base + b[i][j] - 'A' + 'a';
		else tmp = tmp * base + b[i][j];
	}
        if(id.find(tmp) == id.end()) id[tmp] = i; 
	len[i] = k;
    }
    f[0] = 1;
    for(int i = 1; i <= n; i++)
    {
    	ull tmp = 0, mi = 1;
        for(int j = i; j >= 1 && i-j+1 <= 1000; j--)
        {
            tmp = tmp + s[j] * mi; mi = mi * base;
            if(f[i]) break;
            if(id.find(tmp) != id.end() && f[j-1] == 1)
            {
                f[i] = 1;
                pre[i] = id[tmp];
                break;
            }
        }
    }
    while(n)
    {
        pos[++cnt] = pre[n];
        n = n-len[pre[n]];
    }
    for(int i = cnt; i >= 1; i--) printf("%s ",b[pos[i]]+1);
    printf("\n");
    fclose(stdin); fclose(stdout);
    return 0;
}

T3 序列/由乃的OJ

题意描述

给你一个有 \(n\)个点的树,每个点的包括一个位运算 \(opt\) 和一个权值 \(x\),位运算有 &|^ 三种,分别用 $1,2,3$1, 表示。

每次询问包含三个整数 \(x,y,z\),初始选定一个数 \(v\)。然后 \(v\) 依次经过从 \(x\)\(y\) 的所有节点,每经过一个点 \(i\) \(v\) 就变成 \(v\ opt_i\ x_i\) ,所以他想问你,最后到 \(y\) 时,希望得到的值尽可能大,求最大值。给定的初始值 \(v\) 必须是在 \([0,z]\)[之间。

每次修改包含三个整数 \(x,y,z\) ,意思是把 \(x\) 点的操作修改为 \(y\),数值改为 \(z\)

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

solution

这个其实是起床困难综合征的树上版本。

不难想到用线段树来维护。

考虑对线段树上的每一个区间维护 \(4\) 个值 \(l_0,l_1,r_0,r_1\) 分别表示,每一位全为 \(0 /1\) 的数,从左/右依次进行运算得到的结果。

咕咕咕。

posted @ 2021-03-08 20:49  genshy  阅读(53)  评论(0编辑  收藏  举报