2021 EC-final
这场vp前期50分钟把签到一签完,后面四个小时一直在de打牌的bug,蒟蒻哭哭
A. DFS Order
题意:给你一个dfs的码,然后问你这棵树上每个节点的dfs序最大和最小是多少
题解:dfs维护每个节点的深度和size,深度也就是最小的dfs序,n-size+1就是最大的dfs序
B. Beautiful String
题意:一个beautiful字符串的定义是将他拆分成6个字符串,每个字符串都是非空的,\(s=s_1+s_2+s_3+s_4+s_5+s_6\),同时\(s_1=s_2=s_5\),\(s_3=s_6\),然后给你一个字符串,问他及他的子串中有多少个beautiful字符串。
题解:首先,我们看到\(s_1=s_2=s_5\),并且\(s_3=s_6\),就等价于找字符长度>=2的\(s_2^*=s_2+s_3\),\(s_3^*=s_5+s_6\)使得\(s_2^*=s_3^*\),同时满足\(s_1=s_2\)的条件。
然后我们可以拿一个\(lcp[i][j]\)维护以i为开头和以j为开头的最长公共前缀。维护的方法很简单,类似lcs,直接在\(s[i] == s[j]\)时\(lcp[i][j] = lcp[i + 1][j + 1] + 1\),这样我们字符串的两个位置,\(p,q\),就可以直接\(O(1)\)得到以这两个位置开头的字符串\(s_2^*\)和\(s_3^*\)的方案数。这里还得和将\(lcp[p][q]\)和\(q-p-1\)取个\(min\)得到了\(d\),防止相交,而这个\(min-1\)值就是我们的合法\(s_2^*\)和\(s_3^*\)的方案数。
接下来要处理在这个基础上,\(s1=s2\)的方案数。
我们另外存一个数组\(sqr[i][len]\)存的是以 \(i\) 为开头,长度为 \(len\) 的在与 \(i\) 相邻的前面出现的次数
然后sqr的\(len\)是\(\frac{n}{2}\),因为最多也就是两个字符串完全相等,此时长度也就是\(\frac{n}{2}\)
做法就是判断以 \(i\) 为前缀,长度为 \(lcp[i][j]\) ,以 \(j\) 为前缀,长度为 \(lcp[i][j]\) 的两个字符串能否相邻(如何判断他们能否相邻呢,就是当两个字符串已经相邻了,或者说已经相交了,就算相邻)
上面我们处理出来的那个\(s_2^*\)和\(s_3^*\)的方案数\(d\),其实就是拿一个隔板把一个字符串分成非空的两个字符串的方案数,我们要把每个分割情况下,都判断一下是否能存在\(s_1\)符合当前的情况。如果存在,就\(ans++\),显然这种写法会\(t\),我们不可能对每个\(d\)种的方案都重头判一下。我们所希望的是枚举之前的\(p\),\(q\)得到了方案数d,然后直接\(O(1)\)得到对应符合的\(s_1\)的个数。
但是这里我第一遍码只对\(sqr\)求了一遍前缀和,然后wa2,发现是这个原因
39991723655814713848046032694137883815354365954917
很明显答案是917构成我们的\(s_2^*\)和\(s_3^*\)两个字符串。
因为我上面\(sqr\)数组存的是以 \(i\) 为开头,长度为 \(len\) 的在与 \(i\) 相邻的前面出现的次数,对于我的第一次的\(sqr\)而言我的\(sqr[3][1]=1,sqr[3][2]=0,sqr[3][3]=0\),也就是说我们的\(sqr[3][1]\)是有贡献的,然后对于我们每一对p,q而言,只要两者的距离大于这个len,就能提供贡献。那么就相当于给我们的是个差分数组,然后我们要得到前缀和数组,那就要求两次前缀,因为只有我们加上前缀和了,才是既符合\(s_1=s_2\),又满足\(s_2^*=s_3^*\)的交集方案数。
因为网上没找到什么题解,就对着标程理解的码,如果误人子弟了求大佬指错orzorzorz
#include<iostream>
#include<cstring>
#include<stack>
#include<queue>
#include<stdio.h>
#include<string>
#include<string.h>
#include<map>
#include<cstdio>
#include<set>
#include<stdlib.h>
#include<algorithm>
#include <unordered_map>
#include <unordered_set>
#include <math.h>
#include<cmath>
#include<list>
#include<time.h>
#include<set>
#include<vector>
#include<iomanip>
#include<fstream>
#include<cstdlib>
#include<sstream>
#include <random>
//#include <chrono>
#include <functional>
#include <bits/stdc++.h>
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 2e6 + 10;
const double pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double eps2 = 1e-2;
const ll mod = 998244353;
const double gold = (sqrt(5.0) - 1) / 2;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define edl(i,n) (i==n?'\n':' ')
#define sci(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define scd(x) scanf("%lf",&x)
#define int long long
#define pii pair<int,int>
#define endl "\n"
int gcd(int a, int b);
int lcm(int a, int b);
int qpow(int a, int n);
int qmul(int a, int b);
//template <typename T>
//inline void read(T& X);
//template <typename T>
//inline void write(T& s);
//mt19937 rng((int)std::chrono::steady_clock::now().time_since_epoch().count());
const int N = 5010;
char s[N];
int lcp[N][N], sqr[N][N];
void resolve() {
cin >> s;
int n = strlen(s);
for (int i = n; i >= 0; i--) {
for (int j = i; j <= n; j++) {
if (j >= n) lcp[i][j] = 0;
else {
if (s[i] == s[j]) lcp[i][j] = lcp[i + 1][j + 1] + 1;//(i)--(i+lcp[i][j])和(j)--(j+lcp[i][j])的字符完全相同
else lcp[i][j] = 0;
}
}
}
//cout << "lcp" << endl;
//cout << "* ";
//for (int j = 0; j <= n; j++) {
// cout << " " << j << edl(j, n);
//}
//for (int i = 0; i <= n; i++) {
// cout << "*" << i << " ";
// for (int j = 0; j <= n; j++) {
// cout << " " << lcp[i][j] << " ";
// }
// cout << endl;
//}
int len = n / 2;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= len; j++) { sqr[i][j] = 0; }
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (lcp[i][j] >= j - i) sqr[j][j - i]++;
}
}
//判断以i为前缀,长度为lcp[i][j],以j为前缀,长度为lcp[i][j]的两个字符串能否相邻
//sqr[i][len]存的是以i为开头,长度为len的在与i相邻的前面出现的次数
//cout << "sqr" << endl;
//cout << "* ";
//for (int j = 0; j <= n; j++) {
// cout << " " << j << edl(j, n);
//}
//for (int i = 0; i <= n; i++) {
// cout << "*" << i << " ";
// for (int j = 0; j <= n; j++) {
// cout << " " << sqr[i][j] << " ";
// }
// cout << endl;
//}
for (int i = 0; i < n; i++) {
for (int j = 1; j <= len; j++) sqr[i][j] += sqr[i][j - 1];
for (int j = 1; j <= len; j++) sqr[i][j] += sqr[i][j - 1];
}
//cout << "sqr" << endl;
//cout << "* ";
//for (int j = 0; j <= n; j++) {
// cout << " " << j << edl(j, n);
//}
//for (int i = 0; i <= n; i++) {
// cout << "*" << i << " ";
// for (int j = 0; j <= n; j++) {
// cout << " " << sqr[i][j] << " ";
// }
// cout << endl;
//}
//3999178236558147138480460326941378838153543659549178
ll ans = 0;
for (int p = 0; p < n; p++) {
for (int q = p; q <= n; q++) {
int d = min(lcp[p][q], q - p - 1); --d;
if (d >= 0) {
ans += sqr[p][d];
//cout << "p: " << p << " q: " << q << " ans: " << ans << endl;
}
}
}
cout << ans << endl;
}
signed main() {
fast;
int _ = 1;
//freopen("input.in", "r", stdin);
//freopen("output.out", "w", stdout);
cin >> _;
//scl(_);
//srand (time(NULL));
while (_--) {
resolve();
}
}
//template <typename T>
//inline void read(T& X) {
// X = 0; int w = 0; char ch = 0;
// while (!isdigit(ch)) { w |= ch == '-'; ch = getchar(); }
// while (isdigit(ch)) X = (X << 3) + (X << 1) + (ch ^ 48), ch = getchar();
// if (w) X = -X;
//}
//template <typename T>
//inline void write(T &s) {
// T w = 0, c[15];
// if (s < 0) putchar('-'), s = -s;
// while (s) c[++w] = (s % 10) + 48, s /= 10;
// while (w) putchar(c[w--]);
//}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
int qpow(int a, int n) {
int ans = 1;
while (n) {
if (n & 1)
ans = ans * a % mod;
a = a * a % mod;
n >>= 1;
}
return ans;
}
int qmul(int a, int b) {
ll ans = 0;
while (b > 0) {
if (b & 1) ans = (ans + a) % mod;
a = (a + a) % mod;
b >>= 1;
}
return ans;
}
C. String-dle Count
待补
D. Two Walls
待补
E. Prof. Pang and Poker
题意:Alice和Bob还有P三个人在打牌,Alice先出牌,然后是Bob,然后是P,一直这样轮着来。但是P只有一张牌了。出的牌必须比当前回合内出的牌都要大,才能出牌,否则就只能空过。一个人空过两次后,当前回合结束,由上一个出牌的人重新出牌。Alice想要P赢,Bob想要P不赢,给你Alice,Bob和P的牌,问最后Alice和Bob谁赢。
题解:分类讨论的题最切记的是什么情况都要枚举,导致什么情况都漏了。所以只需要枚举赢的情况,其他的情况都是输的了。(当然,几个前置条件先判一下)
1.Alice就1张牌——Shou
2.Alice最小的牌也比P最大的牌大——Shou
3.Bob有两张及以上比P小的牌——Pang
4.Bob最大的牌比P的牌还要小,同时Alice有一张比Bob的牌大,比P的牌小的牌——Pang
5.Bob只有一张比P小,Alice有一张比Bob的牌大,有一张比P的牌小,还有一张在minB<=now<P这样的牌。——Show(我们就是漏判了这个,因为此时最佳策略下,Alice打完了,也算是P没赢)
6.Bob只有一张比P小,Alice有一张比Bob的牌大,有一张比P的牌小,还有一张在minB<=now<P这样的牌,同时还有一张牌以上——Pang
7.其他情况——Shou
F. Vacation
待补
G. Check Pattern is Bad
待补
H. Check Pattern is Good
待补
I. Future Coder
题意:给你一个数组,问你有多少对\(<i,j>\)使得\(a[i]*a[j]<a[i]+a[j]\)(\(i<j\))
题解:
移项-> \(a[i]*a[j]-a[i]<a[j]\)
合并同类项->\(a[i]*(a[j]-1)<a[j]\)
分类判\(a[j]-1\)大于0,小于0,等于0
如果\(a[j]>1\),\(a[i]<\frac{a[j]}{a[j]-1}\)
如果\(a[j]==1\) 永远成立
如果\(a[j]<1\),\(a[i]>\frac{a[j]}{a[j]-1}\)
然后我们发现\(\frac{a[j]}{a[j]-1}\)对于每个给定的\(j\)而言都是固定的,我们对原数组\(a[i]\)sort一下,然后维护一个数组\(b[i]=\frac{a[j]}{a[j]-1}\),对每个b[j]二分找临界的\(a[i]\)即可
J. Elden Ring
题意:给你一个无向边构成的图(可以有环),你在1点,终极BOSS在n号点,其他的点上都是小boss,然后会给你每个点的经验值,如果是1号点就是自身经验,否则是boss的经验。然后你每天可以攻击一个与你占领点相邻的boss,初始占领点只有1号点,然后你如果战胜了i号点,那么这个点也就成为了你的占领点。如果你的经验严格大于这个boss,你就可以战胜他并获得A的经验,每天开始的时候所有boss都会获得B经验,问你多少天可以消灭终极BOSS,如果永远不可以,输出-1
题解:首先因为勇士和BOSS的增量是不同的,我们可以分类讨论一下A和B的情况。
1.\(A==B\),也是最明显的情况,直接跑最短路判断能不能走到n点,如果可以就输出\(dis[n]\),否则输出\(-1\),这里讲的粗显一点,因为下面分析完另外两种情况,这个也就不攻自破了
2.\(A<B\)。因为A<B了,所以我们要越快达到\(n\)越好,如果你第\(i\)天打不过BOSS,那么你第\(i+1\)天也一定打不过BOSS。这里我们存一个\(c[i]\),就是用来维护每个BOSS点最迟能够什么时候干掉。如果你一开始就干不过,就赋为-1。对于每个节点而言,我们能够打赢的时间区间就是在\([1,c[i]]\)这段时间内。在这里所有占领点扔进优先队列中,优先队列就是正常的最短路的优先队列,存放dis和id,dis在这里的意义是你要多少天才能打赢该boss。如果\(c[i]=-1\),或者是当前判到的点\(x\)和后继节点\(y\),他的\(dis[x]>c[y]\)我们直接不用管,因为更新了也没有价值。然后\(dis[x]<dis[y]\)的情况下正常更新。其实就是在正常的对天数跑最短路,附加了一个\([l,r]\)区间内的条件,只有在\([l,r]\)内才能更新最短路。
3.\(A>B\),这种情况下其实我感觉比较绕,因为可以有选择性了,因为会把一些其他的boss当做军备去打。然后这种情况可能可以一个dij搞定?反正我补题的时候理得有点乱,我先去用一个dij判能不能走到n点,再去拿另一个dij判需要多少步走到n点。跟\(A<B\)的时候一样,我们可以先存一个\(c[i]\)数组,用来存放i点的boss最早可以第几天打完,那么此时的区间就是\([c[i],\infin]\)。判断的时候我们把占领点扔进优先队列中,然后如果你当前的dis<我的军备量,那么意味着我永远打不过你,但是你又是我当前队列中的最小dis,所以之后都不用打了,直接输出-1。否则,将你未在队列中的后继点都扔进来。这样,就实现了第一个目的——能不能。然后我们再跑一个dij,这里就和上一个\(A<B\)大差不差了,我们这里判的是\(max(dis[x],c[y])+1<dis[y]\),如果可以就更新。
无论什么情况,输出dis[n]就好了。
K. Vision Test
待补
L. Fenwick Tree
题意:给定一个初始全是0的字符串,和一个update函数,然后输入是想要变成的字符串,(0表示为0,1表示非0),求最少需要多少步才能变成那样。
题解:观察update函数,对于一个位置而言,我们需要找他会从那几个位置跳到当前位置。
例如对于8(1000)而言,我们最多可以通过4(100)6(110)7(111)到这个点。表现形式就是找到最后一个1,将其变0,然后按顺序依次将后面的0变成1,即原数减去\(2^i\)次,\(i\)是那几个位置
然后我们观察发现,如果你当前的位置的期望状态是1,而你前一个状态全是0,那么就需要一步操作。
而如果你当前位置的期望状态是0,而你的前一个状态只有一个是1,那么你需要一步操作来抵消这个1.
如果你前一个状态有多个1,我们总会有一种策略使得当前点是0或是1
M. Prof. Pang and Ants
待补

浙公网安备 33010602011771号