NOI 模拟赛
咕了半年的 Meaningless Round 终于来了,好评
虽然题目我一道都不会吧
T1
$n$ 个人排成一个环,轮流扔硬币,正面就出局,无论如何都给下一个人继续扔,求每个人留到最后的概率 $mod \space 998244353$,硬币扔到正面的概率是 $\frac{p}{q}$
$n \leq 5000$
sol:
容易发现当 $n=2$ 的时候就是一个收敛的无穷级数求和,但当 $n>2$ 的时候如果想要 dp,似乎怎么都绕不开无穷级数这个东西
于是考场上自闭了,列了五六个错误的式子(甚至有一个只能过 $n=2$
想了俩小时拿了 $0$ 分
其实可以考虑高斯消元
把规则换个方式描述就是:标号是一个圆盘,每次在 $1$ 号位置的人扔硬币,扔完圆盘旋转,如果扔的人出局,圆盘删掉一个标号
这样我们可以设 $f_{(i,j)}$ 为剩下 $i$ 个人的时候,$j$ 号位置的人留到最后的概率
讨论 $1$ 号位置是否出局,出局就是 $\frac{p}{q} \times f_{(i-1,j-1)}$,没出局就是 $\frac{p}{q} \times f_{(i,j-1)}$ 注意到这个式子里 $2 \leq j \leq i$,于是有了 $i-1$ 个方程,再加上最后一个 $\sum\limits_{j=1}^i f_{(i,j)} = 1$(因为最后总会有一个人赢),就是每行 $i$ 个未知数 $i$ 个方程,可以高斯消元,总复杂度 $O(n^4)$
事实上,考虑到每个转移总与上一行的数(已计算)和本行所有数有关,我们可以把每个 $f_{(i,j)}$ 都推成一个关于 $f_{(i,1)}$ 和上一行的数有关的式子,再用上文提到的“最后一个方程”把 $f_{(i,1)}$ 解出来,这样每行转移就是 $O(n)$ 的了
总复杂度 $O(n^2)$
T2
定义字符串加法为把两个字符串拼起来,定义 $f(S)$ 为 $S$ 本质不同的子串的数量
给一个字符串 $S$ 和 $q$ 组询问 $[l,r]$
求 $\sum\limits_{i=1}^{l-1} \sum\limits_{j=r+1}^n f(S[1,i] + S[j,n])$
$n,q \leq 100000$
sol:
考试的时候完全不会,就瞎猜了个结论
发现对于每次询问 $[l,r]$ 是在左边选一个前缀,右边选一个后缀(这里前后缀都可以为空)拼起来有多少本质不同的方案
感性上理解一下,左右两边每有一个相同字符,答案就 $-1$,然后就是询问 $\sum\limits_x get(1,l-1,x) \times get(r+1,n,x)$,其中 $get(l,r,x)$ 表示 $x$ 在 $[l,r]$ 中出现的次数
咦,这不是 bzoj5016 吗,然后我傻了,还是差分成了 $4$ 个询问,其实一开始让 $r$ 在 $n+1$ 位置就可以不差分了
但事实上 400000 组询问也在半秒钟内跑完了
然后就莫名其妙过了
#include<bits/stdc++.h> #define LL long long #define rep(i, s, t) for (register int i = (s), i##end = (t); i <= i##end; ++i) #define dwn(i, s, t) for (register int i = (s), i##end = (t); i >= i##end; --i) using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } const int maxn = 100010; int n,a[maxn]; int bl[maxn],tot,cntl[maxn],cntr[maxn]; LL ans[maxn]; struct Ques { int l,r,flag,pos; Ques(){} Ques(int _1,int _2,int _3,int _4): l(_1),r(_2),flag(_3),pos(_4){} bool operator < (const Ques &b)const{return (bl[l] == bl[b.l]) ? (r < b.r) : (bl[l] < bl[b.l]);} }qs[maxn << 2]; int qa[maxn], qb[maxn]; int main() { freopen("B.in","r",stdin); freopen("B.out","w",stdout); n = read(); for(int i=1;i<=n;i++)a[i] = read(); int BLSIZE = sqrt(n); for(int i=1;i<=n;i++)bl[i] = i / BLSIZE; int q = read(); for(int i=1;i<=q;i++) { int xl = 1,xr = read() - 1,yl = read() + 1,yr = n; qa[i] = xr + 1, qb[i] = yl - 1; qs[++tot] = Ques(xr,yr,1,i); if(xl > 1)qs[++tot] = Ques(xl - 1,yr,-1,i); if(yl > 1)qs[++tot] = Ques(xr,yl - 1,-1,i); if(xl > 1 && xr > 1)qs[++tot] = Ques(xl - 1,yl - 1,1,i); } sort(qs + 1,qs + tot + 1); int l = 0,r = 0; LL now = 0; for(int i=1;i<=tot;i++) { //if(qs[i].l > qs[i].r) continue; while(l < qs[i].l){l++;now += cntr[a[l]];cntl[a[l]]++;} while(r < qs[i].r){r++;now += cntl[a[r]];cntr[a[r]]++;} while(l > qs[i].l){cntl[a[l]]--;now -= cntr[a[l]];l--;} while(r > qs[i].r){cntr[a[r]]--;now -= cntl[a[r]];r--;} ans[qs[i].pos] += qs[i].flag * now; } for(int i=1;i<=q;i++) printf("%lld\n", 1LL * qa[i] * (n - qb[i] + 1) - ans[i]); }
T3
给 $n$ 条直线和一个点 $(x,y)$ ,求这些直线所有交点中与 $(x,y)$ 距离前 $m$ 近的点与 $(x,y)$ 距离之和
注意如果有 $k$ 条直线交于一点,这个交点将被算作 $\binom{k}{2}$ 个点
$n \leq 10^5$
sol:
计算几何不会啊 qnq,结果考完发现是解析几何
不仅 OI 退役文化课还要退役
前 $m$ 近的点,我们可以二分一个半径 $R$ ,然后画一个圆把这 $m$ 个点包起来
容易知道两条直线有有效的交点当且仅当它们都与圆有交点,且两条直线与圆交点按照一定方向(顺/逆时针)排好序之后是 $A_l,B_l,A_r,B_r$
这个可以 dp 一下