2024暑假集训测试31

前言

image

本来挺水的一场,挂分挂狠了,T1 被 unordered_map 害死了;T2 赛时一看这不 OSU 嘛,反正也会先把部分分打满回来再写吧……

T3 只想说出题人三天不骂上房揭瓦,你大样例锅了就锅了能不能说明白,就发一条消息 “T3 样例输出” 总共 \(6\) 个字,鬼知道你说的是大样例,看一眼小样例没换还挺纳闷呢。去打 T3,但我不会 AC 自动机啊?拿 kmp 部分分 \(50\) 吧,开不下啊要么胡个主席树?胡完了发现不对和暴力一个分,改成纯暴力。拍大样例,过不去啊??不是为啥暴力过不去?ctrl F 启动,这不对着吗?哎他是不是换样例输出了,看一眼我草真换了,我****。

T4 感觉暴力过 \(3e4\) 都够呛,干脆只开了 \(3e4\) 寻思跑快点,结果拿了 \(50\),赛后看好多 \(70\) 的啊?数组开大,草就最后俩点过不去,好数据。

不对我** T2 没写呢!啊啊啊啊……没写完,赶紧胡个暴力得了……

所以加起来挂了 \(140\)(算上 T4 那个 \(20\)),T3 是 AC 自动机板子,可惜当时学的不咋地赛时根本不会,赛后赶紧补补。

T1 线性只因

二进制按位从大到小维护决策集合即可,若集合内本位为 \(1\) 的个数 \(\ge m\) 则答案加上本位,并将我本位为 \(0\) 的排除决策集合,复杂度 \(O(n\log v)\)

点击查看代码
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10,M=30;
template<typename Tp> inline void read(Tp&x)
{
	x=0;register bool z=true;
	register char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
	for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,m,maxx,ans,tot,cnt,a[N],pos[N];
bool vis[N];
signed main()
{
	read(n,m); 
	for(int i=1,x;i<=n;i++) read(a[i]),maxx=max(maxx,a[i]);
	if(m==1) write(maxx),exit(0);
	for(int i=__lg(maxx);i>=0;i--)
	{
		tot=cnt=0;
		for(int j=1;j<=n;j++) if(!vis[j])
		{
			tot++;
			if(!((a[j]>>i)&1)) pos[++cnt]=j;
		}
		if(tot-cnt>=m) 
		{
			ans|=(1<<i);
			for(int j=1;j<=cnt;j++) vis[pos[j]]=1;
		}
	}
	write(ans);
}

T2 金箱子

维护 \(x^k\) 就不能只维护 \(x^k\),还要维护 \(x^0,x^1,x^2,……,x^{k-1}\)

然后二项式定理展开直接做就行了,复杂度 \(O(nk^2)\),可以优化但我不会,反正能过。

点击查看代码
#include<bits/stdc++.h>
#define ll long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e4+10,M=110,P=998244353;
template<typename Tp> inline void read(Tp&x)
{
	x=0;register bool z=true;
	register char c=getchar();
	for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
	for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
	x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x)
{if(x>9)wt(x/10);putchar((x%10)+'0');}
template<typename Tp> inline void write(Tp x)
{if(x<0)putchar('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
int n,m,f[2][M],mi[2][M],c[M][M];
int C(int n,int m)
{
	if(m>n) return c[n][m]=0;
	if(m==0||m==n) return c[n][m]=1;
	if(c[n][m]) return c[n][m];
	return c[n][m]=(C(n-1,m)+C(n-1,m-1))%P;
}
int solve(int k,int x[],int y[],int p)
{
	ll ans=0;
	for(int i=0;i<=k;i++) (ans+=1ll*C(k,i)*x[k-i]%P*y[i]%P)%=P;
	return ans*p%P;
}
signed main()
{
	read(n,m);
	f[0][0]=f[1][0]=1;
	for(int i=1,p,a,b;i<=n;i++) 
	{
		read(p,a,b);
		mi[0][0]=mi[1][0]=1;
		for(int j=1;j<=m;j++) 
		{
			mi[0][j]=1ll*mi[0][j-1]*a%P;
			mi[1][j]=1ll*mi[1][j-1]*b%P;
		}
		for(int j=1;j<=m;j++)
			f[i&1][j]=(solve(j,f[(i-1)&1],mi[0],p)+solve(j,f[(i-1)&1],mi[1],P+1-p))%P;
	}
	write(f[n&1][m]);
}

T3 可持久化字符串

  • 部分分 \(50pts\):kmp 什么的直接暴力跑。

  • 正解:

    拓扑优化 AC 自动机板子,每次直往后扔一个字符直接扔 trie 树里,不用再跑一边,时空复杂度就保证了,存一下时间戳,等价于求 \(n\) 个模式串在主串中各自出现的次数,拓扑 AC 自动机跑一边就行了,询问离线下来,最后前缀和一下,复杂度线性。

    点击查看代码
    #include<bits/stdc++.h>
    #define ll long long 
    #define endl '\n'
    #define sort stable_sort
    using namespace std;
    const int N=1e5+10;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
    template<typename Tp> inline void wt(Tp x)
    {if(x>9)wt(x/10);putchar((x%10)+'0');}
    template<typename Tp> inline void write(Tp x)
    {if(x<0)putchar('-'),x=~x+1;wt(x);}
    template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar(' ');write(y...);}
    int n,m,cnt,tot,vl[N],vr[N],last[N],en[N],pos[N],deg[N];
    ll sum[N];
    char t[N];
    struct aa {int son[26],fail,id,ans;}trie[N];
    void insert(int p,int id,int c)
    {
        if(!trie[p].son[c]) trie[p].son[c]=++tot;
        p=trie[p].son[c];
        trie[p].id=trie[p].id?trie[p].id:id;
        en[id]=p,pos[id]=trie[p].id;
    }
    void build()
    {
        queue<int>q;
        for(int i=0;i<26;i++) if(trie[0].son[i])
            q.push(trie[0].son[i]);
        while(!q.empty())
        {
            int x=q.front(); q.pop();
            for(int i=0,y,z;i<26;i++)
            {
                y=trie[x].son[i],z=trie[x].fail;
                if(!y) {trie[x].son[i]=trie[z].son[i]; continue;}
                trie[y].fail=trie[z].son[i];
                deg[trie[y].fail]++;
                q.push(y);
            }
        }
    }
    void ask(char s[])
    {
        int p=0,len=strlen(s);
        for(int i=0;i<len;i++)
        {
            p=trie[p].son[s[i]-'a'];
            trie[p].ans++;
        }
    }
    void topo()
    {
        queue<int>q;
        for(int i=0;i<=tot;i++) if(!deg[i]) q.push(i);
        while(!q.empty())
        {
            int x=q.front(); q.pop();
            sum[trie[x].id]=trie[x].ans;
            int y=trie[x].fail;
            deg[y]--,trie[y].ans+=trie[x].ans;
            if(deg[y]==0) q.push(y);
        }
    }
    signed main()
    {
        read(m,n),cin>>t; n=0; char c;
        for(int i=1,op,x;i<=m;i++)
        {
            read(op);
            if(op==1)
            {
                read(x),cin>>c; cnt++;
                insert(en[pos[x]],cnt,c-'a');
                last[cnt]=x;
            }
            if(op==2)
            {
                read(x); cnt++;
                en[cnt]=en[last[x]],pos[cnt]=pos[last[x]];
                last[cnt]=last[last[x]];
            }
            if(op==3) {n++; read(vl[n],vr[n]);}
        }
        build(),ask(t),topo();
        for(int i=1;i<=cnt;i++) sum[i]=en[i]==0?1:sum[pos[i]];
        sum[0]=1; for(int i=1;i<=cnt;i++) sum[i]+=sum[i-1];
        for(int i=1;i<=n;i++)
            write(vl[i]==0?sum[vr[i]]:sum[vr[i]]-sum[vl[i]-1]),puts("");
    }
    

T4 圣诞树

  • 部分分 \(70pts\):求 lca 然后暴力跳父亲,复杂度上限是 \(O(nm)\) 本来只应该得 \(20pts\),出题人应该是故意没想卡,但是最后两个点挺强的卡过不去,卡常什么的可以直接用 bitset。

  • 正解:

    不会整体二分,先不写了。

    官方题解
    • 对于 \(20\%\) 的数据:

      直接暴力 dfs 统计答案即可。

    • 对于 \(40\%\) 的数据:

      对于每个节点 \(u\) 记录节点 \(u\) 到根节点路径上恰好出现了 \(1\) 次的权值,和恰好出现了 \(2\) 次的权值,发现可以通过简单容斥计算答案,用 bitset 进行维护,复杂度为 \(O(\tfrac{n^2}{w})\)

    • 对于另外 \(10\%\) 的数据:

      由于每个朋友只送给 chino 一件礼物,因此实际上需要求解树上路径 \((u,v)\) 中第一个出现次数为 \(0\) 的数,很容易用主席树上二分求解。

    • 对于 \(70\%\) 的数据:

      考虑操作离线,用树上莫队解决,发现操作为查询第一个未出现的数,用分块平衡复杂度可以做到 \(O(n\sqrt{n})\)

    • 对于 \(100\%\) 的数据:

      考虑利用每个朋友最多送给 chino 一件礼物这个性质,考虑离线后做整体二分,设当前二分的值为 \(mid\) ,那么我们需要对于每个询问求解树上路径中 \([L,mid]\) 值域中出现的数的种数。

      发现所有权值的出现情况只有 \(3\) 种:当前权值只出现 \(1\) 次,当前权值出现 \(2\) 次并且出现的位置具有祖先关系,当前权值出现 \(2\) 次并且出现位置没有祖先关系。

      假设所有询问 \((x,y)\) 均满足 \(x\)\(\operatorname{dfn}\) 序小于等于 \(y\)\(\operatorname{dfn}\) 序。

      考虑第一种情况会贡献到的询问,设出现的位置为 \(u\) ,容易发现这会贡献到 \(\operatorname{dfn}_x\in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_u + \operatorname{size}_u, n]\)\(\operatorname{dfn}_x \in [1, \operatorname{dfn}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1]\) 的询问,需要特殊处理 \(\operatorname{lca}(x, y) = u\) 的询问。

      考虑第二种情况,首先按照第一种情况分别处理 \(u, v\) 两点,发现存在一些询问被贡献了两次,设 \(v\)\(u\) 方向上的儿子为 \(p\) ,那么 \(\operatorname{dfn}_x \in [1, \operatorname{dfn}_p - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1]\)\(\operatorname{dfn}_x \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_p + \operatorname{size}_p, n]\) 的询问会被贡献两次。

      考虑第三种情况,仍然按照第一种情况分别处理 \(u,v\) 两端,发现 \(\operatorname{dfn}_x \in [\operatorname{dfn}_u, \operatorname{dfn}_u + \operatorname{size}_u - 1], \operatorname{dfn}_y \in [\operatorname{dfn}_v, \operatorname{dfn}_v + \operatorname{size}_v - 1]\) 的询问会被贡献两次。

      于是原问题转化为二维平面上做矩形加,单点查询,可以用扫描线解决,复杂度为 \(O(n\log^2n)\) ,使用树状数组后可以在 \(1200ms\) 通过本题。

总结

存在严重的时间分配不合理以及不仔细看**出题人的消息的问题,之前 AC 自动机没学明白的锅总算是补上一点了,不然的话这场理应每个人都拿 \(300+\) 的。

附录

今天开全网报名 CSP 了,还是借别人电话绑邮箱,听说高二那边明天基本都要走了?

posted @ 2024-08-22 20:07  卡布叻_周深  阅读(52)  评论(9编辑  收藏  举报