学习日志3

上一篇学习日志因为春节贪玩 鸽了,希望这篇不要再这样了

Need to do

  1. 边分治学习笔记(完成)
  2. CTSC2018暴力写挂(完成)
  3. 十二省联考皮配题解
  4. 完成模拟赛T3(这个实在不会)
  5. 单位根反演学习(完成)
  6. 网络流深入学习
  7. 补模拟赛T2(完成)
  8. 复习burnside/Polya定理,(群论学习先咕咕吧,不会)
  9. 数学:学习线性代数:矩阵可对角化\特征根和特征多项式
  10. 完成blog:一类积性函数求和的方法

 

upd.2021.2.21

希望今\明天能打起状态来,be confident about yourself

本来想学点东西的,结果调题调了1个晚上

 upd 2021.2.22

晚上target

  1. 完成T2题解(完成)
  2. 改T3(不会树分块,爬了,不过链上/序列的的求若干满足条件的二/三元组,可以往分治/分块方面想)
  3. 写一下昨天模拟赛的简要题解
  4. 学习线性代数:矩阵可对角化\特征根和特征多项式(简单学习了一下,深刻觉得想用这玩意解3阶递推式的我一定是脑子有问题,推一个递推式推了1h多还推错了.jpg)
  5. 完成至少一道网络流建模题

一道精妙的数学题

  • 给定两个数列{$a_n$},{$b_n$},$a_0$ = $0$,$b_0$ = $1$
  • $a_n$ = $x$$a_{n-1}$ - $y$$b_{n-1}$
  • $b_n$ = $x$$b_{n-1}$ + $y$$a_{n-1}$
  • 求a,b通项

  一句话题解,设$c_n$ = $a_n$ + $i$$b_n$,i为虚数单位,则有$c_n$ = $(x + y_i)^n$

  很巧妙的构造

 市内模拟赛简要题解

  1. 雅礼集训2017 Day7 事件的相似度

   套路题,我们将询问离线,用扫描线,同时用LCT维护parent树,就是每扫描到一个点,就access一遍,更新这些点当前最长的前缀是哪里,用线段树维护当前点到每个点的区间的前缀的最长公共后缀,在access的过程中顺便更新即可.

  2.[AHOI/HNOI2017]大佬

    容易发现,你怼大佬的时间和回血是无关的,所以我们可以先dp一下你最多可以怼大佬的次数,然后可以bfs出你可以造成的伤害(如果需要全局的值,并且希望其满足某种单调性,用bfs要比dfs好),hash判重,注意到状态数不多,(最多去重完1e6左右,实际数据远远不到)

    然后就变成一个在数列上是否存在满足若干条件的2元组,排序完双指针即可.

  3.[清华集训2016]Alice and Bob又在玩游戏

  一个重要的套路题(ix35鸽鸽说的)

  显然这是一个公平博弈游戏,于是我们考虑SG函数,考虑对于x的子树求SG[x],根据multi_SG定理,我们要枚举删除的是哪个点,然后对得到的SG值作mex

  考虑至下而上合并,发现如果删除x自己,SG值就是$Xor_u$($u \in son_x$),如果是删除的点在某个儿子的子树内,不妨设其为v,那么新的SG值显然是其在v中的SG值异或上所有兄弟的SG值的异或和

  发现对于所有在v子树内的删除方法,转移都一样,于是考虑用每个点建一个Trie维护SG集合,支持全局Xor,支持合并(父亲与儿子合并,先对儿子全局Xor再合并),查询mex值即可

  代码:

#include<bits/stdc++.h>
using namespace std;
int read(){
    int x = 0;char c = getchar();
    while(c < '0' || c > '9')    c = getchar();
    while(c >= '0' && c <= '9')    x = x * 10 + c - 48,c = getchar();
    return x;
}
const int N = 2e5 + 10;
#define T 17
bool fuck = 0;
struct Edge{
    int nxt,point;
}edge[N<<1];
int head[N],tot,n,m;
void add_edge(int u,int v){
    edge[++tot].nxt = head[u];
    head[u] = tot;
    edge[tot].point = v;
}
int rt[N];
struct Trie{
    int ch[N<<5][2],cnt,tag[N<<5],siz[N<<5];int st[N],top;
    void init(){
        memset(ch,0,sizeof(ch));
        cnt = 0;top = 0;
        memset(siz,0,sizeof(siz));
        memset(tag,0,sizeof(tag));
    }
    void pushup(int p){
        siz[p] = siz[ch[p][0]] + siz[ch[p][1]];
    }
    void pushmark(int p,int bit){
        if(!tag[p])    return;
        if(tag[p] & (1 << bit))        swap(ch[p][0],ch[p][1]);
        tag[ch[p][0]] ^= tag[p];tag[ch[p][1]] ^= tag[p];
        tag[p] = 0;
    }
    void ins(int x,int id){
        if(!rt[id])    rt[id] = ++cnt;
        int p = rt[id];
        for(int i = T; i >= 0; --i){
            pushmark(p,i);st[++top] = p;
            int c = ((x & (1 << i)) > 0);
            if(!ch[p][c])    ch[p][c] = ++cnt;
            p = ch[p][c];
        }siz[p] = 1;
        while(top){
            pushup(st[top]),top--;
        }
    }
    int merge(int u,int v,int bit){
        if(!u || !v)    return u | v;
        if(bit == -1){
//            assert(siz[u] == 1 && siz[v] == 1);
            return u;
        }
        pushmark(u,bit);pushmark(v,bit);
        ch[u][0] = merge(ch[u][0],ch[v][0],bit-1);
        ch[u][1] = merge(ch[u][1],ch[v][1],bit-1);
        pushup(u);
        return u;
    }
    int mex(int u){
        int p = rt[u];
        int ans = 0;
        for(int i = T; i >= 0; --i){
            pushmark(p,i);
            if(siz[ch[p][0]] != (1 << i)){
                p = ch[p][0];
            }
            else{
                p = ch[p][1];
                ans += (1 << i);
            }
        }
        return ans;
    }
}trie;
bool vis[N];
int sg[N];
void dfs(int u,int fa){
    vis[u] = 1;
    int _sg = 0;
    for(int i = head[u]; i ; i = edge[i].nxt){
        int v = edge[i].point;
        if(v ^ fa){
            dfs(v,u);
            _sg ^= sg[v];
        }
    }    
    trie.ins(_sg,u);
    for(int i = head[u]; i ; i = edge[i].nxt){
        int v = edge[i].point;
        if(v ^ fa){
//            cout<<u<<' '<<v<<' '<<(_sg ^ sg[v])<<endl;
            trie.tag[rt[v]] ^= (_sg ^ sg[v]);
            rt[u] = trie.merge(rt[u],rt[v],T);
        }
    }
    sg[u] = trie.mex(u);
}
void solve(){
    n = read(),m = read();
    tot = 0;
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
    memset(rt,0,sizeof(rt));
    trie.init();
    for(int i = 1; i <= m; ++i){
        int u = read(),v = read();
        add_edge(u,v);add_edge(v,u);
    }
    int ans = 0;
    for(int i = 1; i <= n; ++i){
        if(!vis[i]){
            dfs(i,0);
            ans ^= sg[i];
        }
    }
    if(ans)    puts("Alice");
    else    puts("Bob");
}
int main(){
    freopen("data.in","r",stdin);    
    freopen("data.out","w",stdout);
    int t = read();
    while(t--)        solve();
    return 0;
}
View Code

 upd 2.28

HDU 6405 Make ZYB Happy

近似于广义SAM模板?

具体怎么做就不说了,广义后缀自动机有以下性质:

  • 设$End_{i,j}$为第i个字符串,第j个前缀在SAM上的点,n为字符串长度总和,则每个点暴力往上跳,计算SAM每个点包含了多少不同字符串的时间复杂度是$O(n\sqrt n)$
  • 证明主要是我们考虑对于一个长度为m的字符串,设S为后缀自动机节点数,它遍历的点数 $\leq$ $min(m^2,S)$,$m^2$是因为总共最多$m^2$个子串,均摊以下即可得到以上结果
/*Make ZYB Happy*/ 
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cstdio>
using namespace std;
#define ll long long
int read(){
    char c = getchar();
    int x = 0;
    while(c < '0' || c > '9')        c = getchar();
    while(c >= '0' && c <= '9')        x = x * 10 + c - 48,c = getchar();
    return x;
}
const int N = 1e6 + 10;
const int mod = 1e9 + 7;
struct SAM{
    int ch[27];
    int len,fa;
}sam[N<<2]; 
int w[N<<2],a[N<<2];
vector<int>End[N];
int ans[N],mi[N],sum[N];
#define pb push_back
int lst = 1,cnt = 1;
char s[N];
int vis[N];
int qpow(int x,int y){
    int ans = 1;
    while(y){
        if(y & 1)    ans = 1ll * ans * x % mod;
        x = 1ll * x * x % mod;
        y >>= 1; 
    }
    return ans;
}
void ins(int c){
    if(sam[lst].ch[c] && sam[lst].len + 1 == sam[sam[lst].ch[c]].len){
        lst = sam[lst].ch[c];
        return;
    }
    int p = lst,np = lst = ++cnt;
    sam[np].len = sam[p].len + 1;
    for(; p && !sam[p].ch[c]; p = sam[p].fa)    sam[p].ch[c] = np;
    if(!p)        sam[np].fa = 1;
    else{
        int q = sam[p].ch[c];
        if(sam[q].len == sam[p].len + 1)    sam[np].fa = q;
        else{
            int nq = ++cnt;
            if(p == lst)    lst = nq;
            sam[nq] = sam[q];
            sam[q].fa = sam[np].fa = nq;
            sam[nq].len = sam[p].len + 1;
            for(; p && sam[p].ch[c] == q; p = sam[p].fa)    sam[p].ch[c] = nq;
        }
    }
}
void add(int &x,int y){
    x += y - mod;
    x += (x >> 31) & mod;
}
void calc(int u,int T){
    if(vis[u] == T)        return;
    if(!u)    return;
    vis[u] = T;
    w[u] = 1ll * w[u] * a[T] % mod; 
    calc(sam[u].fa,T);
}
int main(){
    int n = read();
    for(int i = 1; i <= n; ++i){
        scanf("%s",s+1);
        int m = strlen(s+1);
        End[i].pb(0); 
        lst = 1;
        for(int j = 1; j <= m; ++j){
            ins(s[j]-'a');
            End[i].pb(lst);
        }
    }
    for(int i = 1; i <= cnt; ++i)        w[i] = 1;
    mi[0] = 1;
    for(int i = 1; i <= 1e6; ++i)    mi[i] = 1ll * mi[i-1] * 26 % mod;
    for(int i = 1; i <= 1e6; ++i)     sum[i] = sum[i-1],add(sum[i],mi[i]);
    for(int i = 1; i <= n; ++i)        a[i] = read();
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j < (int)End[i].size(); ++j){
            calc(End[i][j],i);
        }
    }
    for(int i = 2; i <= cnt; ++i){
        int l = sam[sam[i].fa].len + 1,r = sam[i].len;
        add(ans[l],w[i]);add(ans[r+1],(mod-w[i])%mod);
    }
    for(int i = 1; i <= 1e6; ++i)    add(ans[i],ans[i-1]);
    for(int i = 1; i <= 1e6; ++i)    add(ans[i],ans[i-1]);
    int q = read();
    while(q--){
        int m = read();
        printf("%lld\n",1ll * ans[m] * qpow(sum[m],mod-2) % mod);
    }
    return 0;
}
View Code

 upd 3.1

[NOI2016循环之美]

考虑$\frac{n}{m}$满足什么条件在k进制下是纯循环的,首先我们钦定gcd(n,m) == 1,否则可以得到更简的分数,容易发现商循环等价于余数循环,解一个简单的同余方程,可以发现充要条件是gcd(m,k) == 1

然后推导过程如下: 

对于k = 1情况直接整除分块杜教筛即可

[ZJOI2020传统艺能]

据说是ZJ神选2020的签到题,soulist大佬跟我说这是sb题

首先根据期望的线性性,可以对每个点分别考虑,最后再加起来即可

考虑这个点什么情况下可能打上标记

  • 完全包含了这个区间且没有完全包含其父区间
  • 父区间有标记,且修改到其兄弟,下传到它

考虑什么情况下打上的标记会被下传,显然是该点代表的区间被分割开来

可以发现我们除了关心某一轮该点是否有标记外,我们还关心其祖先是否有标记,考虑如下的状态设计,(以下都只讨论u一个节点)

  • $g_i$表示第i轮,u节点有标记的概率
  • $f_i$表示第i轮,u节点及其祖先没有标记的概率
  • $h_i$表示第i轮,u节点没有标记,其存在祖先有标记的概率

另外预处理几个辅助数组

  • $p1_u$表示u上一轮已有标记,这一轮不下传的概率
  • $p2_u$表示该轮修改能把标记打到u上的概率
  • $p3_u$表示该轮修改完全包含了父亲所在区间的概率
  • $p4_u$表示该轮修改递归到u的兄弟,且没有修改到u的概率
  • $p5_u$表示父亲标记没有下传的概率,显然$p5_u$ = $p1_{fa}$

特判1号节点,剩下的分类讨论转移,矩阵快速幂加速即可,具体看代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int read(){
    char c = getchar();
    int x = 0;
    while(c < '0' || c > '9')        c = getchar();
    while(c >= '0' && c <= '9')        x = x * 10 + c - 48,c = getchar();
    return x;
}
const int N = 1e6 + 10;
#define mod 998244353
void add(int &x,int y){
    x += y - mod;
    x += (x >> 31) & mod;
}
int p1[N],p2[N],p3[N],p4[N],p5[N],L[N],R[N],cnt,lc[N],rc[N],n,k;
int qpow(int x,int y){
    int ans = 1;
    while(y){
        if(y & 1)    ans = 1ll * ans * x % mod;
        x = 1ll * x * x % mod;
        y >>= 1;
    }
    return ans;
}
int C(int len){
    return ((1ll * len * (len - 1) / 2) + len) % mod;
}
int Inv;
void build(int &u,int fa,int l,int r,int tag){/*表示是左儿子还是右儿子*/
    if(!u)    u = ++cnt; 
    L[u] = l,R[u] = r;
    int len = r - l + 1;
    if(u != 1){
        int P = (1ll * (r - l) * (n - r + l - 1) + C(len) - 1) % mod;
        P = 1ll * P * Inv % mod;
        p1[u] = (1 - P + mod) % mod;
        int fal = L[fa],far = R[fa];
        if(tag == 0){
            p2[u] = 1ll * l * (far - r) * Inv % mod;
            p4[u] = 1ll * (1ll * (far - r) * (n - far) % mod + (C(far-r))) * Inv % mod;
        }
        else{
            p2[u] = 1ll * (n - r + 1) * (l - fal) * Inv % mod;
            p4[u] = 1ll * (1ll * (l - fal) * (fal - 1) % mod + C(l - fal)) * Inv % mod;
        }
        p3[u] = 1ll * fal * (n - far + 1) % mod * Inv % mod;
        if(fa != 1)
            p5[u] = p1[fa];
        else    p5[u] = Inv;
    }
    if(l == r)    return;
    int mid = read();
    build(lc[u],u,l,mid,0);
    build(rc[u],u,mid+1,r,1);
}
int A[3][3],B[3][3],F[3],G[3];
void mul(){
    memcpy(G,F,sizeof(F));
    memset(F,0,sizeof(F));
    for(int i = 0; i < 3; ++i)
        for(int j = 0; j < 3; ++j)
            add(F[i],1ll * A[i][j] * G[j] % mod);
}
void mulitself(){
    memcpy(B,A,sizeof(A));
    memset(A,0,sizeof(A));
    for(int i = 0; i < 3; ++i)
        for(int j = 0; j < 3; ++j)
            for(int k = 0; k < 3; ++k)
                add(A[i][j],1ll * B[i][k] * B[k][j] % mod);
}
int calc(int u){
//    clr(f);clr(g);clr(h);
    /*f[0] = 1; 
    for(int i = 1; i <= k; ++i){
        g[i] = (1ll * g[i-1] * p1[u] % mod + 1ll * (f[i-1] + h[i-1]) * p2[u] + 1ll * h[i-1] * p4[u]) % mod;
        h[i] = (1ll * f[i-1] * p3[u] % mod + 1ll * h[i-1] * p5[u]) % mod;
        f[i] = (1ll - g[i] - h[i] + 2ll * mod) % mod;
    }
    return g[k];*/
    memset(F,0,sizeof(F));
    F[2] = 1; 
    A[0][0] = (p1[u] - p2[u] + mod) % mod,A[0][1] = p4[u],A[0][2] = p2[u];
    A[1][0] = (mod - p3[u]) % mod,A[1][1] = (p5[u] - p3[u] + mod) % mod,A[1][2] = p3[u];
    A[2][0] = 0,A[2][1] = 0,A[2][2] = 1;
    int m = k;
    while(m){
        if(m & 1)    mul();
        mulitself();
        m >>= 1;
    }
    return F[0];
}
int main(){
    int rt = 0;n = read(),k = read();
    Inv = qpow(C(n),mod-2);
    build(rt,0,1,n,0);
    int ans = 0;
    for(int i = 2; i <= cnt; ++i)    add(ans,calc(i));
    add(ans,Inv);
    printf("%d\n",ans);
    return 0;
}
View Code

 

posted @ 2021-02-16 17:20  y_dove  阅读(143)  评论(0编辑  收藏  举报