2024寒假年前集训日记

1.27

闲话

  • 下午期末家长会的时候,以为家长还没到,于是就在外面等了 \(30min+\) ,等不及了发现家长已经进去了,然后班主任也已经开始讲了,乐。

  • 吃完晚饭后,搬宿舍到了 \(1201\)

  • 晚上, \(huge\) 关于不要忽略算法的细节的讲话。

    点击查看讲话
    咱这次集训哈后面会安排放视频,学过的没学过的都建议听一下,看自己当时学这个知识点的时候就没有什么细节被忽略了。差分约束大家都学了吧,应该都做了 小K的农场 那题了吧。那题是卡基于 BFS 实现的 SPFA,我就看了眼lrb的代码,他是拿双端队列优化后的 SPFA 过的,其他人通过的我也不知道用什么方式过的。你们有拿 DFS 实现的 SPFA 吗?要是没有的话,你们可以学一下,这题 DFS 实现的 SPFA 跑得飞快。
    
  • 晚上下课后,跟着 @xrlong@wkh2008 在小操场跑了两圈,最后和他们差出了 \(100m\)

做题纪要

CF1433E Two Round Dances

CF739A Alyona and mex

1.28

闲话

  • 上午放的 \(hash\) 视频,但我打了一上午的差分约束,课基本没听。
  • 下午放的 \(KMP\) 视频。
  • 晚上,【数据删除】半小时内连续被 \(huge\) 两次抓到摸鱼,于是【数据删除】以一己之力让 \(1\) 机房洛谷被禁了。然后 \(huge\) 讲了点东西,详见 2024寒假集训纪要 1.28 部分。
  • 晚上下课后,因时间不够了,故跟着 @xrlong@wkh2008 在小操场跑了一圈,最后和他们差出了 \(50m\)

做题纪要

luogu P1993 小 K 的农场

luogu P3275 [SCOI2011] 糖果

LibreOJ 10033. 「一本通 2.1 例 1」Oulipo

  • \(hash\) 板子。

    点击查看代码
    const ll mod=998244353,base=13331;
    ll a[1000002],b[1000002],jc[1000002];
    char s1[1000002],s2[1000002];
    void sx_hash(char s[],ll a[],ll len)
    {
        for(ll i=0;i<=len;i++)
        {
            a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod);
        }
    }
    ll ask_hash(ll a[],ll l,ll r)
    {
        return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod;
    }
    int main()
    {
        ll t,len1,len2,ans,i,j;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>(s1+1)>>(s2+1);
            ans=0;
            len1=strlen(s1+1);
            len2=strlen(s2+1);
            for(j=0;j<=max(len1,len2);j++)
            {
                jc[j]=(j==0)?1:(jc[j-1]*base%mod);
            }
            sx_hash(s1,a,len1);
            sx_hash(s2,b,len2);
            for(j=1;j+len1-1<=len2;j++)
            {
                ans+=(ask_hash(a,1,len1)==ask_hash(b,j,j+len1-1));
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

luogu P3370【模板】字符串哈希

  • \(hash\) 板子。

    点击查看代码
    const ll mod=998244353,base=13331;
    ll a[20000],jc[20000];
    char s[2001];
    ll sx_hash(char s[],ll len)
    {
        ll ans;
        for(ll i=0;i<=len;i++)
        {
            ans=(i==0)?0:((ans*base%mod+s[i])%mod);
        }
        return ans;
    }
    int main()
    {
        ll n,ans=1,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>(s+1);
            a[i]=sx_hash(s,strlen(s+1));
        }
        sort(a+1,a+1+n);
        for(i=1;i<=n-1;i++)
        {
            ans+=(a[i]!=a[i+1]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P3667 [USACO17OPEN] Bovine Genomics G

  • 显然,区间长度具有单调性。考虑二分长度,然后把 \(hash\) 值丢到一个 map 里面,进行判断。

    • 容易出错的点:对于一段区间 \([l,r]\) ,只有对于所有的 \(i(1 \le i \le n)\) 均有 \(\sum\limits_{j=1}^{n}[A_{i_{l}} \sim A_{i_{r}} \ne B_{j_{l}} \sim B_{j_{r}}]=n\) 才满足题意。
  • 需要双哈希。

    点击查看代码
    const ll mod1=998244353,mod2=1000000007,base=13331;
    ll a[1002][3][1002],b[1002][3][1002],jc[3][1002];
    char s1[1002],s2[1002];
    void sx_hash(char s[],ll a[],ll len,ll mod)
    {
        for(ll i=0;i<=len;i++)
        {
            a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod);
        }
    }
    ll ask_hash(ll a[],ll jc[],ll l,ll r,ll mod)
    {
        return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod;
    }
    bool check(ll mid,ll n,ll m)
    {
        ll i,l,r,flag;
        for(l=1,r=l+mid-1;r<=m;l++,r++)
        {
            map<ll,bool>vis[3];
            flag=0;
            for(i=1;i<=n;i++)
            {
                vis[1][ask_hash(a[i][1],jc[1],l,r,mod1)]=true;
                vis[2][ask_hash(a[i][2],jc[2],l,r,mod2)]=true;
            }
            for(i=1;i<=n;i++)
            {
                if(vis[1].find(ask_hash(b[i][1],jc[1],l,r,mod1))!=vis[1].end()&&vis[2].find(ask_hash(b[i][2],jc[2],l,r,mod2))!=vis[2].end())
                {
                    flag=1;
                    break;
                }
            }
            if(flag==0)
            {
                return false;
            }
        }
        return true;
    }
    int main()
    {
        ll n,m,l=1,r,ans,mid,i;
        cin>>n>>m;
        r=m;
        for(i=0;i<=m;i++)
        {
            jc[1][i]=(i==0)?1:(jc[1][i-1]*base%mod1);
            jc[2][i]=(i==0)?1:(jc[2][i-1]*base%mod2);
        }
        for(i=1;i<=n;i++)
        {
            cin>>(s1+1);
            sx_hash(s1,a[i][1],m,mod1);
            sx_hash(s1,a[i][2],m,mod2);
        }
        for(i=1;i<=n;i++)
        {
            cin>>(s2+1);
            sx_hash(s2,b[i][1],m,mod1);
            sx_hash(s2,b[i][2],m,mod2);
        }
        while(l<=r)
        {
            mid=(l+r)/2;
            if(check(mid,n,m)==true)
            {
                l=mid+1;
            }
            else
            {
                ans=mid;
                r=mid-1;
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P4503 [CTSC2014] 企鹅 QQ

  • 考虑枚举不同的位置,然后将前后的 \(hash\) 值再次进行 \(hash\) 然后拼起来。

  • map 常数过大,在找一个数在区间内出现的次数时,使用 sort 挨个枚举进行判断,而不是使用 map 存储。

  • 这题卡 \(hash\) 的模数,故选择 unsigned long long 的自然溢出和 rand() 的随机数来作为模数。

    点击查看代码
    const ull base=13331;
    ull a[30002][300],aa[30002],jc[70002];
    char s[300],ss[300];
    void sx_hash(char s[],ull a[],ull len)
    {
        for(ull i=0;i<=len;i++)
        {
            a[i]=(i==0)?0:(a[i-1]*base+s[i]);
        }
    }
    ull ask_hash(ull a[],ull l,ull r)
    {
        return a[r]-a[l-1]*jc[r-l+1];
    }
    int main()
    {
        ull n,m,ss,ans=0,sum,mod1,mod2,i,j;
        cin>>n>>m>>ss;
        srand(time(0));
        mod1=rand();
        mod2=rand();
        for(i=0;i<=m;i++)
        {
            jc[i]=(i==0)?1:(jc[i-1]*base);
        }
        for(i=1;i<=n;i++)
        {
            cin>>(s+1);
            sx_hash(s,a[i],m);
        }
        for(i=1;i<=m;i++)
        {
            for(j=1;j<=n;j++)
            {
                aa[j]=ask_hash(a[j],1,i-1)*mod1+ask_hash(a[j],i+1,m)*mod2;
            }
            sort(aa+1,aa+1+n);
            sum=1;
            for(j=1;j<=n-1;j++)
            {
                if(aa[j]==aa[j+1])
                {
                    ans+=sum;
                    sum++;
                }
                else
                {
                    sum=1;
                }
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P10114 [LMXOI Round 1] Size

  • 前置知识:若 \(\sum\limits_{i=1}^{n}d_i \le V\) ,则 \(\{ d \}\) 中最多只有 \(\sqrt{V}\) 个不同的数。

  • \(sum_{i}\) 表示 \(i\)\(\{ d \}\) 中出现的次数, \(\{ a \}\)\(\{ d \}\) 去重后的序列。\(\sum\limits_{i=1}^{|a|}\sum\limits_{j=1}^{|a|}sum_{a_{i}} \times sum_{a_{j}} \times ((a_{i} \bigoplus a_{j})+(a_{i} \bigotimes a_{j}))\) 即为所求。

    点击查看代码
    ll d[2000001],a[2000001],sum[50000001];
    int main()
    {
        ll n,m=0,ans=0,i,j;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>d[i];
            if(sum[d[i]]==0)
            {
                m++;
                a[m]=d[i];
            }
            sum[d[i]]++;
        }
        for(i=1;i<=m;i++)
        {
            for(j=1;j<=m;j++)
            {
                ans+=sum[a[i]]*sum[a[j]]*(__builtin_popcountll(a[i]+a[j])+__builtin_popcountll(abs(a[i]-a[j])));
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

1.29

闲话

  • 【数据删除】因昨天晚上的事情被家长接走了,然后机房就变消停了不少。
  • 不知道什么时候, \(miaomiao\) 来催了下做题。
  • 中午吃完饭后吃了个豆干,然后嗓子就开始疼。
  • 下午考勤机(刷脸)到了,不知道是第几次教练给吩咐作息时间。
    • 为什么晚上吃饭的时间这么短。
  • 晚上吃饭的时候,看了下从机房跑到食堂需要 \(2min\) ,应该可以更快。
  • 晚上放的 \(manacher\) 和扩展 \(KMP\) 视频。视频放完后,出去接水的路上,碰见了 \(feifei\) 问两个来上课的 \(HZOI2023\) 都要考期末了为什么还来上课;但平常 \(HZOI2024\) 就算年级部说停课仍来上课,教练问都不问。震惊到了。
  • 晚上下课后,跟着 @xrlong@wkh2008 在小操场跑了两圈,勉强跟得上。

做题纪要

luogu P4591 [TJOI2018] 碱基序列

  • \(f_{i,j}(1 \le i \le k,1 \le j \le |s|)\) 表示使用第 \(i\) 个氨基酸,能匹配到原碱基串的第 \(j\) 个位置的合法方案数。

  • 状态转移方程为 \(f_{i,j}=\begin{cases}1 & i=0 \\ \sum\limits_{k=1}^{a_{i}}[s_{j-|ss_{k}|+1} \sim s_{j}=ss_{1} \sim ss_{k_{1 \sim |ss_{k}|}}] \times f_{i-1,j-|ss_{k}|+1-1} & i \ne 0 \end{cases}\) ,为方便代码书写,我们选择枚举左右端点 \(l,r\) 使得 \(l=j-|ss_{k}|+1,r=j\)

  • \(\sum\limits_{i=1}^{|s|}f_{k,i}\) 即为所求。

    点击查看代码
    const ll mod=998244353,base=13331;
    ll a[20000],b[200][20][20],jc[20000],len[200][20],f[200][20000];
    char s1[20000],s2[200][20][20];
    void sx_hash(char s[],ll a[],ll len)
    {
        for(ll i=0;i<=len;i++)
        {
            a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod);
        }
    }
    ll ask_hash(ll a[],ll l,ll r)
    {
        return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod;
    }
    int main()
    {
        ll n,ans=0,maxlen,aa,i,j,l,r,p=1000000007;
        cin>>n>>(s1+1);
        maxlen=strlen(s1+1);
        for(i=0;i<=maxlen;i++)
        {
            jc[i]=(i==0)?1:(jc[i-1]*base%mod);
        }
        sx_hash(s1,a,maxlen);
        for(i=0;i<=maxlen;i++)
        {
            f[0][i]=1;
        }
        for(i=1;i<=n;i++)
        {
            cin>>aa;
            for(j=1;j<=aa;j++)
            {
                cin>>(s2[i][j]+1);
                len[i][j]=strlen(s2[i][j]+1);
                sx_hash(s2[i][j],b[i][j],len[i][j]);
                for(l=1,r=l+len[i][j]-1;r<=maxlen;l++,r++)
                {
                    f[i][r]=(f[i][r]+(ask_hash(a,l,r)==ask_hash(b[i][j],1,len[i][j]))*f[i-1][l-1])%p;
                }
            }
        }
        for(i=1;i<=maxlen;i++)	
        {
            ans=(ans+f[n][i])%p;
        }
        cout<<ans<<endl;
        return 0;
    }
    

LibreOJ 10150. 「一本通 5.1 练习 1」括号配对

  • \(f_{l,r}(1 \le l \le r \le |s|)\) 表示为使从 \(l \sim r\) 变为合法的括号序列至少需要添加的字符数。

  • 定义 \(check(l,r)=\begin{cases} true &s[l]='[' \ and \ s[r]=']' \\ true &s[l]='(' \ and \ s[r]=')' \\ false & otherwise \end{cases}\)

  • 考虑分别对左边或右边进行添加括号以达到合法状态,或者以区间 \(DP\) 的形式拆分成两部分求解,故状态转移方程为 \(f_{l,r}=\begin{cases}1 & l=r \\ \min \{f_{l,r},f_{l+1,r-1},f_{l+1,r}+1,f_{l,r-1}+1,\min\limits_{i=l}^{r-1} \{ f_{l,i}+f_{i+1,r} \} \} & check(l,r)=true \\ \min \{f_{l,r},f_{l+1,r}+1,f_{l,r-1}+1,\min\limits_{i=l}^{r-1} \{ f_{l,i}+f_{i+1,r} \} \} & check(l,r)=false \end{cases}\)

  • 注意初始值的设定。

    点击查看代码
    int f[200][200];
    char s[200];
    int main()
    {
        ll n,len,l,r,i;
        cin>>(s+1);
        n=strlen(s+1);
        for(l=1;l<=n;l++)
        {
            f[l][l]=1;
            for(r=l+1;r<=n;r++)
            {
                f[l][r]=0x3f3f3f3f;
            }
        }
        for(len=2;len<=n;len++)
        {
            for(l=1,r=l+len-1;r<=n;l++,r++)
            {
                if((s[l]=='['&&s[r]==']')||(s[l]=='('&&s[r]==')'))
                {
                    f[l][r]=min(f[l][r],f[l+1][r-1]);
                }
                f[l][r]=min(f[l][r],min(f[l+1][r]+1,f[l][r-1]+1));
                for(i=l;i<=r-1;i++)
                {
                    f[l][r]=min(f[l][r],f[l][i]+f[i+1][r]);
                }
            }
        }
        cout<<f[1][n]<<endl;
        return 0;
    }
    

luogu P4305 [JLOI2011] 不重复数字

  • 注意此题较大的输入输出量。

    点击查看代码
    int main()
    {
        int t,n,a,i,j;
        scanf("%d",&t);
        for(i=1;i<=t;i++)
        {
            scanf("%d",&n);
            map<int,bool>vis;
            for(j=1;j<=n;j++)
            {
                scanf("%d",&a);
                if(vis.find(a)==vis.end())
                {
                    printf("%d ",a);
                    vis[a]=true;
                }
            }
            printf("\n");
        }
        return 0;
    }
    

luogu P3375【模板】KMP

  • \(KMP\) 板子。

    点击查看代码
    ll nxt[2000000];
    char s1[2000000],s2[2000000];
    int main()
    {
        int n,m,i,j;
        cin>>(s1+1)>>(s2+1);
        n=strlen(s1+1);
        m=strlen(s2+1);
        for(i=2,nxt[1]=j=0;i<=m;i++)
        {
            while(j>=1&&s2[i]!=s2[j+1])
            {
                j=nxt[j];
            }
            j+=(s2[i]==s2[j+1]);
            nxt[i]=j;
        }
        for(i=1,j=0;i<=n;i++)
        {
            while(j>=1&&(j==m||s1[i]!=s2[j+1]))
            {
                j=nxt[j];
            }
            j+=(s1[i]==s2[j+1]);
            if(j==m)
            {
                cout<<i-m+1<<endl;
                j=nxt[m];
            }
        }
        for(i=1;i<=m;i++)
        {
            cout<<nxt[i]<<" ";
        }
        return 0;
    }
    

LibreOJ 10036. 「一本通 2.1 练习 2」Seek the Name, Seek the Fame

  • 有一个比较显然的结论: \(Border\)\(Border\) 还是 \(Border\)

  • 因为 \(next_{i}(1 \le i \le |S|)\) 表示的是 \(S_{1} \sim S_{i}\) 的最长的 \(Border\) 长度,故反着跳 \(next\) 即可。

    点击查看代码
    ll nxt[400002];
    char s[400002];
    void print(ll x)
    {
    	if(x>=1)
    	{
    		print(nxt[x-(nxt[x]==x)]);//若nxt[x]==x成立,则说明从1到x是一个Border
    		cout<<x<<" ";
    	}
    }
    int main()
    {
    	ll n,i,j;
    	while(cin>>(s+1))
    	{
    		n=strlen(s+1);
    		for(i=2,nxt[1]=j=0;i<=n;i++)
    		{
    			while(j>=1&&s[i]!=s[j+1])
    			{
    				j=nxt[j];
    			}
    			j+=(s[i]==s[j+1]);
    			nxt[i]=j;
    		}
    		print(n);
    		cout<<endl;
    	}
    	return 0;
    }
    

luogu P4824 [USACO15FEB] Censoring S

  • 多倍经验: LibreOJ 10043. 「一本通 2.2 例 1」剪花布条

  • 考虑用一个栈维护剩下的字符串,在匹配成功后将匹配成功的部分弹出栈,再继续进行匹配。故需要额外记录 \(f_{i}(1 \le i \le |S|)\) 表示 \(S\) 中以 \(i\) 结尾的子串与 \(T\) 的前缀能够匹配的最长长度;特别地,当 \(f_{i}=|T|\) 时, \(i\)\(T\)\(S\) 中的某一次出现的位置(左端点)。

  • 因为最后需要顺序输出,故使用 deque 代替。

    点击查看代码
    ll nxt[1000002],f[1000002];
    char s1[1000002],s2[1000002];
    deque<ll>q;
    int main()
    {
        ll n,m,i,j,k;
        cin>>(s1+1)>>(s2+1);
        n=strlen(s1+1);
        m=strlen(s2+1);
        for(i=2,nxt[1]=j=0;i<=m;i++)
        {
            while(j>=1&&s2[i]!=s2[j+1])
            {
                j=nxt[j];
            }
            j+=(s2[i]==s2[j+1]);
            nxt[i]=j;
        }
        for(i=1,j=0;i<=n;i++)
        {
            q.push_back(i);
            while(j>=1&&(j==m||s1[i]!=s2[j+1]))
            {
                j=nxt[j];
            }
            j+=(s1[i]==s2[j+1]);
            f[i]=j;
            if(j==m)
            {
                for(k=1;k<=m;k++)
                {
                    q.pop_back();
                }
                j=f[(q.empty()==0)?q.back():0];//记得跳回去
            }
        }
        while(q.empty()==0)
        {
            cout<<s1[q.front()];
            q.pop_front();
        }
        return 0;
    }
    

每日总结、反思

  • 以讲题的方式加深对题目的理解,巩固知识点是好的。
  • 自己码力不行和思维受限的问题越来越明显了,要想办法解决,一定要想办法解决。

1.30

闲话

  • 下午放的 \(LCA\) 视频。

做题纪要

luogu P3435 [POI2006] OKR-Periods of Words

  • 简化题意:给定一个字符串 \(S\) ,求 \(S\) 的所有前缀的最长周期的长度之和。

  • 前置知识:对于一个字符串 \(S\) ,由于 \(next_{|S|}\)\(S\) 最长 \(Border\) 的长度,故此时有 \(|S|-next_{|S|}\)\(S\) 的最小周期长度(最小循环元长度)。

    • 这里可以画图理解一下。
  • 本题因要求最大周期,故需要求最小 \(Border\) 长度,需要进行跳 \(next\)。在跳 \(next\) 的过程中,记得记忆化一下。

    点击查看代码
    ll nxt[1000002],sum[1000002];
    char s[1000002];
    int main()
    {
        ll n,i,j,ans=0;
        cin>>n>>(s+1);
        for(i=2,nxt[1]=j=0;i<=n;i++)
        {
            while(j>=1&&s[i]!=s[j+1])
            {
                j=nxt[j];
            }
            j+=(s[i]==s[j+1]);
            nxt[i]=j;
            sum[i]=(j>=1&&nxt[j]==0)?j:sum[nxt[i]];
            ans+=(sum[i]==0)?0:i-sum[i];
        }
        cout<<ans<<endl;
        return 0;
    }
    

UVA10298 Power Strings

  • 前置知识:当 \((|S|-next_{|S|})||S|\) 时,此时有 \(\dfrac{|S|}{|S|-next_{|S|}}\)\(S\) 的最大循环次数;否则, \(S\) 的最大循环次数为 \(1\)

    点击查看代码
    ll nxt[1000002],sum[1000002];
    char s[1000002];
    int main()
    {
        ll n,i,j;
        while(cin>>(s+1))
        {
            n=strlen(s+1);
            if(n==1&&s[1]=='.')
            {
                break;
            }
            else
            {
                for(i=2,nxt[1]=j=0;i<=n;i++)
                {
                    while(j>=1&&s[i]!=s[j+1])
                    {
                        j=nxt[j];
                    }
                    j+=(s[i]==s[j+1]);
                    nxt[i]=j;
                }
                cout<<((n%(n-nxt[n])==0)?(n/(n-nxt[n])):1)<<endl;
            }
        }
        return 0;
    }
    

luogu P4391 [BOI2009]Radio Transmission 无线传输

  • \((|S|-next_{|S|}) \nmid |S|\) 时,将 \(|S|-next_{|S|}\) 复制 \(2\) 次即可;当 \((|S|-next_{|S|})||S|\) 时,将 \(|S|-next_{|S|}\) 复制 \(\dfrac{|S|}{|S|-next_{|S|}}\) 次即可。故 \(|S|-next_{|S|}\) 即为所求。

    点击查看代码
    ll nxt[1000002];
    char s[1000002];
    int main()
    {
        ll n,i,j;
        cin>>n>>(s+1);
        for(i=2,nxt[1]=j=0;i<=n;i++)
        {
            while(j>=1&&s[i]!=s[j+1])
            {
                j=nxt[j];
            }
            j+=(s[i]==s[j+1]);
            nxt[i]=j;
        }
        cout<<n-nxt[n]<<endl;
        return 0;
    }
    

luogu P2375 [NOI2014] 动物园

  • \(cnt_{i}(1 \le i \le |S|)\) 表示是 \(S_{1} \sim S_{i}\)\(Border\) 数量,故有 \(cnt_{i}=cnt_{next_{i}}+1\)

    • 这里可以画图理解一下。
  • 在查询 \(sum_{i}\) 时,将 \(next\) 往回跳,直至 \(2j \le i\) 从而满足不重叠条件,此时有 \(num_{i}=cnt_{j}\)

    点击查看代码
    ll nxt[1000002],num[1000002],cnt[1000002];
    char s[1000002];
    int main()
    {
        ll t,n,i,j,k,ans,p=1000000007;
        cin>>t;
        for(k=1;k<=t;k++)
        {
            cin>>(s+1);
            n=strlen(s+1);
            ans=cnt[1]=1;
            for(i=2,nxt[1]=j=0;i<=n;i++)
            {
                while(j>=1&&s[i]!=s[j+1])
                {
                    j=nxt[j];
                }
                j+=(s[i]==s[j+1]);
                nxt[i]=j;
                cnt[i]=cnt[nxt[i]]+1;
            }
            for(i=2,j=0;i<=n;i++)
            {
                while(j>=1&&s[i]!=s[j+1])
                {
                    j=nxt[j];
                }
                j+=(s[i]==s[j+1]);
                while(j*2>i)
                {	
                    j=nxt[j];
                }
                num[i]=cnt[j];
                ans=ans*(num[i]+1)%p;
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

luogu P5829【模板】失配树

  • 失配树板子。

  • \(KMP\) 的过程中,连一条从 \(next_{i}\)\(i\) 的有向边,最后就得到了一棵以 \(0\) 为根节点的树。因 \(Border\) 必须是真前缀,故最长公共前缀即失配树上的除自己之外的最近公共祖先。故当 \(LCA(p,q) \in \{ p,q\}\) 时, \(fa_{LCA(p,q)}\) 即为所求。

    • 理解:若 \(A\)\(B\)\(Border\)\(B\)\(C\)\(Border\) ,则 \(A\)\(C\)\(Border\)
    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[1000002];
    ll nxt[1000002],head[1000002],fa[1000002][25],dep[1000002],N,cnt=0;
    char s[1000002];
    void add(ll u,ll v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(ll x,ll father)
    {
        fa[x][0]=father;
        dep[x]=dep[father]+1;
        for(ll i=1;(1<<i)<=dep[x];i++)
        {
            fa[x][i]=fa[fa[x][i-1]][i-1];
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=father)
            {
                dfs(e[i].to,x);
            }
        }
    }
    ll fail_lca(ll x,ll y)
    {
        if(dep[x]>dep[y])
        {
            swap(x,y);
        }
        for(ll i=N;i>=0;i--)
        {
            if(dep[x]+(1<<i)<=dep[y])
            {
                y=fa[y][i];
            }
        }
        if(x!=y)
        {
            for(ll i=N;i>=0;i--)
            {
                if(fa[x][i]!=fa[y][i])
                {
                    x=fa[x][i];
                    y=fa[y][i];
                }
            }
        }
        return fa[x][0];//注意这里和普通LCA不同
    }
    int main()
    {
        ll n,m,l,r,i,j;
        cin>>(s+1)>>m;
        n=strlen(s+1);
        N=log2(n+1)+1;
        for(i=2,nxt[1]=j=0;i<=n;i++)
        {
            while(j>=1&&s[i]!=s[j+1])
            {
                j=nxt[j];
            }
            j+=(s[i]==s[j+1]);
            nxt[i]=j;
        }
        for(i=1;i<=n;i++)
        {
            add(nxt[i],i);
        }
        dfs(0,n+1);
        for(i=1;i<=m;i++)
        {
            cin>>l>>r;
            cout<<fail_lca(l,r)<<endl;
        }
        return 0;
    }
    

BZOJ1461 字符串的匹配 | BZOJ1892 Match

  • 类似 luogu P1972 [SDOI2009]HH的项链 和树状数组求 luogu P1908 逆序对 的过程,同样使用权值树状数组维护排名。在匹配失败或已经有 \(A\) 的子串和 \(B\) 相等时,均减去对序列的影响。

  • 注意到可能会有相等的元素,故我们需要同时判断小于等于该数和小于该数是否满足题意。

  • 因在本题中判断两个字符串相等的条件是元素排名相等,故 \(KMP\) 的相等判定也需稍作修改。

  • @Pursuing_OIer 提供了一组 \(hack\) 数据。

    点击查看数据
    input:
    4 3 5
    5
    5
    5
    5
    5
    5
    5
    
    ans:
    2
    1
    2
    
    
    点击查看代码
    ll nxt[500002],c[500002],rankd[500002],rankx[500002],ans[500002],s1[500002],s2[500002];
    ll lowbit(ll x)
    {
        return (x&(-x));
    }
    void add(ll n,ll x,ll val)
    {
        for(ll i=x;i<=n;i+=lowbit(i))
        {
            c[i]+=val;
        }
    }	
    ll getsum(ll x)
    {
        ll ans=0;
        for(ll i=x;i>=1;i-=lowbit(i))
        {
            ans+=c[i];
        }
        return ans;
    }
    int main()
    {
        ll n,m,s,i,j,k,sum=0;
        cin>>n>>m>>s;
        for(i=1;i<=n;i++)
        {
            cin>>s1[i];
        }
        for(i=1;i<=m;i++)
        {
            cin>>s2[i];
            add(s,s2[i],1);
            rankd[i]=getsum(s2[i]);
            rankx[i]=getsum(s2[i]-1);
        }
        memset(c,0,sizeof(c));
        for(i=2,nxt[1]=j=0;i<=m;i++)
        {
            add(s,s2[i],1);
            while(j>=1&&(getsum(s2[i])!=rankd[j+1]||getsum(s2[i]-1)!=rankx[j+1]))
            {
                for(k=i-j;k<=i-nxt[j]-1;k++)//这里可以画图理解一下
                {
                    add(s,s2[k],-1);//减去影响
                }
                j=nxt[j];
            }
            j+=(getsum(s2[i])==rankd[j+1]&&getsum(s2[i]-1)==rankx[j+1]);
            nxt[i]=j;
        }
        memset(c,0,sizeof(c));
        for(i=1,j=0;i<=n;i++)
        {
            add(s,s1[i],1);
            while(j>=1&&(getsum(s1[i])!=rankd[j+1]||getsum(s1[i]-1)!=rankx[j+1]))
            {
                for(k=i-j;k<=i-nxt[j]-1;k++)//这里可以画图理解一下
                {
                    add(s,s1[k],-1);//减去影响
                }
                j=nxt[j];
            }
            j+=(getsum(s1[i])==rankd[j+1]&&getsum(s1[i]-1)==rankx[j+1]);
            if(j==m)
            {
                for(k=i-j+1;k<=i-nxt[j];k++)//这里可以画图理解一下
                {
                    add(s,s1[k],-1);//减去影响
                }
                sum++;
                ans[sum]=i-j+1;
                j=nxt[m];
            }
        }
        cout<<sum<<endl;
        for(i=1;i<=sum;i++)
        {
            cout<<ans[i]<<endl;
        }
        return 0;
    }
    

luogu P3167 [CQOI2014] 通配符匹配

  • 多倍经验: luogu P2536 [AHOI2005]病毒检测
  • 非正解
    • 因通配符个数不超过 \(10\) ,考虑爆搜。

      • 因搜索顺序问题,如果正着搜会得到 \(40pts\) ,但因数据没满,故反着搜能够通过。
        • \(hack\) 数据生成器详见本博客 \(5\) 楼。
    • 记通配符个数为 \(m\) ,最坏情况下时间复杂度目测为 \(O(n|s|^{m+1})\)

      点击查看代码
      char s1[200000],s2[200000];
      bool dfs(ll sx,ll sy)
      {
          if(sx==0)
          {
              return (sy==0);
          }
          else
          {
              if(sy==0)
              {
                  for(ll i=sx;i>=1;i--)
                  {
                      if(s1[i]!='*')
                      {
                          return false;
                      }
                  }
                  return true;
              }
              else
              {
                  if(s1[sx]=='*')
                  {
                      for(ll i=sy;i>=0;i--)
                      {
                          if(dfs(sx-1,i)==true)
                          {
                              return true;
                          }
                      }
                      return false;
                  }
                  else
                  {
                      return ((s1[sx]=='?'||s1[sx]==s2[sy])?dfs(sx-1,sy-1):false);
                  }
              }
          }
      }
      int main()
      {
          ll n,lens,i;
          cin>>(s1+1)>>n;
          lens=strlen(s1+1);
          for(i=1;i<=n;i++)
          {
              cin>>(s2+1);
              if(dfs(lens,strlen(s2+1))==true)
              {
                  cout<<"YES"<<endl;
              }
              else
              {
                  cout<<"NO"<<endl;
              }
          }
          return 0;
      }
      
  • 正解
    • 为方便代码书写,在模式串和文本串最后均添加一个 ?

    • 将模式串按照通配符划分成若干段。设 \(f_{i,j}\) 表示由模式串分出的第 \(i\) 个子串是否能够匹配到文本串第 \(j\) 位。类比爆搜做法,分别对通配符进行分讨,然后进行转移。

      点击查看代码
      const ll mod=998244354,base=13331;
      ll a[1010],b[1010],jc[1010],vis[1010],f[1010][1010];
      char s1[1010],s2[1010];
      void sx_hash(char s[],ll a[],ll len)
      {
          for(ll i=0;i<=len;i++)
          {
              a[i]=(i==0)?0:((a[i-1]*base%mod+s[i])%mod);
          }
      }
      ll ask_hash(ll a[],ll l,ll r)
      {
          return (a[r]-a[l-1]*jc[r-l+1]%mod+mod)%mod;
      }
      int main()
      {
          ll n,m,t,i,j,k,l,r,len,cnt=0,ans=0;
          cin>>(s1+1)>>t;
          n=strlen(s1+1)+1;
          s1[n]='?';
          sx_hash(s1,a,n);
          for(i=1;i<=n;i++)
          {
              if(s1[i]=='*'||s1[i]=='?')
              {
                  cnt++;
                  vis[cnt]=i;
              }
          }
          for(i=0;i<=500;i++)
          {	
              jc[i]=(i==0)?1:(jc[i-1]*base%mod);
          }
          for(i=1;i<=t;i++)
          {
              cin>>(s2+1);
              memset(f,0,sizeof(f));
              f[0][0]=1;
              m=strlen(s2+1)+1;
              s2[m]='?';
              sx_hash(s2,b,m);
              for(j=0;j<=cnt;j++)
              {
                  if(j!=0&&s1[vis[j]]=='*')
                  {
                      for(k=1;k<=m;k++)
                      {
                          f[j][k]=(f[j][k-1]==1)?1:f[j][k];
                      }
                  }
                  if(j+1<=cnt)
                  {
                      len=(vis[j+1]-1)-(vis[j]+1)+1;
                      for(l=1,r=l+len-1;r<=m;l++,r++)
                      {
                          if(f[j][l-1]==1)
                          {
                              if(ask_hash(a,vis[j]+1,vis[j+1]-1)==ask_hash(b,l,r))
                              {
                                  if(s1[vis[j+1]]=='?')
                                  {
                                      f[j+1][r+1]=1;
                                  }
                                  else
                                  {
                                      f[j+1][r]=1;
                                  }
                              }
                          }
                      }
                  }
              }
              ans+=(f[cnt][m]==0);
          }
          cout<<ans<<endl;
          return 0;
      }
      

每日总结、反思

  • 合理地利用画图是好的。
  • 要学会正确地分析时间复杂度。

1.31

闲话

做题纪要

HZOJ 520. 走迷宫

luogu P4552 [Poetize6] IncDec Sequence

  • 第一问详见 2024初三年前集训测试1 T4 鸭子游戏

  • 第二问在第一问的基础上,令 \(b_{n+1}=-a_{n}\) 。在原来第二步的基础上, \(\max(p,q)-\min(p,q)\) 被分配给了 \(b_{1}\)\(b_{n+1}\) 进行操作。设 \(x\) 表示此时 \(b_{1}\) 进行操作的次数, \(y\) 表示此时 \(b_{n+1}\) 进行操作的次数,故有 \(x+y=\max(p,q)-\min(p,q)\) ,其中 \(x,y \in \mathbb{N}^{*}\) 。易知 \(x\) 共有 \(\max(p,q)-\min(p,q)+1\) 种不同的取值,即最终可得到 \(\max(p,q)-\min(p,q)+1\) 种不同的数列。

    点击查看代码
    ll a[100002],b[100002];
    int main()
    {
        ll n,p=0,q=0,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
            b[i]=a[i]-a[i-1];
        }
        for(i=2;i<=n;i++)
        {
            p+=(b[i]>0)?b[i]:0;
            q+=(b[i]<0)?-b[i]:0;
        }
        cout<<max(p,q)<<endl;
        cout<<max(p,q)-min(p,q)+1<<endl;
        return 0;
    }
    

luogu P3823 [NOI2017]蚯蚓排队

  • 观察到 \(k \le 50\) ,每次修改最多只会对前后 \(50\) 个位置内的字符串产生影响,故每次连接、分开时使用双向链表进行模拟,进行暴力更改。

  • 因要统计每个字符串出现的次数,故将字符串进行 \(hash\) 后插入哈希表中进行统计。

  • 哈希表的实现因 mapunordered_map 常数较大,故选择手写哈希表或使用黑科技 平板电视 pb_ds 中的 gp_hash_table

    • 在本题中 gp_hash_tablecc_hash_table 跑得更快一点。
  • 本题略带卡常,故使用 unsigned long long 的自然溢出来实现。

    点击查看代码
    #include<ext/pb_ds/tree_policy.hpp>
    #include<ext/pb_ds/assoc_container.hpp>
    using namespace __gnu_pbds;
    const ll mod=998244353,base=13331;
    ull a[200002],b[10000002],jc[10000002];
    int pre[200002],nxt[200002];
    char s1[200002][3],ls[100],s2[10000002];
    gp_hash_table<ull,ll>vis;
    static inline void sx_hash(char s1[],ull a[],int len)
    {
        for(int i=0;i<=len;++i)
        {
            a[i]=(i==0)?0:(a[i-1]*base+s1[i]);
        }
    }
    static inline ull ask_hash(ull a[],int l,int r)
    {
        return (a[r]-a[l-1]*jc[r-l+1]);
    }
    int main()
    {
        ll ans;
        int n,m,pd,u,v,l,r,len,i,j;
        scanf("%d%d",&n,&m);
        for(i=0;i<=10000000;++i)
        {
            jc[i]=(i==0)?1:(jc[i-1]*base);
        }
        for(i=1;i<=n;++i)
        {
            scanf("%s",s1[i]+1);
            len=strlen(s1[i]+1);
            sx_hash(s1[i],a,len);
            ++vis[ask_hash(a,1,len)];
        }
        for(i=1;i<=m;++i)
        {
            scanf("%d",&pd);
            if(pd==1)
            {
                scanf("%d%d",&u,&v);
                nxt[u]=v;
                pre[v]=u;
                l=49;
                r=50;
                memset(ls,0,sizeof(ls));
                for(j=u;j!=0&&l>=1;j=pre[j],--l)
                {
                    ls[l]=s1[j][1];
                }
                ++l;
                for(j=v;j!=0&&r<=98;j=nxt[j],++r)
                {
                    ls[r]=s1[j][1];
                }
                --r;
                sx_hash(ls,b,r);
                for(j=l;j<=49;++j)
                {
                    for(len=51-j;len<=min(50,r-j+1);++len)
                    {
                        ++vis[ask_hash(b,j,j+len-1)];
                    }
                }
            }
            if(pd==2)
            {
                scanf("%d",&u);
                v=nxt[u];
                nxt[u]=pre[v]=0;
                l=49;
                r=50;
                memset(ls,0,sizeof(ls));
                for(j=u;j!=0&&l>=1;j=pre[j],--l)
                {
                    ls[l]=s1[j][1];
                }
                ++l;
                for(j=v;j!=0&&r<=98;j=nxt[j],++r)
                {
                    ls[r]=s1[j][1];
                }
                --r;
                sx_hash(ls,b,r);
                for(j=l;j<=49;++j)
                {
                    for(len=51-j;len<=min(50,r-j+1);++len)
                    {
                        --vis[ask_hash(b,j,j+len-1)];
                    }
                }
            }
            if(pd==3)
            {
                scanf("%s%d",s2+1,&v);
                len=strlen(s2+1);
                sx_hash(s2,b,len);
                ans=1;
                for(j=v;j<=len;++j)
                {
                    ans=ans*vis[ask_hash(b,j-v+1,j)]%mod;
                }
                printf("%lld\n",ans);
            }
        }
        return 0;
    }
    

每日总结、反思

  • 常见、典型的技巧要记住,因为不知道什么时候就用到了。

2.1

闲话

  • 上午放的 \(\varphi,quick \ pow,exgcd,CRT,exCRT\) 视频。
  • \(feifei\) 称鉴于初中生到位情况良好,所以决定撤去考勤机,让我们以后自觉到位。然后考勤机就出现在了隔壁高一的机房,乐。

做题纪要

luogu P8306 【模板】字典树

  • \(Trie\) 树板子。

  • 本题选择记录 \(sum_{i}\) 表示从根节点开始到节点 \(i\) 有多少个模板串以此作为前缀。在搜索 \(T\) 时,搜索完成后直接返回 \(sum\) 即可。

  • 每组数据前记得手动清空数组。

    点击查看代码
    int trie[3000010][100],sum[3000010],tot=0;
    char s[3000010];
    int val(char x)
    {
        return x-'0';
    }
    void insert(char s[],int len)
    {
        int x=0,i;
        for(i=0;i<=len-1;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                tot++;
                trie[x][val(s[i])]=tot;
            }
            x=trie[x][val(s[i])];
            sum[x]++;
        }
    }
    int find(char s[],int len)
    {
        int x=0,i;
        for(i=0;i<=len-1;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                return 0;
            }
            x=trie[x][val(s[i])];
        }
        return sum[x];
    }
    int main()
    {
        int t,n,q,i,j,k;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>n>>q;
            for(j=0;j<=tot;j++)
            {
                sum[j]=0;
                for(k=0;k<=99;k++)
                {
                    trie[j][k]=0;
                }
            }
            tot=0;
            for(j=1;j<=n;j++)
            {
                cin>>s;
                insert(s,strlen(s));
            }
            for(j=1;j<=q;j++)
            {
                cin>>s;
                cout<<find(s,strlen(s))<<endl;
            }
        }
        return 0;
    }
    

AcWing 142. 前缀统计

  • \(Trie\) 树板子。

  • 本题选择记录 \(sum_{i}\) 表示节点 \(i\) 是多少个字符串的末尾节点。在搜索 \(T\) 时,沿途累加每个节点的 \(sum\) 即可。

    点击查看代码
    int trie[1000000][30],sum[1000000],tot=0;
    char s[1000000];
    int val(char x)
    {
        return x-'a'+1;
    }
    void insert(char s[],int len)
    {
        int x=0,i;
        for(i=1;i<=len;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                tot++;
                trie[x][val(s[i])]=tot;
            }
            x=trie[x][val(s[i])];
        }
        sum[x]++;
    }
    int find(char s[],int len)
    {
        int x=0,ans=0,i;
        for(i=1;i<=len;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                break;
            }
            x=trie[x][val(s[i])];
            ans+=sum[x];
        }
        return ans;
    }
    int main()
    {
        int n,q,i;
        cin>>n>>q;
        for(i=1;i<=n;i++)
        {
            cin>>(s+1);
            insert(s,strlen(s+1));
        }
        for(i=1;i<=q;i++)
        {
            cin>>(s+1);
            cout<<find(s,strlen(s+1))<<endl;
        }
        return 0;
    }
    

SP4033 PHONELST - Phone List

  • 多倍经验: UVA11362 Phone List

  • 如果在一开始将所有字符串全都插进一棵 \(Trie\) 树里,最后再寻找要特判找到自己的时候,或者将是否有前缀转化出现次数是否大于 \(1\)

    点击查看代码
    ll trie[120000][15],sum[120000],tot=0;
    char s[120000][15];
    int val(char x)
    {
        return x-'0';
    }
    void insert(char s[],ll len)
    {
        ll x=0,i;
        for(i=1;i<=len;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                tot++;
                trie[x][val(s[i])]=tot;
            }
            x=trie[x][val(s[i])];
            sum[x]++;
        }
    }
    ll find(char s[],ll len)
    {
        ll x=0,i;
        for(i=1;i<=len;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                return 0;
            }
            x=trie[x][val(s[i])];
        }
        return sum[x];
    }
    int main()
    {
        ll t,n,flag,i,j;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>n;
            tot=flag=0;
            memset(trie,0,sizeof(trie));
            memset(sum,0,sizeof(sum));
            for(j=1;j<=n;j++)
            {
                cin>>(s[j]+1);
                insert(s[j],strlen(s[j]+1));
            }
            for(j=1;j<=n;j++)
            {
                if(find(s[j],strlen(s[j]+1))>1)
                {
                    flag=1;
                    break;
                }
            }
            if(flag==1)
            {
                cout<<"NO"<<endl;
            }
            else
            {
                cout<<"YES"<<endl;
            }
        }
        return 0;
    }
    

luogu P2580 于是他错误的点名开始了

  • 注意要判断报的名字在名单上是否出现。

    点击查看代码
    int trie[600000][30],sum[600000],num[600000],tot;
    char s[100];
    int val(char x)
    {
        return x-'a'+1;
    }
    void insert(char s[],int len)
    {
        int x=0,i;
        for(i=1;i<=len;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                tot++;
                trie[x][val(s[i])]=tot;
            }
            x=trie[x][val(s[i])];
        }
        sum[x]++;
    }
    int find(char s[],int len)
    {
        int x=0,i;
        for(i=1;i<=len;i++)
        {
            if(trie[x][val(s[i])]==0)
            {
                return -1;
            }
            x=trie[x][val(s[i])];
        }
        if(sum[x]==0)
        {
            return -1;
        }
        else
        {
            num[x]++;
            return num[x];
        }
    }
    int main()
    {
        int n,m,ans,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>(s+1);
            insert(s,strlen(s+1));
        }
        cin>>m;
        for(i=1;i<=m;i++)
        {
            cin>>(s+1);
            ans=find(s,strlen(s+1));
            if(ans==-1)
            {
                cout<<"WRONG"<<endl;
            }
            if(ans==1)
            {
                cout<<"OK"<<endl;
            }
            if(ans>1)
            {
                cout<<"REPEAT"<<endl;
            }
        }
        return 0;
    }
    

LibreOJ 10050. 「一本通 2.3 例 2」The XOR Largest Pair

  • \(01Trie\) 树板子。把 \(a_{i}(1 \le i \le n)\) 视作一个 \(32\) 位的二进制 \(01\) 串,然后插进一棵 \(Trie\) 树里。

  • 异或性质详见 【学习笔记】数学知识-高斯消元与线性空间

  • 从贪心角度的分析,我们要尽可能走与当前位相反的字符。

    点击查看代码
    ll trie[3300000][3],a[100001],tot=0;
    void insert(ll s)
    {
        ll x=0,i;
        for(i=31;i>=0;i--)
        {
            if(trie[x][(s>>i)&1]==0)
            {
                tot++;
                trie[x][(s>>i)&1]=tot;
            }
            x=trie[x][(s>>i)&1];
        }
    }
    ll find(ll s)
    {
        ll x=0,ans=0,i;
        for(i=31;i>=0;i--)
        {
            if(trie[x][((s>>i)&1)^1]==0)
            {
                x=trie[x][(s>>i)&1];
            }
            else
            {
                x=trie[x][((s>>i)&1)^1];
                ans=ans^(1<<i);//这里等价于ans=ans|(1<<i);
            }
        }
        return ans;
    }
    int main()
    {
        ll n,ans=0,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
            insert(a[i]);
        }
        for(i=1;i<=n;i++)
        {
            ans=max(ans,find(a[i]));
        }
        cout<<ans<<endl;
        return 0;
    }
    

CF706D Vasiliy's Multiset

  • \(01Trie\) 带删除板子。

    点击查看代码
    int trie[10000000][3],sum[10000000],tot=0;
    void insert(int s)
    {
        int x=0,i;
        for(i=30;i>=0;i--)
        {
            if(trie[x][(s>>i)&1]==0)
            {
                tot++;
                trie[x][(s>>i)&1]=tot;
            }
            x=trie[x][(s>>i)&1];
            sum[x]++;
        }
    }
    void earse(int s)
    {
        int x=0,i;
        for(i=30;i>=0;i--)
        {
            x=trie[x][(s>>i)&1];
            sum[x]--;
        }
    }
    int find(int s)
    {
        int x=0,ans=0,i;
        for(i=30;i>=0;i--)
        {
            if(trie[x][((s>>i)&1)^1]==0||sum[trie[x][((s>>i)&1)^1]]==0)
            {
                x=trie[x][(s>>i)&1];
            }
            else
            {
                x=trie[x][((s>>i)&1)^1];
                ans=ans^(1<<i);
            }
        }
        return ans;
    }
    int main()
    {	
        int n,x,i;
        char pd;
        cin>>n;
        insert(0);
        for(i=1;i<=n;i++)
        {
            cin>>pd>>x;
            if(pd=='+')
            {
                insert(x);
            }
            if(pd=='-')
            {
                earse(x);
            }
            if(pd=='?')
            {
                cout<<find(x)<<endl;
            }
        }
        return 0;
    }
    

luogu P4551 最长异或路径

  • luogu P2420 让我们异或吧的结论:设 \(A_{i}\) 表示从根节点到 \(i\) 的路径上的所有边权的异或值。即 \(A_{i}=A_{fa_{i}} \bigoplus w_{fa_{i}->i}\)。由异或性质,因两点的 \(LCA\) 到根的路径异或和会被计算两次从而对答案不产生影响,故 \(A_{u} \bigoplus A_{v}\) 即为所求。

    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[200001];
    ll trie[3300000][3],head[200001],dep[200001],a[200001],tot=0,cnt=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa,ll w)
    {
        a[x]=a[fa]^w;
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x,e[i].w);
            }
        }
    }
    void insert(ll s)
    {
        ll x=0,i;
        for(i=31;i>=0;i--)
        {
            if(trie[x][(s>>i)&1]==0)
            {
                tot++;
                trie[x][(s>>i)&1]=tot;
            }
            x=trie[x][(s>>i)&1];
        }
    }
    ll find(ll s)
    {
        ll x=0,ans=0,i;
        for(i=31;i>=0;i--)
        {
            if(trie[x][((s>>i)&1)^1]==0)
            {
                x=trie[x][(s>>i)&1];
            }
            else
            {
                x=trie[x][((s>>i)&1)^1];
                ans=ans^(1<<i);
            }
        }
        return ans;
    }
    int main()
    {
        ll n,u,v,w,i,ans=0;
        cin>>n;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
        }
        dfs(1,0,0);
        for(i=1;i<=n;i++)
        {
            insert(a[i]);
        }
        for(i=1;i<=n;i++)
        {
            ans=max(ans,find(a[i]));
        }
        cout<<ans<<endl;
        return 0;
    }
    

LibreOJ 10051. 「一本通 2.3 例 3」Nikitosh 和异或

  • 考虑把原串分成 \([1,i]\)\([i+1,n]\) 两段,分别进行统计求和。

    点击查看代码
    int trie[13000000][3],a[13000000],sum1[13000000],sum2[13000000],tot=0;
    void insert(int s)
    {
        int x=0,i;
        for(i=30;i>=0;i--)
        {
            if(trie[x][(s>>i)&1]==0)
            {
                tot++;
                trie[x][(s>>i)&1]=tot;
            }
            x=trie[x][(s>>i)&1];
        }
    }
    int find(int s)
    {
        int x=0,ans=0,i;
        for(i=30;i>=0;i--)
        {
            if(trie[x][((s>>i)&1)^1]==0)
            {
                x=trie[x][(s>>i)&1];
            }
            else
            {
                x=trie[x][((s>>i)&1)^1];
                ans=ans^(1<<i);
            }
        }
        return ans;
    }
    int main()
    {	
        int n,ans=0,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        for(i=1;i<=n;i++)
        {
            insert(a[i]);
            sum1[i]=max(sum1[i-1],find(a[i]));
        }
        memset(trie,0,sizeof(trie));
        for(i=n;i>=1;i--)
        {
            insert(a[i]);
            sum2[i]=max(sum2[i+1],find(a[i]));
        }
        for(i=1;i<=n-1;i++)
        {
            ans=max(ans,sum1[i]+sum2[i-1]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P3805 【模板】manacher 算法

  • 马拉车板子。

    点击查看代码
    int r[34000000];
    string s,t=" #";
    int main()
    {
        int n,maxr=0,id=0,ans=0,i;
        cin>>s;
        for(i=0;i<s.size();i++)	
        {
            t+=s[i];
            t+="#";
        }
        n=t.size()-1;
        for(i=1;i<=n;i++)
        {
            r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):1;//一开始自己能与自己匹配
            while(1<=i-r[i]&&i+r[i]<=n&&t[i+r[i]]==t[i-r[i]])
            {
                r[i]++;
            }
            if(maxr<i+r[i])
            {
                maxr=i+r[i];
                id=i;
            }
        }
        for(i=1;i<=n;i++)
        {
            ans=max(ans,r[i]-1);
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P3501 [POI2010]ANT-Antisymmetry

  • 多倍经验: SP15569 Antisymmetry

  • 类似马拉车,同样记录最右边的“反对称”字符串右端点。

  • 因最终要除去 # 对答案的影响,故 \(\sum\limits_{i=1}^{|T|}\dfrac{r_{i}}{2}\) 即为所求。

    点击查看代码
    ll r[1600000];
    string s,t=" #";
    int main()
    {
        ll m,n,maxr=0,id=0,ans=0,i;
        cin>>m>>s;
        for(i=0;i<s.size();i++)	
        {
            t+=s[i];
            t+="#";
        }
        n=t.size()-1;
        for(i=1;i<=n;i++)
        {
            r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):0;//因为一开始可能自己不一定能与自己匹配
            while(1<=i-r[i]&&i+r[i]<=n&&((t[i+r[i]]=='#'&&t[i+r[i]]==t[i-r[i]])||(t[i+r[i]]!='#'&&t[i+r[i]]+t[i-r[i]]=='0'+'1')))
            {
                r[i]++;
            }
            if(maxr<i+r[i])
            {
                maxr=i+r[i];
                id=i;
            }
        }
        for(i=1;i<=n;i++)
        {
            ans+=r[i]/2;
        }
        cout<<ans<<endl;
        return 0;
    }
    

BZOJ3790 神奇项链

  • 发现一个回文串可以控制从左端点到右端点的部分,可以理解为区间上的一条线。然后就转化为可重叠区间线段覆盖问题了,贪心或树状数组优化 \(DP\) 维护即可。

    • 贪心策略:以左端点为第一关键字从小到大排序,以右端点为第二关键字从大到小排序。每次选择一个左端点在当前范围内,并且右端点最靠右的线段。
    点击查看代码
    struct node
    {
        int l,r;
    }a[4000000];
    int r[4000000];
    string s,t;
    bool cmp(node a,node b)
    {
        return (a.l==b.l)?(a.r>b.r):(a.l<b.l);
    }
    int main()
    {
        int n,maxr,id,maxx,ans,i,j;
        while(cin>>s)
        {
            maxr=id=ans=0;
            t=" #";
            for(i=0;i<s.size();i++)
            {
                t+=s[i];
                t+='#';
            }
            n=t.size()-1;
            for(i=1;i<=n;i++)
            {
                r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):1;
                while(1<=i-r[i]&&i+r[i]<=n&&t[i+r[i]]==t[i-r[i]])
                {
                    r[i]++;
                }
                if(maxr<i+r[i])
                {
                    maxr=i+r[i];
                    id=i;
                }
            }
            for(i=1;i<=n;i++)
            {
                a[i].l=i-r[i]+1;
                a[i].r=i+r[i]-1;
            }
            sort(a+1,a+1+n,cmp);
            i=a[1].r;
            j=2;
            while(i<=n-1)
            {
                maxx=i;
                while(a[j].l<=i&&j<=n)
                {
                    maxx=max(maxx,a[j].r);
                    j++;
                }
                i=maxx;
                ans++;
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

每日总结、反思

  • 提升做题效率,不要轻易被外界事物吸引、打扰。

2.2

闲话

  • \(miaomiao\) 上午 \(7:40 \sim 12:10\) 安排了一场模拟赛。
  • 感觉 \(VScode\) 的终端在开了自动保存后貌似有延迟。
    • 其实是 \(VScode\) 的自动保存延迟,改了之后就好多了。
  • \(miaomiao\) 终于贯彻了 \(bobo\) 的作风,明确表示假期集训期间禁止穿校服。
  • 高一、高二的放假了,机房里有些人跟着一起走了。

做题纪要

luogu P1368 【模板】最小表示法

  • 最小表示法板子。

    点击查看代码
    int s[700000];
    int main()
    {
        int n,i,l,r,len;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>s[i];
            s[n+i]=s[i];
        }
        l=1;
        r=2;
        while(l<=n&&r<=n)
        {
            len=0;
            while(len<=n-1&&s[l+len]==s[r+len])
            {
                len++;
            }
            if(len==n)
            {
                break;
            }
            else
            {
                if(s[l+len]>s[r+len])
                {
                    l+=len+1;//因为此时l+1,l+2...l+k一定不是最优解
                    l+=(l==r);
                }
                else
                {
                    r+=len+1;
                    r+=(l==r);
                }
            }
        }
        for(i=min(l,r);i<=n;i++)
        {
            cout<<s[i]<<" ";
        }
        for(i=1;i<=min(l,r)-1;i++)
        {
            cout<<s[i]<<" ";
        }
        return 0;
    }
    

luogu P2397 yyy loves Maths VI (mode)

  • 摩尔投票法板子。

    • 摩尔投票法只能找到在序列内出现次数严格大于 \(\left\lceil \dfrac{n}{2} \right\rceil\) 的众数。
    • 在不保证存在众数的情况下,在摩尔投票法结束后,需要判断下得到的数是否为众数。
  • 算法的核心思想是用不同候选人之间的票数相互抵消,并记录抵消完此时候选人是否仍然合法,若不合法,则更改下一个。

    点击查看代码
    int main()
    {
        ll n,a,i,num=0,sum=1;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a;
            if(a==num)
            {
                sum++;
            }
            else
            {
                sum--;
            }
            if(sum==0)
            {
                num=a;
                sum++;
            }
        }
        cout<<num<<endl;
        return 0;
    }
    

luogu P4555 [国家集训队]最长双回文串

  • 类似 LibreOJ 10051. 「一本通 2.3 例 3」Nikitosh 和异或 ,将原字符串分成两部分,分别进行统计求和。

  • \(left_{i}\) 表示以 \(i\) 为左端点的最长回文串长度, \(right_{i}\) 表示以 \(i\) 为右端点的最长回文串长度。

  • 在马拉车的过程中,找到回文串长度后,对该回文串内进行暴力转移,时间复杂度为 \(O(|T|^2)\)

  • 考虑在马拉车的过程中只对找到的最长回文串的左右端点进行转移。然后限制左右端点只能为 # ,得到状态转移方程 \(left_{i}=\max(left_{i},left_{i-2}+2),right_{i}=\max(right_{i},right_{i-2}+2)\)

  • 因我们实际的操作是以 # 为左右端点的,故 \(\max\limits_{i=1}^{|T|} \{ [left_{i} \ne 0] \times [right_{i} \ne 0] \times (left_{i}+right_{i}) \}\) 即为所求。

    点击查看代码
    ll r[400000],sum1[400000],sum2[400000];
    string s,t=" #";
    int main()
    {
        ll n,maxr=0,id=0,ans=0,i;
        cin>>s;
        for(i=0;i<s.size();i++)
        {
            t+=s[i];
            t+='#';
        }
        n=t.size()-1;
        for(i=1;i<=n;i++)
        {
            r[i]=(i<maxr)?min(r[id-(i-id)],maxr-i):0;
            while(1<=i-r[i]&&i+r[i]<=n&&t[i+r[i]]==t[i-r[i]])
            {
                r[i]++;
            }
            if(maxr<i+r[i])
            {
                maxr=i+r[i];
                id=i;
            }
            sum1[i+r[i]-1]=max(sum1[i+r[i]-1],r[i]-1);
            sum2[i-r[i]+1]=max(sum2[i-r[i]+1],r[i]-1);
        }
        for(i=n;i>=3;i-=2)
        {
            sum1[i]=max(sum1[i],sum1[i+2]-2);
        }
        for(i=3;i<=n;i+=2)
        {
            sum2[i]=max(sum2[i],sum2[i-2]-2);
        }
        for(i=1;i<=n;i++)
        {
            ans=(sum1[i]!=0&&sum2[i]!=0)?max(ans,sum1[i]+sum2[i]):ans;
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P3846 [TJOI2007] 可爱的质数/【模板】BSGS

  • 多倍经验: UVA10225 Discrete Logging

  • \(BSGS\) 板子。

    点击查看代码
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b>0)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;	
    }
    ll bsgs(ll a,ll b,ll p)
    {
        if(1%p==b%p)
        {
            return 0;
        }
        else
        {
            map<ll,ll>vis;
            ll k=sqrt(p)+1,i,sum;
            for(i=0;i<=k-1;i++)
            {
                vis[b*qpow(a,i,p)%p]=i;
            }
            a=qpow(a,k,p);
            for(i=0;i<=k;i++)
            {
                sum=qpow(a,i,p);
                if(vis.find(sum)!=vis.end())
                {
                    if(i*k-vis[sum]>=0)
                    {
                        return i*k-vis[sum];
                    }
                }
            }
            return -1;
        }
    }
    int main()
    {
        ll a,b,p,ans;
        cin>>p>>a>>b;
        ans=bsgs(a,b,p);
        if(ans==-1)
        {
            cout<<"no solution"<<endl;
        }
        else
        {
            cout<<ans<<endl;
        }
        return 0;
    }
    

luogu P2485 [SDOI2011]计算器

  • 同余半家桶。

  • 不保证 \(\gcd(a,p)=1\) ,记得特判。

    点击查看代码
    ll gcd(ll a,ll b)
    {
        return b?gcd(b,a%b):a;
    }
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b>0)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    ll bsgs(ll a,ll b,ll p)
    {
        if(1%p==b%p)
        {
            return 0;
        }
        else
        {
            map<ll,ll>vis;
            ll k=sqrt(p)+1,i,sum;
            for(i=0;i<=k-1;i++)
            {
                vis[b*qpow(a,i,p)%p]=i;
            }
            a=qpow(a,k,p);
            for(i=0;i<=k;i++)
            {
                sum=qpow(a,i,p);
                if(vis.find(sum)!=vis.end())
                {
                    if(i*k-vis[sum]>=0)
                    {
                        return i*k-vis[sum];
                    }
                }
            }
            return -1;
        }
    }
    int main()
    {
        ll t,k,a,b,p,ans,i;
        cin>>t>>k;
        for(i=1;i<=t;i++)
        {
            cin>>a>>b>>p;
            if(k==1)
            {
                cout<<qpow(a,b,p)<<endl;
            }
            if(k==2)
            {
                if(b%gcd(a,p)==0)
                {
                    cout<<qpow(a,p-2,p)*b%p<<endl;
                }
                else
                {
                    cout<<"Orz, I cannot find x!"<<endl;
                }
                
            }
            if(k==3)
            {
                if(a%p==0)
                {
                    if(b%p==0)
                    {
                        cout<<1<<endl;
                    }
                    else
                    {
                        cout<<"Orz, I cannot find x!"<<endl;
                    }
                }
                else
                {
                    ans=bsgs(a,b,p);
                    if(ans==-1)
                    {
                        cout<<"Orz, I cannot find x!"<<endl;
                    }
                    else
                    {
                        cout<<ans<<endl;
                    }
                }
            }
        }
        return 0;
    }
    

HZOJ 860. 上海

HZOJ 861. 华二

每日总结、反思

  • 要学会从身边各种事物中找到做题的灵感。

2.3

闲话

  • \(huge\) 也表示能不穿校服就不穿校服,
  • 高三的也放假了,中午 \(huge\) 没有通知去哪个食堂吃饭,但跟着一大群高一的去了小食堂,总共 \(4\) 个窗口,有点拥挤。看来以后去食堂得加快速度了。
  • 又被通知搬宿舍,搬到了 \(9513\) ,貌似隔壁就是宿管的宿舍。搬完宿舍后去吃饭,看见了转生奥的 @reach_the_top ,还有我在家那边乡村中学才看到的现象——不顾旁边桌子上还有人在吃饭,直接踩着桌子过去。

做题纪要

luogu P3306 [SDOI2013] 随机数生成器

  • 多倍经验: [ABC270G] Sequence in mod P | Gym103486C Random Number Generator

  • 递推式为 \(x_{n}=(ax_{n-1}+b) \bmod p\) ,发现可以统一消去 \(\bmod p\) ,只在最后参与计算。以下过程省去模运算。

  • \(x_{1}=t\) 时,则 \(n=1\) 即为所求。

  • \(a=0,x_{1} \ne t\) 时,递推式转化为 \(x_{n}=b \bmod p\) 。若 \(b=t\) ,则 \(n=2\) 即为所求;否则无解。

  • \(a \ne 0,x_{1} \ne t\) 时,设 \(x_{n}+c=a(x_{n-1}+c)\)

    • \(a=1\) 时,递推式转化为 \(\begin{aligned} x_{n}&=x_{n-1}+b \\ &=x_{n-2}+2b \\ &=x_{n-3}+3b \\ &= \dots \\ &=x_{1}+(n-1)b \end{aligned}\) ,将 \(x_{n}=t\) 代入得 \(t=(x_{1}+(n-1)b) \bmod p\) ,移项得 \((n-1)b \equiv (t-x_{1}) \pmod {p}\) 。当 \(\gcd(b,p)|(t-x_{1})\) 时,移项得 \(n-1 \equiv \dfrac{t-x_{1}}{b} \pmod {p}\) ,解得 \(n \equiv \dfrac{t-x_{1}}{b}+1 \pmod {p}\) ;否则无解。
    • \(a \ne 1\) 时,解得 \(c=\dfrac{b}{a-1}\) 并和 \(x_{n}=t\) 一起代入原式得 \(\begin{aligned} t+\dfrac{b}{a-1}&=a(x_{n-1}+\dfrac{b}{a-1}) \\ &=a^{2}(x_{n-2}+\dfrac{b}{a-1}) \\ &=a^{3}(x_{n-3}+\dfrac{b}{a-1}) \\ &= \dots \\ &=a^{n-1}(x_{1}+\dfrac{b}{a-1}) \end{aligned}\) 。当 \(\gcd(x_{1}+\dfrac{b}{a-1},p)|(t+\dfrac{b}{a-1})\) 时,移项得 \(a^{n-1} \equiv \dfrac{t+\dfrac{b}{a-1}}{x_{1}+\dfrac{b}{a-1}} \pmod {p}\) ,跑遍 \(BSGS\) 求解即可;否则无解。
    点击查看代码
    ll qpow(ll a,ll b,ll p)
    {
        ll ans=1;
        while(b>0)
        {
            if(b&1)
            {
                ans=ans*a%p;
            }
            b>>=1;
            a=a*a%p;
        }
        return ans;
    }
    ll gcd(ll a,ll b)
    {
        return b?gcd(b,a%b):a;
    }
    ll bsgs(ll a,ll b,ll p)
    {
        if(1%p==b%p)
        {
            return 0;
        }
        else
        {
            map<ll,ll>vis;
            ll k=sqrt(p)+1,i,sum;
            for(i=0;i<=k-1;i++)
            {
                vis[b*qpow(a,i,p)%p]=i;
            }
            a=qpow(a,k,p);
            for(i=0;i<=k;i++)
            {
                sum=qpow(a,i,p);
                if(vis.find(sum)!=vis.end())
                {
                    if(i*k-vis[sum]>=0)
                    {
                        return i*k-vis[sum];
                    }
                }
            }
            return -1;
        }
    }
    int main()
    {
        ll t,p,a,b,x1,day,ans,sum,i;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>p>>a>>b>>x1>>day;
            if(x1==day)
            {
                cout<<1<<endl;
            }
            else
            {
                if(a==0)
                {
                    cout<<((b==day)?2:-1)<<endl;
                }
                else
                {
                    if(a==1)
                    {
                        if((day-x1)%gcd(b,p)==0)
                        {
                            cout<<qpow(b,p-2,p)*(day-x1+p)%p+1<<endl;
                        }
                        else
                        {
                            cout<<-1<<endl;
                        }
                    }
                    else
                    {
                        sum=qpow(a-1,p-2,p)*b%p;;
                        if((day+sum)%gcd(x1+sum,p)==0)
                        {
                            ans=bsgs(a,((day+sum)%p)*qpow(x1+sum,p-2,p)%p,p);
                            cout<<((ans==-1)?ans:ans+1)<<endl;
                        }
                        else
                        {
                            cout<<-1<<endl;
                        }
                    }
                }
            }
        }
        return 0;
    }
    

luogu P3478 [POI2008]STA-Station

  • 多倍经验: luogu P1395 会议 | [ABC220F] Distance Sums 2

  • 钦定 \(1\) 为根节点。

  • 第一遍 \(DFS\) 时,设 \(f_{x}\) 表示以 \(x\) 为根的子树内的所有节点到 \(x\) 的距离之和,即 \(f_{x}=\sum\limits_{y \in Son(x)}(f_{y}+size_{y})\)

  • 第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,所有节点到 \(x\) 的距离之和,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,以 \(x\) 为根的子树内的 \(size_{x}\) 个节点对答案的贡献会减少 \(1\) ,以 \(fa_{x}\) 为根节点的树内除了以 \(x\) 为根的子树内的 \(n-size_{x}\) 个节点对答案的贡献会增加 \(1\) ,状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ g_{fa_{x}}+n-size_{x}-size_{x} & x \ne 1 \end{cases}\)

    点击查看代码
    struct node
    {
        ll nxt,to;
    }e[2000001];
    ll head[2000001],siz[2000001],f[2000001],g[2000001],cnt=0;
    void add(ll u,ll v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
        siz[x]=1;
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                f[x]+=f[e[i].to]+siz[e[i].to];
                siz[x]+=siz[e[i].to];
            }
        }
    }
    void reroot(ll x,ll fa,ll n)
    {
        if(x!=1)
        {
            g[x]=g[fa]+n-siz[x]-siz[x];
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x,n);
            }
        }
    }
    int main()
    {
        ll n,u,v,maxx=0,id=0,i;
        cin>>n;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        dfs(1,0);
        g[1]=f[1];
        reroot(1,0,n);
        for(i=1;i<=n;i++)
        {
            if(g[i]>maxx)
            {
                maxx=g[i];
                id=i;
            }
        }
        cout<<id<<endl;
        return 0;
    }
    

CF1187E Tree Painting

  • 发现在将第一个白色变成黑点后,其他将白色变为黑色的顺序并不会影响答案。每次染色,实际得到的贡献为以该节点为根的子树大小。

  • 钦定 \(1\) 为根节点。

  • 第一遍 \(DFS\) 时,设 \(f_{x}\) 表示先将 \(x\) 变成黑色后,以 \(x\) 为根的子树内的所有节点对 \(x\) 的贡献之和,即 \(f_{x}=1+\sum\limits_{y \in Son(x)}(f_{y}+size_{y})\)

  • 第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,先将 \(x\) 变成黑色后,以 \(x\) 为根的子树内的所有节点对 \(x\) 的贡献之和,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,以 \(x\) 为根的子树内的 \(size_{x}\) 个节点对答案的贡献会减少 \(1\) ,以 \(fa_{x}\) 为根节点的树内除了以 \(x\) 为根的子树内的 \(n-size_{x}\) 个节点对答案的贡献会增加 \(1\) ,状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ g_{fa_{x}}+n-size_{x}-size_{x} & x \ne 1 \end{cases}\)

    点击查看代码
    struct node
    {
        ll nxt,to;
    }e[400001];
    ll head[400001],siz[400001],f[400001],g[400001],cnt=0;
    void add(ll u,ll v)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
        siz[x]=1;
        f[x]=1;
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                f[x]+=f[e[i].to]+siz[e[i].to];
                siz[x]+=siz[e[i].to];
            }
        }
    }	
    void reroot(ll x,ll fa,ll n)
    {
        if(x!=1)
        {
            g[x]=g[fa]+n-siz[x]-siz[x];
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x,n);
            }
        }
    }
    int main()
    {
        ll n,u,v,ans=0,i;
        cin>>n;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v;
            add(u,v);
            add(v,u);
        }
        dfs(1,0);
        g[1]=f[1];
        reroot(1,0,n);
        for(i=1;i<=n;i++)
        {
            ans=max(ans,g[i]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P2986 [USACO10MAR] Great Cow Gathering G

  • 多倍经验: CF1092F Tree with Maximum Cost

  • 发现有了点权和边权,但没有太大变化。把点权拆成此处有几个点,计入 \(size\) 计算中。

  • 钦定 \(1\) 为根节点。

  • 第一遍 \(DFS\) 时,设 \(f_{x}\) 表示以 \(x\) 为根的子树内的所有节点到 \(x\) 的距离之和,即 \(f_{x}=\sum\limits_{y \in Son(x)}(f_{y}+size_{y} \times w_{x,y})\)

  • 第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,所有节点到 \(x\) 的距离之和,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,以 \(x\) 为根的子树内的 \(size_{x}\) 个节点对答案的贡献会减少 \(w_{fa,x}\) ,以 \(fa_{x}\) 为根节点的树内除了以 \(x\) 为根的子树内的 \(n-size_{x}\) 个节点对答案的贡献会增加 \(w_{fa,x}\) ,状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ g_{fa_{x}}+(n-size_{x}) \times w_{fa_{x},x}-size_{x} \times w_{fa_{x},x}& x \ne 1 \end{cases}\)

    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[200001];
    ll head[200001],siz[200001],f[200001],g[200001],c[200001],cnt=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
        siz[x]=c[x];
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {	
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                f[x]+=f[e[i].to]+siz[e[i].to]*e[i].w;
                siz[x]+=siz[e[i].to];
            }
        }
    }
    void reroot(ll x,ll fa,ll n,ll w)
    {
        if(x!=1)
        {
            g[x]=g[fa]+(n-siz[x])*w-siz[x]*w;
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x,n,e[i].w);
            }
        }
    }
    int main()
    {
        ll n,u,v,w,sum=0,minn=0x7f7f7f7f7f7f7f7f,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>c[i];
            sum+=c[i];
        }
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
        }
        dfs(1,0);
        g[1]=f[1];
        reroot(1,0,sum,0);
        for(i=1;i<=n;i++)
        {
            minn=min(minn,g[i]);
        }
        cout<<minn<<endl;
        return 0;
    }
    

POJ3585 Accumulation Degree

  • 钦定 \(1\) 为根节点。

  • 第一遍 \(DFS\) 时,设 \(f_{x}\) 表示以 \(x\) 为根的子树内,当 \(x\) 作为源点,从 \(x\) 出发流向子树的流量的总和的最大值,即 \(f_{x}=\sum\limits_{y \in Son(x)}\begin{cases} c_{x,y} & din_{y}=1 \\ \min(f_{y},c_{x,y}) & din_{y} \ne 1 \end{cases}\)

  • 第二遍 \(DFS\) 时,进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,当 \(x\) 作为源点,从 \(x\) 出发流向整个水系的流量的总和的最大值。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,原从 \(fa_{x}\) 流向 \(x\) 的流量为 \(\begin{cases} \min(f_{x},c_{fa_{x},x}) & din_{x} \ne 1 \\ c_{fa_{x},x} & din_{x}=1 \end{cases}\) ,流向除以 \(x\) 为根节点的子树外的其他子树的流量为 \(g_{fa_{x}}-\begin{cases} \min(f_{x},c_{fa_{x},x}) & din_{x} \ne 1 \\ c_{fa_{x},x} & din_{x}=1 \end{cases}\) ;先从 \(x\) 流向 \(fa_{x}\) 再流向除以 \(x\) 为根节点的子树外的其他子树的流量为 \(\begin{cases} \min(c_{x,fa_{x}},g_{fa_{x}}-\begin{cases} \min(f_{x},c_{fa_{x},x}) & din_{x} \ne 1 \\ c_{fa_{x},x} & din_{x}=1 \end{cases}) & din_{fa_{x}} \ne 1 \\ c_{x,fa_{x}} & din_{fa_{x}=1} \end{cases}\) 。状态转移方程为 \(g_{x}=f_{x}+\begin{cases} c_{x,fa_{x}} & din_{fa_{x}}=1 \\ \min(c_{x,fa_{x}},g_{fa_{x}}-\min(f_{x},c_{fa_{x},x})) & din_{fa_{x}} \ne 1,din_{x} \ne 1 \\ \min(c_{x,fa_{x}},g_{fa_{x}}-c_{fa_{x},x}) & din_{fa_{x}} \ne 1,din_{x} =1 \end{cases}\)

    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[400001];
    ll head[400001],f[400001],g[400001],din[400001],cnt=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                f[x]+=(din[e[i].to]==1)?e[i].w:min(f[e[i].to],e[i].w);
            }
        }
    }
    void reroot(ll x,ll fa,ll w)
    {
        if(x!=1)
        {
            g[x]=f[x]+((din[fa]==1)?w:min(g[fa]-((din[x]==1)?w:min(f[x],w)),w));
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x,e[i].w);
            }
        }
    }
    int main()
    {
        ll t,n,u,v,w,maxx,i,j;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cnt=maxx=0;
            memset(e,0,sizeof(e));
            memset(head,0,sizeof(head));
            memset(f,0,sizeof(f));
            memset(g,0,sizeof(g));
            memset(din,0,sizeof(din));
            cin>>n;
            for(j=1;j<=n-1;j++)
            {
                cin>>u>>v>>w;
                add(u,v,w);
                add(v,u,w);
                din[u]++;
                din[v]++;
            }
            dfs(1,0);
            g[1]=f[1];
            reroot(1,0,0);
            for(j=1;j<=n;j++)
            {
                maxx=max(maxx,g[j]);
            }
            cout<<maxx<<endl;
        }
        return 0;
    }
    

HDU2196 Computer

  • 钦定 \(1\) 为根节点。

  • \(f_{x},cmax_{x},g_{x}\) 分别表示节点 \(x\) 到以 \(x\) 为根的子树内的叶子节点的最长距离、次长距离和节点 \(x\) 到除以 \(x\) 为根的子树内的叶子节点的最长距离。

  • 第一遍 \(DFS\) 时,处理出 \(f_{x},cmax_{x}\) ,并记录到以 \(x\) 为根的子树内的叶子节点的最长距离时的路径上的叶子节点 \(path\)

  • 第二遍 \(DFS\) 时,有 \(g_{x}=\begin{cases} 0 & x=1 \\ w_{fa_{x},x}+\max(cmax_{fa_{x}},g_{fa_{x}}) & x=path_{fa_{x}} \\ w_{fa_{x},x}+\max(f_{fa_{x}},g_{fa_{x}}) & x \ne 1,x \ne path_{fa_{x}} \end{cases}\)

    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[20001];
    ll head[20001],f[20001],g[20001],cmax[20001],path[20001],cnt=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;	
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                if(f[e[i].to]+e[i].w>f[x])
                {
                    cmax[x]=f[x];
                    f[x]=f[e[i].to]+e[i].w;
                    path[x]=e[i].to;
                }
                else
                {
                    cmax[x]=max(cmax[x],f[e[i].to]+e[i].w);
                }
            }
        }
    }
    void reroot(ll x,ll fa,ll w)
    {
        if(x!=1)
        {
            g[x]=w+((x==path[fa])?max(cmax[fa],g[fa]):max(f[fa],g[fa]));
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x,e[i].w);
            }
        }
    }
    int main()
    {
        ll n,u,v,w,i;
        while(cin>>n)
        {
            cnt=0;
            memset(e,0,sizeof(e));
            memset(head,0,sizeof(head));
            memset(f,0,sizeof(f));
            memset(g,0,sizeof(g));
            memset(cmax,0,sizeof(cmax));
            memset(path,0,sizeof(path));
            for(i=2;i<=n;i++)
            {
                u=i;
                cin>>v>>w;
                add(u,v,w);
                add(v,u,w);
            }
            dfs(1,0);
            g[1]=0;
            reroot(1,0,0);
            for(i=1;i<=n;i++)
            {
                cout<<max(f[i],g[i])<<endl;
            }
        }
        return 0;
    }
    

每日总结、反思

  • 对于一些看似乱搞做法,要理解其与正解之间的本质关系。
  • 全面分析问题,分讨不要少情况。

2.4

闲话

  • 中午因跟着高二的提前 \(3min\) 去吃饭,下午就被 \(huge\) \(D\) 了。

做题纪要

luogu P6419 [COCI2014-2015#1] Kamp

  • 钦定 \(1\) 为根节点。

  • \(f_{x},zmax_{x},cmax_{x},g_{x}\) 分别表示从节点 \(x\) 出发把以 \(x\) 为根节点的子树内的所有人送回去并回到 \(x\) 的最少时间,节点 \(x\) 到以 \(x\) 为根的子树内的叶子节点的最长距离、次长距离,当 \(x\) 为整棵树的根节点时从节点 \(x\) 出发把以 \(x\) 为根节点的子树内的所有人送回去并回到 \(x\) 的最少时间。

  • 第一遍 \(DFS\) 时,处理出 \(f_{x},zmax_{x},cmax_{x}\) 。具体的,有 \(f_{x}=\sum\limits_{y \in Son(x)}[size_{y} \ge 1] \times (f_{y}+2w_{x,y})\)

  • 第二遍 \(DFS\) 时,进行大分讨和进行换根。设 \(g_{x}\) 表示以 \(x\) 为整棵树的根节点时,从节点 \(x\) 出发把以 \(x\) 为根节点的子树内的所有人送回去并回到 \(x\) 的最少时间,仍钦定 \(1\) 为根节点。考虑根节点由 \(fa_{x}\) 变为 \(x\) 的过程中,当 \(size_{x}=k\) 时有 \(g_{x}=f_{x}\) ;当 \(size_{x}=0\) 时有 \(g_{x}=g_{fa_{x}}+2w_{x,fa_{x}}\) ;否则,从 \(fa_{x}\) 出发把除以 \(x\) 为根节点的子树外的所有人送回去并回到 \(fa_{x}\) 的最少时间为 \(g_{fa_{x}}-f_{x}-2w_{fa_{x},x}\) ,再从 \(fa_{x}\) 到达 \(x\) 需要再消耗 \(2w_{x,fa_{x}}\) 的时间。故状态转移方程为 \(g_{x}=\begin{cases} f_{x} & x=1 \\ f_{x} & x \ne 1,size_{x}=k \\ g_{fa_{x}}+2w_{x,fa_{x}} & x \ne 1,size_{x}=0 \\ g_{fa_{x}}-f_{x}-2w_{fa_{x},x}+2w_{x,fa_{x}}+f_{x} & x \ne 1,size_{x} \ne 0,size_{x} \ne k \end{cases}\) 。同时记录当 \(x\) 为整棵树的根节点时,节点 \(x\) 到以 \(x\) 为根的子树内的叶子节点的最长距离 \(zmaxx_{x}\)

  • 从贪心的角度来看,最长距离我们只走一遍,故 \(g_{i}-\max(zmax_{i},zmaxx_{i})\) 即为所求。

    点击查看代码
    struct node
    {
        ll nxt,to,w;
    }e[1000001];
    ll head[1000001],siz[1000001],f[1000001],g[1000001],zmax[1000001],zmaxx[1000001],cmax[1000001],sum[1000001],cnt=0;
    void add(ll u,ll v,ll w)
    {
        cnt++;
        e[cnt].nxt=head[u];
        e[cnt].to=v;
        e[cnt].w=w;
        head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
        siz[x]=(sum[x]==1);
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                dfs(e[i].to,x);
                siz[x]+=siz[e[i].to];
                if(siz[e[i].to]!=0)
                {
                    f[x]+=f[e[i].to]+2*e[i].w;
                    if(zmax[e[i].to]+e[i].w>zmax[x])
                    {
                        cmax[x]=zmax[x];
                        zmax[x]=zmax[e[i].to]+e[i].w;
                    }
                    else
                    {
                        cmax[x]=max(cmax[x],zmax[e[i].to]+e[i].w);
                    }
                }
            }
        }
    }
    void reroot(ll x,ll fa,ll m,ll w)
    {
        if(x!=1)
        {
            if(siz[x]==m)
            {
                g[x]=f[x];
                zmaxx[x]=zmax[x];
            }
            else
            {
                g[x]=(siz[x]==0)?g[fa]+2*w:g[fa]-f[x]-2*w+2*w+f[x];
                zmaxx[x]=w+((zmax[x]+w==zmax[fa])?max(zmaxx[fa],cmax[fa]):max(zmaxx[fa],zmax[fa]));
            }	
        }
        for(ll i=head[x];i!=0;i=e[i].nxt)
        {
            if(e[i].to!=fa)
            {
                reroot(e[i].to,x,m,e[i].w);
            }
        }
    }
    int main()
    {
        ll n,m,u,v,w,a,i;
        cin>>n>>m;
        for(i=1;i<=n-1;i++)
        {
            cin>>u>>v>>w;
            add(u,v,w);
            add(v,u,w);
        }
        for(i=1;i<=m;i++)
        {
            cin>>a;
            sum[a]=1;
        }
        dfs(1,0);
        g[1]=f[1];
        reroot(1,0,m,0);
        for(i=1;i<=n;i++)
        {
            cout<<g[i]-max(zmax[i],zmaxx[i])<<endl;
        }
        return 0;
    }
    

AcWing 104. 货仓选址

  • 简化题意:给定长度为 \(n\) 的序列 \(\{ a \}\) ,求 \(\min \{ \sum\limits_{i=1}^{n}|a_{i}-x| \}\)

  • \(a_{1} \sim a_{n}\) 进行排序。设比 \(x\) 小的有 \(p\) 个,比 \(x\) 大的有 \(q\) 个。若 \(p<q\) ,则 \(x\) 每增加 \(1\)\(a_{1} \sim a_{p}\) 对答案的贡献会增加 \(1\)\(a_{n-q+1} \sim a_{n}\) 对答案的贡献会减少 \(1\) ,总贡献会减少 \(q-p\) ;若 \(p>q\) ,则 \(x\) 每减少 \(1\)\(a_{1} \sim a_{p}\) 对答案的贡献会减少 \(1\)\(a_{n-q+1} \sim a_{n}\) 对答案的贡献会增加 \(1\) ,总贡献会减少 \(p-q\) 。最终当 \(p=q\) 时取到最小值,即 \(x\)\(\{ a \}\) 的中位数(非严格意义)时取到最小值。具体的,将 \(a_{1} \sim a_{n}\) 进行排序后,若 \(n\) 为奇数,则 \(x=a_{\left\lceil \frac{n}{2} \right\rceil}=a_{\frac{1+n}{2}}\) 时取到最小值;若 \(n\) 为偶数,有 \(\left\lfloor \dfrac{1+n}{2} \right\rfloor=\dfrac{n}{2}\) ,则 \(x \in [a_{\frac{n}{2}},a_{\frac{n}{2}+1}]\) 时取到最小值。

    点击查看代码
    ll a[100001];
    int main()
    {
        ll n,ans=0,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        sort(a+1,a+1+n);
        for(i=1;i<=n;i++)
        {
            ans+=abs(a[i]-a[(ll)ceil(1.0*n/2)]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

CF523D Statistics of Recompressing Videos

  • priority_queue 大法好。

    点击查看代码
    priority_queue<ll>q;
    int main()
    {
        ll n,k,s,m,i,ans;
        scanf("%lld%lld",&n,&k);
        for(i=1;i<=n;i++)
        {
            scanf("%lld%lld",&s,&m);
            ans=s+m;
            if(q.empty()==0)
            {
                if(q.size()<=k-1)
                {
                    q.push(-ans);
                }
                else
                {
                    ans=(s>=-q.top())?ans:-q.top()+m;
                    q.pop();
                    q.push(-ans);
                }
            }
            else
            {
                q.push(-ans);
            }
            printf("%lld\n",ans);
        }
        return 0;
    }
    

luogu P4677 山区建小学

  • 将每两个村镇之间的距离 \(d\) 转化成到第一个城镇的距离 \(a\)

  • \(f_{i,j},g_{i,j}\) 表示分别表示 \(1 \sim i\) 个村镇中建了 \(j\) 个小学的最小距离总和, \(i \sim j\) 个村镇中建了 \(1\) 个小学的最小距离总和。

  • \(\{ a \}\) 此时已经是有序的了,故有 \(g_{i,j}=\sum\limits_{k=i}^{j}|a_{k}-a_{\left\lfloor \frac{i+j}{2} \right\rfloor}|,f_{i,j}=\min\limits_{k=j-1}^{i-1} \{ f_{k,j}+g_{k+1,i}\}\)

  • \(f\) 数组除 \(f_{0,0}=0\) 外初始化为 \(\infty\) 。最终 \(f_{n,m}\) 即为所求。

    点击查看代码
    int a[501],f[501][501],g[501][501];
    int main()
    {
        int n,m,i,j,k;
        cin>>n>>m;
        memset(f,0x3f,sizeof(f));
        for(i=2;i<=n;i++)
        {
            cin>>a[i];
            a[i]+=a[i-1];
        }		
        for(i=1;i<=n;i++)
        {
            for(j=i;j<=n;j++)
            {
                for(k=i;k<=j;k++)
                {
                    g[i][j]+=abs(a[k]-a[(i+j)/2]);
                }
            }
        }
        f[0][0]=0;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=min(i,m);j++)
            {
                for(k=j-1;k<=i-1;k++)
                {
                    f[i][j]=min(f[i][j],f[k][j-1]+g[k+1][i]);
                }
            }
        }
        cout<<f[n][m]<<endl;
        return 0;
    }
    

POJ1723 SOLDIERS

  • \(y\) 的处理和普通货仓选址一样。

  • \(x\) 排序后,为方便处理,将第 \(i\) 个士兵放在第 \(i\) 个位置。设起点为 \(xx\) ,等价于求 \(\min \{ \sum\limits_{i=1}^{n}|x_{i}-(xx+i-1)| \}=\min \{ \sum\limits_{i=1}^{n}|x_{i}-i+1-xx| \}\) ,然后处理就和普通货仓选址一样了。

    点击查看代码
    int x[10001],y[10001];
    int main()
    {
        int n,ans=0,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>x[i]>>y[i];
        }
        sort(y+1,y+1+n);
        for(i=1;i<=n;i++)
        {
            ans+=abs(y[i]-y[(ll)ceil(1.0*n/2)]);
        }
        sort(x+1,x+1+n);
        for(i=1;i<=n;i++)
        {
            x[i]+=-i+1;
        }
        sort(x+1,x+1+n);
        for(i=1;i<=n;i++)
        {
            ans+=abs(x[i]-x[(ll)ceil(1.0*n/2)]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

HZOJ 862. 高爸

LibreOJ 100. 矩阵乘法

  • 多倍经验: luogu B2105 矩阵乘法 | luogu B3615 测测你的矩阵乘法 | LibreOJ 10219. 「一本通 6.5 例 1」矩阵 A×B

  • 按题意模拟。

    点击查看代码
    struct Matrix
    {
        ll ma[501][501];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }a,b,c;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+((a.ma[i][h]+p)%p)*((b.ma[h][j]+p)%p)%p)%p;
                }
            }
        }
        return c;
    }
    int main()
    {
        ll n,m,k,p=1e9+7,i,j;
        cin>>n>>m>>k;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                cin>>a.ma[i][j];
            }
        }
        for(i=1;i<=m;i++)
        {
            for(j=1;j<=k;j++)
            {
                cin>>b.ma[i][j];
            }
        }
        c=mul(a,b,n,m,k,p);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=k;j++)
            {
                cout<<c.ma[i][j]<<" ";
            }
            cout<<endl;
        }
        return 0;
    }
    

luogu P3390 【模板】矩阵快速幂

  • 矩阵快速幂板子。

  • 记得定义单位矩阵 \(I= \begin{bmatrix} 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 1 \end{bmatrix}\)

    点击查看代码
    struct Matrix
    {
        ll ma[101][101];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }a,c;
    inline Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    inline Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }   
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll n,b,p=1000000007,i,j;
        cin>>n>>b;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                cin>>a.ma[i][j];
            }
        }
        c=qpow(a,b,p,n);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                cout<<c.ma[i][j]<<" ";
            }
            cout<<endl;
        }
        return 0;
    }
    

luogu P1962 斐波那契数列

  • 多倍经验: LibreOJ 10220. 「一本通 6.5 例 2」Fibonacci 第 n 项 | LibreOJ 10223. 「一本通 6.5 练习 1」Fibonacci

  • \(F_{n}= \begin{bmatrix} Fib_{n} & Fib_{n+1}\end{bmatrix}\) ,容易有 \(\begin{aligned}F_{n} &= \begin{bmatrix} Fib_{n} & Fib_{n+1}\end{bmatrix} \\ &= \begin{bmatrix} Fib_{n-1} & Fib_{n}\end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1\end{bmatrix} \\ &= \begin{bmatrix} Fib_{n-2} & Fib_{n-1}\end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1\end{bmatrix}^{2} \\ &= \begin{bmatrix} Fib_{n-3} & Fib_{n-2}\end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1\end{bmatrix}^{3} \\ &= \dots \\ &= \begin{bmatrix} Fib_{0} & Fib_{1}\end{bmatrix} \times \begin{bmatrix} 0 &1 \\ 1 & 1\end{bmatrix}^{n} \\ &= F_{0} \times \begin{bmatrix} 0 &1 \\ 1 & 1\end{bmatrix}^{n} \end{aligned}\)

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll b,n=1,m=2,k=2,p=1000000007;
        cin>>b;
        f.ma[1][1]=0;
        f.ma[1][2]=1;
        a.ma[1][1]=0;
        a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
        cout<<mul(f,qpow(a,b,p,m),n,m,k,p).ma[1][1]<<endl;
        return 0;
    }
    

LibreOJ 10221. 「一本通 6.5 例 3」Fibonacci 前 n 项和

  • \(Fib_{1}=Fib_{3}-Fib_{2},Fib_{2}=Fib_{4}-Fib_{3},Fib_{3}=Fib_{5}-Fib_{4}, \dots ,Fib_{n}=Fib_{n+2}-Fib_{n+1}\) ,有 \(\begin{aligned} S_{n} &=\sum\limits_{i=1}^{n}Fib_{i} \\ &= \sum\limits_{i=1}^{n}Fib_{i+2}-Fib_{i+1} \\ &=\sum\limits_{i=1}^{n}Fib_{i+2}-\sum\limits_{i=1}^{n}Fib_{i+1} \\ &=\sum\limits_{i=3}^{n+2}Fib_{i}-\sum\limits_{i=2}^{n+1}Fib_{i} \\ &=Fib_{n+2}-Fib_{2} \\ &=Fib_{n+2}-1\end{aligned}\)

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll b,p,n=1,m=2,k=2;
        cin>>b>>p;
        f.ma[1][1]=0;
        f.ma[1][2]=1;
        a.ma[1][1]=0;
        a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
        cout<<(mul(f,qpow(a,b+2,p,m),n,m,k,p).ma[1][1]-1+p)%p<<endl;
        return 0;
    }
    

每日总结、反思

  • scanf,printf 时一定要看清类型名,不要出现 scanf("%dll",&...) 或一个 long long%ld 来读的现象,会出离奇问题。
  • 编译指令记得开全部警告。
  • 当输出变得离奇(包括但不限于每隔一段时间换一个答案)时,要记得回去看初始化。

2.5

闲话

  • \(miaomiao\) 上午 \(7:50 \sim 12:10\) 安排了一场模拟赛,这次把网关了。
  • 不知道为什么 \(1\) 机房又把 Acwing 封了。
  • 中午吃饭的时候,又一次看见了 @reach_the_top ,和他搭了几句话。

做题纪要

LibreOJ 10222. 「一本通 6.5 例 4」佳佳的 Fibonacci

  • 推式子。

  • \(\begin{aligned} T_{n} &= \sum\limits_{i=1}^{n}i \times Fib_{i} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}Fib_{j} \\ &=\sum\limits_{i=1}^{n}(S_{n}-S_{i-1}) \\ &=nS_{n}-\sum\limits_{i=1}^{n}S_{i-1} \\ &=n(Fib_{n+2}-1)-\sum\limits_{i=1}^{n}(Fib_{i+1}-1) \\ &=n \times Fib_{n+2}-\sum\limits_{i=2}^{n+1}Fib_{i} \\ &=n \times Fib_{n+2}-(S_{n}-S_{1}) \\ &=n \times Fib_{n+2}-S_{n+1}+Fib_{1} \\ &=n \times Fib_{n+2}-(Fib_{n+3}-1)+1 \\ &=n \times Fib_{n+2}-Fib_{n+3}+2 \end{aligned}\)

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll b,p,n=1,m=2,k=2,sum1,sum2;
        cin>>b>>p;
        f.ma[1][1]=0;
        f.ma[1][2]=1;
        a.ma[1][1]=0;
        a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
        sum1=mul(f,qpow(a,b+2,p,m),n,m,k,p).ma[1][1]*b%p;
        f.ma[1][1]=0;
        f.ma[1][2]=1;
        a.ma[1][1]=0;
        a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
        sum2=mul(f,qpow(a,b+3,p,m),n,m,k,p).ma[1][1];
        cout<<(sum1-sum2+2+p)%p;
        return 0;
    }
    

HDU1575 Tr A

  • 矩阵快速幂板子。

  • 额外知识点

    • 主对角线:在 \(n\) 阶行列式中从左上角到右下角的对角线。
    • 迹:在 \(n\) 阶行列式中主对角线上元素之和。
    • 次对角线:在 \(n\) 阶行列式中从右上角到左下角的对角线。
    点击查看代码
    struct Matrix
    {
        ll ma[15][15];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }a,c;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll t,n,b,ans,i,j,k,p=9973;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>n>>b;  
            ans=0;
            for(j=1;j<=n;j++)
            {
                for(k=1;k<=n;k++)
                {
                    cin>>a.ma[j][k];
                }
            }
            c=qpow(a,b,p,n);
            for(j=1;j<=n;j++)
            {
                ans=(ans+c.ma[j][j]%p)%p;
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

tgHZOJ 264. 选拔队员

  • 设有 \(i(0 \le i \le n)\) 个女生, \(n-i\) 个男生。

  • \(n-i\) 个男生中,有 \(i-1\) 个男生需要各放在每两个女生之间,剩余 \(n-i-(i-1)\) 个男生可以任意放,此时等价于求 \(\sum\limits_{j=1}^{i+1}x_{j}=n-i-(i-1)\) 的非负整数解数量 \(\dbinom{n-i-(i-1)+i+1-1}{i+1-1}=\dbinom{n-i+1}{i}\)

  • 由打表,有 \(\begin{aligned} \sum\limits_{i=0}^{n}\dbinom{n-i+1}{i} &=Fib_{n+2} \end{aligned}\) 即为所求。

    • 证明
      • 数学归纳法。
      • \(n=1,n=2\) 时,有命题成立。
      • 假设当 \(n=m-1,m(2 \le m)\) 时命题成立。当 \(n=m+1\) 时,有 \(\\ \begin{aligned} \sum\limits_{i=0}^{m+1}\dbinom{m+1-i+1}{i} &=\sum\limits_{i=0}^{m+1}\dbinom{m-i+1}{i-1}+\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=1}^{m+1}\dbinom{m-i+1}{i-1}+\sum\limits_{i=0}^{m+1}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m+1}\dbinom{m-i}{i}+\sum\limits_{i=0}^{m+1}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m}\dbinom{m-i}{i}+\sum\limits_{i=0}^{m}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m-1}\dbinom{m-i}{i}+\sum\limits_{i=0}^{m}\dbinom{m-i+1}{i} \\ &=\sum\limits_{i=0}^{m-1}\dbinom{(m-1)-i+1}{i}+\sum\limits_{i=0}^{m}\dbinom{m-i+1}{i} \\ &=Fib_{m+1}+Fib_{m+2} \\ &=Fib_{m+3} \end{aligned}\)
      • 故命题成立。
    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(Matrix));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll t,b,p,n=1,m=2,k=2,i;
        cin>>t>>p;
        for(i=1;i<=t;i++)
        {
            cin>>b;
            f.ma[1][1]=0;
            f.ma[1][2]=1;
            a.ma[1][1]=0;
            a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
            cout<<mul(f,qpow(a,b+2,p,m),n,m,k,p).ma[1][1]<<endl;
        }
        return 0;
    }
    

HZOJ 864. 夕景昨日

HZOJ 865. 透明答案

HZOJ 866. 界外科学

HZOJ 867. 回忆补时

luogu P1306 斐波那契公约数

  • 推式子。

  • \(n \ge m\) ,有 \(\begin{aligned} \gcd(Fib_{n},Fib_{m}) &=\gcd(Fib_{n-1}+Fib_{n-2},Fib_{m}) \\ &=\gcd(2Fib_{n-2}+Fib_{n-3},Fib_{m}) \\ &=\gcd(3Fib_{n-3}+2Fib_{n-4},Fib_{m}) \\ &=\gcd(5Fib_{n-4}+3Fib_{n-5},Fib_{m}) \\ &=\gcd(Fib_{5}Fib_{n-4}+Fib_{4}Fib_{n-5},Fib_{m}) \\ &=\dots \\ &=\gcd(Fib_{m+1}Fib_{n-m}+Fib_{m}Fib_{n-m-1},Fib_{m}) \\ &=\gcd(Fib_{m+1}Fib_{n-m},Fib_{m}) \\ &=\gcd(Fib_{n-m},Fib_{m}) \\ &=\gcd(Fib_{n \bmod m},Fib_{m}) \\ &=\gcd(Fib_{\gcd(n,m)},Fib_{\gcd(n,m)}) \\ &=Fib_{\gcd(n,m)} \end{aligned}\)

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    ll gcd(ll a,ll b)
    {
        return b?gcd(b,a%b):a;
    }
    int main()
    {
        ll aa,bb,n=1,m=2,k=2,p=100000000;
        cin>>aa>>bb;
        f.ma[1][1]=0;
        f.ma[1][2]=1;
        a.ma[1][1]=0;
        a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
        cout<<mul(f,qpow(a,gcd(aa,bb),p,m),n,m,k,p).ma[1][1]<<endl;
        return 0;
    }
    

luogu P1168 中位数

  • 对顶堆板子。

  • 算法的核心思想是维护一个大根堆和小根堆,将大根堆倒置过来在小根堆的上方,使从上到下数字依次增大。序列内中位数是大、小根堆中大小较大的堆的堆顶。

    点击查看代码
    ll a[100001];
    priority_queue<int,vector<int>,less<int> >qmin;
    priority_queue<int,vector<int>,greater<int> >qmax;
    int main()
    {
        ll n,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        qmin.push(a[1]);
        cout<<a[1]<<endl;
        for(i=2;i<=n;i++)
        {
            if(a[i]>qmin.top())
            {
                qmax.push(a[i]);
            }
            else
            {
                qmin.push(a[i]);
            }
            while(abs((int)qmin.size()-(int)qmax.size())>=2)
            {
                if(qmin.size()>qmax.size())
                {
                    qmax.push(qmin.top());
                    qmin.pop();
                }
                else
                {
                    qmin.push(qmax.top());
                    qmax.pop();
                }
            }
            if(i%2==1)
            {
                cout<<((qmin.size()>qmax.size())?qmin.top():qmax.top())<<endl;
            }
        }
        return 0;
    }
    

每日总结、反思

  • 要学会猜测结论然后逆向证明的方式来尝试证明。
  • 要学会正确的打表。

2.6

闲话

  • 今天就要 放假 回家集训了。
  • 上午 \(field\) 称大课间的时候让我们出去活动活动,顺便调侃了我们一下。

做题纪要

luogu P1801 黑匣子

  • 查询第 \(k\) 时应维护大根堆的大小始终为 \(k-1\),此时小根堆的堆顶即为所求。

    点击查看代码
    int a[200001];
    priority_queue<int,vector<int>,less<int> >qmin;
    priority_queue<int,vector<int>,greater<int> >qmax;
    int main()
    {
        int n,m,last=0,u,i,j;
        cin>>n>>m;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        for(i=1;i<=m;i++)
        {
            cin>>u;
            for(j=last+1;j<=u;j++)
            {
                qmin.push(a[j]);
            }
            last=u;
            while(qmin.size()>=i)
            {
                qmax.push(qmin.top());
                qmin.pop();
            }
            cout<<qmax.top()<<endl;
            qmin.push(qmax.top());
            qmax.pop();
        }
        return 0;
    }
    

luogu P3871 [TJOI2010]中位数

  • 规定若序列长度为偶数,则指处在中间位置的两个数中较小的一个,故输出大根堆的堆顶即可。

    点击查看代码
    ll a[100001];
    priority_queue<int,vector<int>,less<int> >qmin;
    priority_queue<int,vector<int>,greater<int> >qmax;
    int main()
    {   
        ll n,m,aa,i;
        string pd;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        qmin.push(a[1]);
        for(i=2;i<=n;i++)
        {
            if(a[i]>qmin.top())
            {
                qmax.push(a[i]);
            }
            else
            {
                qmin.push(a[i]);
            }
            while(abs((int)qmax.size()-(int)qmin.size())>=2)
            {
                if(qmin.size()>qmax.size())
                {
                    qmax.push(qmin.top());
                    qmin.pop();
                }
                else
                {
                    qmin.push(qmax.top());
                    qmax.pop();
                }
            }
        }
        cin>>m;
        for(i=1;i<=m;i++)
        {
            cin>>pd;
            if(pd=="add")
            {
                cin>>aa;
                n++;
                if(aa>qmin.top())
                {
                    qmax.push(aa);
                }
                else
                {
                    qmin.push(aa);
                }
                while(abs((int)qmax.size()-(int)qmin.size())>=2)
                {
                    if(qmin.size()>qmax.size())
                    {
                        qmax.push(qmin.top());
                        qmin.pop();
                    }
                    else
                    {
                        qmin.push(qmax.top());
                        qmax.pop();
                    }
                }
            }
            else
            {
                if(n%2==0)
                {
                    cout<<qmin.top()<<endl;
                }
                else
                {
                    cout<<((qmin.size()>qmax.size())?qmin.top():qmax.top())<<endl;
                }
            }
        }
        return 0;
    }
    

luogu P6033 [NOIP2004 提高组] 合并果子 加强版

  • luogu P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 中我们使用堆来维护一堆数中最小的两个数。但本题中优先队列的插入耗时太大,考虑从优化插入入手。容易发现,插入的数具有单调性。

  • 将原序列进行排序,并将排序后的数插入一个队列 \(q_{1}\) 里;每次合并时,在 \(q_{1}\)\(q_{2}\) 里面选出最小的两个数,相加统计答案并插入 \(q_{2}\) 中。

    点击查看代码
    ll a[10000001],vis[100001];
    queue<ll>q1,q2;
    ll read()
    {
        ll x=0,f=1;
        char c=getchar();
        while(c>'9'||c<'0')
        {
            if(c=='-')
            {
                f=-1;
            }
            c=getchar();
        }
        while('0'<=c&&c<='9')
        {
            x=x*10+c-'0';
            c=getchar();
        }
        return x*f;
    }
    void write(ll x)
    {
        if(x<0)
        {
            putchar('-');
            x=-x;
        }
        if(x>9)
        {
            write(x/10);
        }
        putchar((x%10)+'0');
    }
    int main()
    {
        ll n,x,y,ans=0,minn=0x7f7f7f7f,maxx=0,i,j;
        n=read();
        for(i=1;i<=n;i++)
        {
            a[i]=read();
            maxx=max(maxx,a[i]);
            minn=min(minn,a[i]);
            vis[a[i]]++;
        }
        for(i=minn;i<=maxx;i++)
        {
            for(j=1;j<=vis[i];j++)
            {
                q1.push(i);
            }
        }
        for(i=1;i<=n-1;i++)//n个数需要合并n-1次
        {
            if(q2.empty()!=0||(q1.empty()==0&&q2.empty()==0&&q1.front()<q2.front()))
            {
                x=q1.front();
                q1.pop();
            }
            else
            {
                x=q2.front();
                q2.pop();
            }
            if(q2.empty()!=0||(q1.empty()==0&&q2.empty()==0&&q1.front()<q2.front()))
            {
                y=q1.front();
                q1.pop();
            }
            else
            {
                y=q2.front();
                q2.pop();
            }
            ans+=x+y;
            q2.push(x+y);
        }
        write(ans);
        return 0;
    }
    

luogu P1939 矩阵加速(数列)

  • 规定 \(a_{0}=0\)

  • \(F_{n}=\begin{bmatrix} a_{n} & a_{n+1} & a_{n+2} \end{bmatrix}\) ,容易有 \(\begin{aligned} F_{n} &=\begin{bmatrix} a_{n} & a_{n+1} & a_{n+2} \end{bmatrix} \\ &=\begin{bmatrix} a_{n-1} & a_{n} & a_{n+1} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}\\ &=\begin{bmatrix} a_{n-2} & a_{n-1} & a_{n} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{2} \\ &=\begin{bmatrix} a_{n-3} & a_{n-2} & a_{n-1} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{3} \\ &=\dots \\ &=\begin{bmatrix} a_{0} & a_{1} & a_{2} \end{bmatrix} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{n} \\ &=F_{0} \times \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 1 \end{bmatrix}^{n} \end{aligned}\)

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll t,b,n=1,m=3,k=3,p=1000000007,i;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>b;
            f.ma[1][1]=0;
            f.ma[1][2]=f.ma[1][3]=1;
            a.ma[1][1]=a.ma[1][2]=a.ma[2][2]=a.ma[2][3]=a.ma[3][1]=0;
            a.ma[1][3]=a.ma[2][1]=a.ma[3][2]=a.ma[3][3]=1;
            cout<<mul(f,qpow(a,b,p,m),n,m,k,p).ma[1][1]<<endl;
        }
        return 0;
    }
    

luogu P1349 广义斐波那契数列

  • \(F_{n}=\begin{bmatrix} a_{n} & a_{n+1} \end{bmatrix}\) ,容易有 \(\begin{aligned} F_{n} &=\begin{bmatrix} a_{n} & a_{n+1} \end{bmatrix} \\ &=\begin{bmatrix} a_{n-1} & a_{n} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix} \\ &=\begin{bmatrix} a_{n-2} & a_{n-1} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{2} \\ &=\begin{bmatrix} a_{n-3} & a_{n-2} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{3} \\ &=\dots \\ &=\begin{bmatrix} a_{1} & a_{2} \end{bmatrix} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{n-1} \\ &=F_{1} \times \begin{bmatrix} 0 & q \\ 1 & p \end{bmatrix}^{n-1} \end{aligned}\)

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a, Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll b,p,n=1,m=2,k=2;
        cin>>a.ma[2][2]>>a.ma[1][2]>>f.ma[1][1]>>f.ma[1][2]>>b>>p;
        a.ma[1][1]=0;
        a.ma[2][1]=1;
        cout<<mul(f,qpow(a,b-1,p,m),n,m,k,p).ma[1][1]<<endl;
        return 0;
    }
    

luogu P4910 帕秋莉的手环

  • tgHZOJ 264. 选拔队员 不同的是本题是一个环。原首尾的两个珠子会相互影响。考虑对首尾珠子进行分讨。

  • 当第一个珠子是金属性珠子时,最后一个珠子不一定是木属性珠子。然后把原来的结论搬过来,不同的数量为 \(\begin{aligned} \sum\limits_{i=0}^{n-1}\dbinom{n-1-i+1}{i} &=Fib_{n+1} \end{aligned}\)

  • 当第一个珠子是木属性珠子时,第二个珠子和最后一个珠子一定是金属性珠子。然后把原来的结论搬过来,不同的数量为 \(\begin{aligned} \sum\limits_{i=0}^{n-3}\dbinom{n-3-i+1}{i} &=Fib_{n-1} \end{aligned}\)

  • \(Fib_{n-1}+Fib_{n+1}\) 即为所求。

    点击查看代码
    struct Matrix
    {
        ll ma[5][5];
        Matrix()
        {
            memset(ma,0,sizeof(ma));
        }
    }f,a;
    Matrix mul(Matrix a,Matrix b,ll n,ll m,ll k,ll p)
    {
        Matrix c;
        for(ll i=1;i<=n;i++)
        {
            for(ll j=1;j<=k;j++)
            {
                for(ll h=1;h<=m;h++)
                {
                    c.ma[i][j]=(c.ma[i][j]+(a.ma[i][h]%p)*(b.ma[h][j]%p)%p)%p;
                }
            }
        }
        return c;
    }
    Matrix qpow(Matrix a,ll b,ll p,ll n)
    {
        Matrix ans;
        for(ll i=1;i<=n;i++)
        {
            ans.ma[i][i]=1;
        }
        while(b>0)
        {
            if(b&1)
            {
                ans=mul(ans,a,n,n,n,p);
            }
            b>>=1;
            a=mul(a,a,n,n,n,p);
        }
        return ans;
    }
    int main()
    {
        ll t,b,n=1,m=2,k=2,sum,i,p=1000000007;
        cin>>t;
        for(i=1;i<=t;i++)
        {
            cin>>b;
            f.ma[1][1]=0;
            f.ma[1][2]=1;
            a.ma[1][1]=0;
            a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
            sum=mul(f,qpow(a,b-1,p,m),n,m,k,p).ma[1][1];
            f.ma[1][1]=0;
            f.ma[1][2]=1;
            a.ma[1][1]=0;
            a.ma[1][2]=a.ma[2][1]=a.ma[2][2]=1;
            cout<<(mul(f,qpow(a,b+1,p,m),n,m,k,p).ma[1][1]+sum)%p<<endl;
        }
        return 0;
    }
    

每日总结、反思

  • 做过的题不能再错。
posted @ 2024-01-29 22:11  hzoi_Shadow  阅读(182)  评论(15编辑  收藏  举报
扩大
缩小