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]);
}
View Code

 

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 一下

posted @ 2019-04-02 15:46  探险家Mr.H  阅读(204)  评论(0编辑  收藏  举报