Tsawke 的十月模拟赛 题解
Tsawke 的十月模拟赛 题解
T1 这是一道比原来的T1更像T1的妙妙性质题
原题是 LG-P5497 [LnOI2019SP]龟速单项式变换(SMT),原题范围 $ 10^{18} $,我感觉没意思就加强到了 $ 10{106} $,不用谢。
首先有个结论:$ n \ge m $ 则成立,反之不成立,所以原题直接读入 $ n, m $ 然后 n >= m ? "Yes" : "No"
即可,对于本题也就是用字符串读入,先判断一下长度再按位判断就行。
然后考虑证明这个结论,我们假装对这个序列做一个前缀和,令 $ s_i $ 表示 $ \sum_{j = 1}^{i} a_j $,显然若 $ \exists i, j $ 满足 $ m \mid s_i - s_j $,那么则合法。
我们考虑对于每一个 $ s_i \(,\) s_i \bmod{m} $ 共有 $ m - 1 $ 种取值(取值显然不能为 $ 0 $),若 $ s_i $ 的数量,或者说 $ n $,满足 $ n \ge m $,那么一定会至少有一对 $ s_i \equiv s_j \pmod{m} $,这个东西俗称鸽巢原理,应该很显然吧?然后有了这个式子,那么 $ s_i - s_j \equiv 0 \bmod{m} $ 也就很显然了,所以 $ n \ge m $ 显然有解,至于构造的话任选 $ n $ 个数就是了。
对于 $ n \lt m $ 也很显然,一定可以构造出一个前缀和的序列满足每个 $ s_i \bmod{m} $ 都不同,这样也就无法找到满足条件的一对 $ i, j $ 了。
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template<typename T = int>
inline T read(void);
string N, M;
#define EXIT(f) printf("%s\n", f ? "NO" : "YES"), exit(0)
// #define EXIT(f) printf("%s\n", f ? "YES" : "NO"), exit(0)
int main(){
freopen("intriguing_t1.in", "r", stdin);
freopen("intriguing_t1.out", "w", stdout);
cin >> N >> M;
if(N.length() > M.length())EXIT(true);
if(N.length() < M.length())EXIT(false);
for(int i = 1; i <= (int)N.length(); ++i){
if(N.at(i - 1) > M.at(i - 1))EXIT(true);
if(N.at(i - 1) < M.at(i - 1))EXIT(false);
}EXIT(true);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
T2 最喜欢推柿子了
原创题,没原题,需要的话或许我可以在 Luogu 做一道??已经加了
emm 就是按照给的那几个公式去推一下式子就行。
然后即可 $ O(1) $ 求解。
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
typedef __float128 f128;
template<typename T = int>
inline T read(void);
int main(){
freopen("tomato.in", "r", stdin);
freopen("tomato.out", "w", stdout);
ll N = read<ll>(), X = read<ll>();
f128 tmp1 = sinf128(((f128)N + (f128)0.5) * X) * sinf128((f128)X / (f128)2);
f128 tmp2 = (f128)1.0 - cosf128((f128)X);
f128 ans = (f128)0.5 + tmp1 / tmp2;
printf("%.6lf\n", (double)ans);
// f128 anss(0.0);
// for(ll i = 0; i <= N; ++i){
// anss += cosf128((f128)i * (f128)X);
// }
// printf("%.6lf\n", (double)anss);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
T3 划水?想屁吃!
原题 LG-P4220 [WC2018]通道。是的这是一道黑色的 WC。
虚树 + 点分治什么是虚树??我不会!
显然作为机房最弱的 tsawke 不可能会虚树分治,也不可能出虚树的题,所以这道题是爬山。
显然出题人想让我们用爬山算法解决。显然 Hack 数据也可以用奇怪的爬山通过。
(原时限 4000ms,因为是在虚拟机中测评,会慢不少,于是开了 6000ms)
考虑发现似乎这玩意是个多峰函数,于是我们可以尝试多点爬山,没必要用退火(我感觉退火应该也能过)。
考虑在时限范围内,每次随机选择一个为选择过的节点,作为起始点 $ S $,对于这个的找最优终点应该很显然吧,树形结构,随便搜一下,以 $ S $ 为根记录一下深度,然后跑一遍找三个图中的距离加和最大的,此时的结果便是当前最优解,以其更新 $ ans $。然后这里人类智慧就来了,考虑我们找直径的时候不是两次 dfs 求解吗,我们也可以这么去考虑,把这次的终点 $ T $ 作为新的 $ S $ 然后再跑下一个终点,这个东西可以随便重复个几次,作为一次爬山,注意每次记录已经当过起点的点,下次不选这些。
然后再重新随机一个点再爬一次山,这就是多点爬山了。。。可以考虑加个卡时稳一点。然后这个东西只要你写的不是特别丑,Luogu 随便过,但是在 loj 上存在三个 Hack 数据,这样的爬山会被卡掉,当然毒瘤的 tsawke 也把这三个数据搬过来了,于是我们考虑一些新的人类智慧,在更新答案的时候像以前一样更新,然后在我们从当前的 $ S $ 找 $ T $ 作为新的 $ S $ 的时候,我们只考虑任意两棵树型结构,或者任意一棵树形结构的贡献去更新新的 $ S $。这个东西是什么原因呢,通过观察数据揣摩出 Hack 数据的人的思想,我们可以想到想要让我们这个多点爬山变假的办法,基本就只能是让不同树形结构之间差距特别大,但是正解又在一个不太符合权值最大的树的单调性的位置上,这样我们就很难通过爬山去找到那个奇怪的正解的位置,于是我们考虑用一个类似枚举子集的方法去消除掉某一两棵树的贡献,只用三棵树里的一部分去作为那个多峰函数找最优解。
然后考虑在实现中,先跑几次一般的爬山,以保证对于一般数据都能通过,可以设为 $ 10 $ 次。然后再去跑这几个人类智慧的爬山,中间如果时间快要超限了就直接 exit
,这样就可以也通过 loj 上的所有数据。
和出题人斗智斗勇了属于是。
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MAXN 110000
#define MAX_TIME (double)(5.2)
#define CURRENT_TIME ((double)clock() / CLOCKS_PER_SEC)
template<typename T = int>
inline T read(void);
struct Edge{
Edge* nxt;
int to;
ll val;
OPNEW;
}ed[MAXN * 6];
ROPNEW(ed);
int N;
Edge* head[4][MAXN];
ll dis[4][MAXN];
bool vis[4][MAXN];
bool used[MAXN];
ll ans(-1);
void dfs(int p, int ver, int fa = -1){
for(auto i = head[ver][p]; i; i = i->nxt){
if(SON == fa)continue;
dis[ver][SON] = dis[ver][p] + i->val;
dfs(SON, ver, p);
}
}
void Luangao(int f1, int f2, int f3){
int rt = rndd(1, N);
while(used[rt] && CURRENT_TIME < MAX_TIME)rt = rndd(1, N);
int T = 10;
while(T--){
if(used[rt])break;
used[rt] = true;
for(int i = 1; i <= 3; ++i)dis[i][rt] = 0, dfs(rt, i);
ll mx(0);
for(int i = 1; i <= N; ++i){
ans = max(dis[1][i] + dis[2][i] + dis[3][i], ans);
if(!used[i] && dis[1][i] * f1 + dis[2][i] * f2 + dis[3][i] * f3 > mx)
mx = dis[1][i] * f1 + dis[2][i] * f2 + dis[3][i] * f3, rt = i;
}
}
}
signed main(){
freopen("escape_from_llq.in", "r", stdin);
freopen("escape_from_llq.out", "w", stdout);
N = read();
for(int i = 1; i <= 3; ++i)
for(int j = 1; j <= N - 1; ++j){
int s = read(), t = read(); ll v = read<ll>();
head[i][s] = new Edge{head[i][s], t, v};
head[i][t] = new Edge{head[i][t], s, v};
}
for(int i = 1; i <= 10 && CURRENT_TIME < MAX_TIME; ++i)Luangao(1, 1, 1);
for(int i = 0; i <= 1; ++i)for(int j = 0; j <= 1; ++j)for(int k = 0; k <= 1; ++k)
if(CURRENT_TIME < MAX_TIME)Luangao(i, j, k);
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
T4 什么阴间三元环
一道新的原创题,没原题,一般的三元环计数你们都会吧??
就是把无向图的边定向,从度数大的指向度数小的,度数相同的按照编号偏序,然后从每个点枚举出点再枚举出点的出点,如果出点的出点也是起始点的出点的话那么就构成一个三元环,这样不重不漏且复杂度为 $ O(m \sqrt{m}) $。对于有向图三元环计数,当成无向图按照原来方法做然后判断一下原图是否存在这几条边就行,这样可以过一点部分分。
然后还有 $ m = 0 $ 的时候,这时候画一下就会发现无法构成任何三元环,于是 puts("0")
即可。
然后在每有一个 $ m $ 操作的时候显然一定编号小的点出点 $ -1 $,编号大的那个出点 $ +1 $,这玩意感觉是可以 $ O(1) $ 左右维护的吧可能,于是这是最后一个部分分。
然后考虑正解,显然对于三元环三个点,我们认为它们是一个有序三元组 $ (u, v, w) $,要求有 $ u \rightarrow u \rightarrow w $,那么竞赛图中一共有 $ n \choose 3 $ 个这样的三元组,这很显然。然后考虑令点 $ i $ 的出点的集合为 $ \mathbb{V}_i $,于是显然对于 $ u $,如果有 $ v, w \in \mathbb{V}u $,那么这一定无法构成三元环。同时显然这样的三元组会且仅会在 $ u $ 被枚举到一次,于是最终的式子便为:$ {n \choose 3} - \sum^n {\vert \mathbb{V}_i \vert \choose 2} $。
然后这样的话显然初始时为 $ 0 $,每次 $ m $ 的操作,我们把小的数作为 $ s $,大的数作为 $ t $,然后把原来减掉的组合数加回来,修改出点的数量,再把新的组合数减回去,处理好取模即可。
复杂度 $ O(n + m) $。
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define MOD 1145141
#define MAXN 11000000
template<typename T = int>
inline T read(void);
int N, M;
ll fact[MAXN], inv[MAXN];
ll qpow(ll a, ll b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}
return ret;
}
void Init(void){
fact[0] = 1;
for(int i = 1; i <= N; ++i)fact[i] = fact[i - 1] * i % MOD;
inv[N] = qpow(fact[N], MOD - 2);
for(int i = N - 1; i >= 0; --i)inv[i] = inv[i + 1] * (i + 1) % MOD;
}
ll GetC(ll n, ll m){
if(m > n)return 0;
return fact[n] * inv[m] % MOD * inv[n - m] % MOD;
}
ll out_pts[MAXN];
bool changed[MAXN];
ll ans(0);
int main(){
freopen("fxxk_triatomic_ring.in", "r", stdin);
freopen("fxxk_triatomic_ring.out", "w", stdout);
N = read(), M = read();
Init();
for(int i = 1; i <= N; ++i)out_pts[i] = N - i;
ans = GetC(N, 3);
for(int i = 1; i <= N; ++i)ans = (ans - GetC(out_pts[i], 2) + MOD) % MOD;
printf("%lld\n", ans);
while(M--){
int s = read(), t = read();
if(s > t)swap(s, t);
ans = ((ans + GetC(out_pts[s], 2)) % MOD + GetC(out_pts[t], 2)) % MOD;
--out_pts[s], ++out_pts[t];
ans = ((ans - GetC(out_pts[s], 2) + MOD) % MOD - GetC(out_pts[t], 2) + MOD) % MOD;
printf("%lld\n", ans);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
T6 不会有人语法题都切不掉吧?不会吧不会吧?
emm 没什么可说的,也算是搬的题吧,从某个网页上复制下来的。
原链接 C++「阅读程序」小测验。链接里更多的题和题解都有,感兴趣可以看看。
T5 嗯?又是data_struct?
奇怪的数据结构题,正解是莫队套 bitset
。
首先很显然,如果我们设三个区间中的交集大小为 $ \xi $,那么最终的答案即为 $ (r_1 - l_1 + 1) + (r_2 - l_2 + 1) + (r_3 - l_3 + 1) - 3 \times \xi $。
所以那么我们所未知的需要求的也就只有 $ \xi $,考虑如何维护,因为我们要维护的是公共的数的个数,而不是种类数,而我们显然是需要对这个序列进行离散化的,所以我们可以考虑在离散化的时候做一点文章,一般的离散化,对于相同的数我们会用同一个数代替,但是这里我们可以考虑存一下每一个数已经出现了几次,令其为 $ cnt_i $,于是我们可以考虑不去使用 unique
以为每个数留下足够的空间,然后让相同的数,假设其离散化后的值为 $ v $,就变成 $ cnt_v + v $,也就是让相同的数变成不同的,这样之后我们再用 bitset
套个莫队,每个 bitset
维护当前莫队区间内让每个数变得不同之后的第 $ i $ 个是否存在,然后三个区间维护好后做一下与运算就行,输出答案的时候我们要求的 $ \xi $ 也就是对应 bitset
的 count()
。
同时需要注意这么多 bitset
空间显然开不下,但是时间还在可以接受的范围内,所以考虑时间换空间,因为询问之间独立,所以把询问拆成三份,分别求解后输出答案即可。
最终复杂度感觉大概是 $ O(m \sqrt{n} + \dfrac{nm}{\omega}) $,应该没算错吧,虽然对于后一项,感觉不太能过,但是确实也是 $ O(\texttt{能过}) $,可能因为完全卡不满吧。。。(或者是我算错了)
#define _USE_MATH_DEFINES
#include <bits/extc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
using namespace __gnu_pbds;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define BLK(x) (x / QueryLen + 1)
#define MAXN 110000
template<typename T = int>
inline T read(void);
int N, M;
int QueryLen;
int splitM;
int a[MAXN];
int cnt[MAXN], len[MAXN];
bitset < MAXN > cur;
bitset < MAXN > sum[MAXN / 3 + 10];
struct Query{
int l, r;
int idx;
friend const bool operator < (const Query &x, const Query &y){
int xb = BLK(x.l), yb = BLK(y.l);
if(xb != yb)return xb < yb;
return xb & 1 ? x.r < y.r : x.r > y.r;
}
};
void add(int v){cur.set(v + cnt[v]++);}
void del(int v){cur.reset(v + --cnt[v]);}
void Solve(void){
memset(cnt, 0, sizeof(cnt));
cur.reset();
int tot(0);
vector < Query > qs;
while(++tot <= splitM && M--){
len[tot] = 0;
sum[tot].set();
int T = 3;
while(T--){
int l = read(), r = read();
qs.push_back(Query{l, r, tot});
len[tot] += r - l + 1;
}
}sort(qs.begin(), qs.end());
int gl(1), gr(0);
for(auto q : qs){
while(gl > q.l)add(a[--gl]);
while(gr < q.r)add(a[++gr]);
while(gl < q.l)del(a[gl++]);
while(gr > q.r)del(a[gr--]);
sum[q.idx] &= cur;
}--tot;
for(int i = 1; i <= tot; ++i)
printf("%d\n", len[i] - 3 * (int)sum[i].count());
}
int main(){
freopen("d0te__sturct.in", "r", stdin);
freopen("d0te__sturct.out", "w", stdout);
N = read(), M = read();
vector < int > arr;
for(int i = 1; i <= N; ++i)a[i] = read(), arr.push_back(a[i]);
QueryLen = sqrt(N), splitM = M / 3 + 1;
sort(arr.begin(), arr.end());
for(int i = 1; i <= N; ++i)a[i] = distance(arr.begin(), lower_bound(arr.begin(), arr.end(), a[i])) + 1;
Solve(), Solve(), Solve();
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}