多校A层冲刺NOIP2024模拟赛25
多校A层冲刺NOIP2024模拟赛25
\(T1\) A. 图 \(100pts/100pts\)
-
对于每个状态开一个
bitset
加速位运算即可。点击查看代码
bitset<10010>vis[10010],tmp[4]; char s[10010]; int main() { #define Isaac #ifdef Isaac freopen("a.in","r",stdin); freopen("a.out","w",stdout); #endif int n,m,ans=0,i,j,k; cin>>n>>m; for(k=1;k<=m;k++) { cin>>(s+1); for(i=1;i<=3;i++) { tmp[i].reset(); } for(i=n;i>=1;i--) { if(s[i]=='1') { vis[i]^=tmp[2]; vis[i]^=tmp[3]; } if(s[i]=='2') { vis[i]^=tmp[1]; vis[i]^=tmp[3]; } if(s[i]=='3') { vis[i]^=tmp[1]; vis[i]^=tmp[2]; vis[i]^=tmp[3]; } tmp[s[i]-'0'][i]=1; } } for(i=1;i<=n;i++) { ans+=vis[i].count(); } cout<<ans<<endl; return 0; }
\(T2\) B. 序列 \(0pts/0pts\)
-
考虑将所有操作表示成 \(a_{i}\) 乘以一个系数的形式。
- 赋值仅考虑赋值后的最大值 \(y_{max}\) ,将其转换成 \(y_{max}-a_{i}\) 的形式(先不用管正负号的问题)。
- 加一定是先加较大的数再加较小的数,不妨先降序排序,其贡献为 \(\dfrac{a_{i}'+y_{t}}{a_{i}'}\) ,其中 \(a_{i}'=a_{i}+\sum\limits_{j=1}^{t-1}y_{j}\) 。
- 乘直接处理即可。
-
将贡献扔到操作序列上降序排序即可。
-
因为可能存在 \(a_{i}' \equiv 0 \pmod {10000000007}\) 的情况,所以可以把答案中的 \(10^{9}+7\) 的指数单独提出去(另外可以证明只会非负整数)。
点击查看代码
const ll p=1000000007; ll a[100010],maxx[100010],last[100010]; vector<ll>sum[100010]; vector<pair<__int128_t,__int128_t> >c; bool cmp(pair<__int128_t,__int128_t>a,pair<__int128_t,__int128_t>b) { return a.first*b.second>b.first*a.second; } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } int main() { #define Isaac #ifdef Isaac freopen("b.in","r",stdin); freopen("b.out","w",stdout); #endif ll n,m,t,x,y,ans=1,cnt=0,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; ans=ans*a[i]%p; } for(i=1;i<=m;i++) { cin>>t>>x>>y; if(t==1) { maxx[x]=max(maxx[x],y); } if(t==2) { sum[x].push_back(y); } if(t==3) { c.push_back(make_pair(y,1)); } } for(i=1;i<=n;i++) { if(maxx[i]>a[i]) { sum[i].push_back(maxx[i]-a[i]); } sort(sum[i].begin(),sum[i].end(),greater<ll>()); for(j=0;j<sum[i].size();j++) { c.push_back(make_pair(a[i]+sum[i][j],a[i])); a[i]+=sum[i][j]; } } sort(c.begin(),c.end(),cmp); for(i=0;i<=m;i++) { cout<<(cnt==0)*ans<<" "; if(i<c.size()) { if(c[i].first%p==0) { c[i].first/=p; cnt++; } if(c[i].second%p==0) { c[i].second/=p; cnt--; } ans=(ans*(c[i].first%p)%p)*qpow(c[i].second%p,p-2,p)%p; } } return 0; }
\(T3\) C. 树 \(10pts/0pts\)
-
部分分
- \(20pts\) :暴力连边然后进行博弈,时间复杂度为 \(n^{2d+1}d\) 。
点击查看代码
const ll p=1000000007; int ans; vector<int>e[8000010]; void add(int u,int v) { e[u].push_back(v); } int ask(int x,int fa) { for(int i=0;i<e[x].size();i++) { if(e[x][i]!=fa&&ask(e[x][i],x)==0) { return 1; } } return 0; } void dfs(int pos,int d,int n) { if(pos==d+1) { ans=(ans+ask(1,0))%p; } else { for(int i=1+(pos-1)*n;i<=pos*n;i++) { for(int j=1+pos*n;j<=(pos+1)*n;j++) { e[i].push_back(j); dfs(pos+1,d,n); e[i].pop_back(); } } } } int main() { #define Isaac #ifdef Isaac freopen("c.in","r",stdin); freopen("c.out","w",stdout); #endif int n,d,u,v,i,j; cin>>n>>d; for(j=1;j<=n-1;j++) { cin>>u>>v; for(i=0;i<=d;i++) { add(u+i*n,v+i*n); add(v+i*n,u+i*n); } } dfs(1,d,n); cout<<ans<<endl; return 0; }
-
正解
\(T4\) D. 字符串 \(0pts/0pts\)
-
部分分
- \(10pts\) :双指针判断是否需要插入,写法和判断一个数列是否是另一个数列的子序列差不多。
-
正解
- 对每个字符按照 \(t\) 中位置进行编号,然后等价于查询 \([l,r]\) 中极长严格递增段的个数加 \(1\) 。考虑统计断点(相邻两个字符逆序)的个数。
- 观察到 \(k \in [1,10]\) ,所以直接对着值域挂在线段树的每个节点上就行了。
点击查看代码
int k,tmp[11][11],id[11]; char s[200010],t[25]; struct SMT { struct SegmentTree { int lc,rc,sum[11][11],lazy; SegmentTree() { lc=rc=lazy=0; memset(sum,0,sizeof(sum)); } SegmentTree operator + (const SegmentTree &another) { SegmentTree tmp; for(int i=1;i<=k;i++) { for(int j=1;j<=k;j++) { tmp.sum[i][j]=sum[i][j]+another.sum[i][j]; } } tmp.sum[rc][another.lc]++; tmp.lc=lc; tmp.rc=another.rc; return tmp; } }tree[800010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt]=tree[lson(rt)]+tree[rson(rt)]; } void build(int rt,int l,int r) { if(l==r) { tree[rt].lc=tree[rt].rc=s[l]-'a'+1; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } int move_right(int x,int d) { return (x+d-1)%k+1; } void pushlazy(int rt,int lazy) { tree[rt].lc=move_right(tree[rt].lc,lazy); tree[rt].rc=move_right(tree[rt].rc,lazy); for(int i=1;i<=k;i++) { for(int j=1;j<=k;j++) { tmp[move_right(i,lazy)][move_right(j,lazy)]=tree[rt].sum[i][j]; } } for(int i=1;i<=k;i++) { for(int j=1;j<=k;j++) { tree[rt].sum[i][j]=tmp[i][j]; } } tree[rt].lazy=(tree[rt].lazy+lazy)%k; } void pushdown(int rt) { if(tree[rt].lazy!=0) { pushlazy(lson(rt),tree[rt].lazy); pushlazy(rson(rt),tree[rt].lazy); tree[rt].lazy=0; } } void update(int rt,int l,int r,int x,int y,int val) { if(x<=l&&r<=y) { pushlazy(rt,val); return; } pushdown(rt); int mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(rt),mid+1,r,x,y,val); } pushup(rt); } SegmentTree query(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt]; } pushdown(rt); int mid=(l+r)/2; if(y<=mid) { return query(lson(rt),l,mid,x,y); } if(x>mid) { return query(rson(rt),mid+1,r,x,y); } return query(lson(rt),l,mid,x,y)+query(rson(rt),mid+1,r,x,y); } int ask(int l,int r,int n) { int sum=1; SegmentTree tmp=query(1,1,n,l,r); for(int i=1;i<=k;i++) { id[t[i]-'a'+1]=i; } for(int i=1;i<=k;i++) { for(int j=1;j<=k;j++) { sum+=(id[i]>=id[j])*tmp.sum[i][j]; } } return sum; } }T; int main() { #define Isaac #ifdef Isaac freopen("d.in","r",stdin); freopen("d.out","w",stdout); #endif int n,m,pd,l,r,c,i; cin>>n>>m>>k>>(s+1); T.build(1,1,n); for(i=1;i<=m;i++) { cin>>pd>>l>>r; if(pd==1) { cin>>c; T.update(1,1,n,l,r,c); } else { cin>>(t+1); cout<<T.ask(l,r,n)<<endl; } } return 0; }
总结
- \(T2\)
- 读假题了,把 至多 读成 恰好 了。
- 又没想到转化到操作序列上排序,一直在想对某个单体怎么处理。
- \(T3\) 理解错了有根树的含义,以为复制后的树中仍需满足原上下级关系,挂了 \(10pts\) 。
- \(T4\) 赛时降智了,导致不会写判断一个数列是否是另一个数列的子序列。
后记
-
\(T1\) 的提示不知道有什么用。
-
\(T3\) 中途补充了大样例。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18561433,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。