前言
- 比赛链接。
本来挺水的一场,挂分挂狠了,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 金箱子
- 类似题:P1654 OSU!。
维护 \(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 了,还是借别人电话绑邮箱,听说高二那边明天基本都要走了?
标签:int,31,operatorname,2024,read,dfn,void,inline,集训 From: https://www.cnblogs.com/Charlieljk/p/18374641