NOIP 模拟赛 11~20
模拟11 A层联测24
100+0+20+10=130pts rk32
T1 签到题
T2 最大值的最小竟然没想到二分,退役吧。。爆搜所有路径不知道哪写挂了赛后被卡成零蛋。。。
T3 暴力枚举
T4 二位前缀查分暴力
T1 花菖蒲
首先有解一定满足 。
当 时,可以想到构造菊花图。
当 时,可以想到构造毛毛虫
所以正解就是毛毛虫套菊花图。
需要特盘菊花图的度数正好也为 时,度数为 的点的个数会加一,即 一定无解。
总点数为 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; vector<pair<int,int>>ans; void output(){ cout<<ans.size()+1<<'\n'; for(auto const&x:ans)cout<<x.first<<' '<<x.second<<'\n'; } int main(){ int a=read(),b=read(); if(a==0&&b==0){puts("1");return 0;} else if(a==3&&b==0){puts("0");return 0;} else if(b>a-2){puts("0");return 0;} else if(b==a-3){puts("0");return 0;} int tot=1,fa=1; for(int i=1;i<=b;i++){ ans.emplace_back(make_pair(fa,++tot)); fa=tot; ans.emplace_back(make_pair(fa,++tot)); } ans.emplace_back(make_pair(fa,++tot)); if(b==a-2){ output(); return 0; } fa=tot; for(int i=1;i<=a-b-1;i++){ ans.emplace_back(make_pair(fa,++tot)); } output(); return 0; }
T2 百日草
求最大值的最小,明显是二分答案。
那么对于一个确定的答案合法当且仅当对于所有经过的边,需要满足 ,其中 为到 的距离。
那么就只走符合条件的边,看是否能走到 ,然后是 最短路,直接用 求即可。
需要注意:因为会有重边,所以不能在 入队时判断是否访问,而是要在取出队首时判断,复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const ll inf=1ll<<50; const int M=3e5+10; struct node{ int w,v,nxt; }e[M]; int head[M],cnt(0); il void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt; } int n,m; bool vis[M]; ll dep[M]; queue<int> q; bool check(ll ans){ while(!q.empty())q.pop(); q.push(1); for(int i=1;i<=n;i++)vis[i]=0,dep[i]=inf; dep[1]=0; while(!q.empty()){ int x=q.front(); q.pop(); if(vis[x])continue; vis[x]=1; for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(1ll*(dep[x]+1)*e[i].w>ans)continue; dep[y]=min(dep[y],dep[x]+1); if(y==n)return 1; q.push(y); } } return 0; } main(){ n=read(),m=read(); ll mx=0; for(int i=1;i<=m;i++){ int u=read(),v=read(); ll w=read(); add(u,v,w); mx=max(w,mx); } ll L=0,R=1ll*mx*(n-1),ans; while(L<=R){ ll mid=(L+R)>>1; if(check(mid)){ R=mid-1; ans=mid; } else L=mid+1; } cout<<ans; return 0; }
T3 紫丁香
很有意思的一道题。这里说说官解做法。
首先有一个结论:若 为偶数,则答案为 ;若 为奇数,则答案为 。
证明:
先考虑原图是颗树的情况,我们以任意点为根,那么对于所有非根节点,都有一条从父亲连过来的边,通过决定选()或不选()该父边,来调整该点度数的奇偶性,即若该点加上父边后现在的度数为偶数,则断掉父边,否则保留。
那么只有根节点的奇偶性无法保证。
若 为偶数,则除根节点外有 个点的度数已经为奇数,又因为总度数为为偶数(每加一条边都会使总度数加 ),则根节点的度数也为奇数,所以答案再加 ,为 。
若 为奇数,则根节点的度数为偶数,答案就是 。
然后我们考虑加上所有非数边,发现这样只可能影响每个点的初始度数,而我们仍可以通过上述方法构造,即对最后答案没有影响。
证毕。
如果没有要求构造方法这道题就没了,接下来我们考虑如何构造字典序最大的方案。
根据这个结论我们就可以先把原图的所有的非树边全选上,再去和生成树斗智斗勇。为了让字典序尽可能大,我们的生成树选择最大生成树(这里每条边的边权即为该边的编号),这样我们就可以让编号小的点尽可能多的被选,而需要作出决策的点的编号尽可能大。
我们先来考虑对于一个已知的决策,修改(即每条边的选择是 则变为 ,是 则变为 )一条链会改变什么? 对于链两端的边奇偶性改变,非两端的边奇偶性都不变,这个比较显然。
然后就先 一遍从叶子节点往上作出决策(即该点的父边是否需要选择),然后我们发现如果 为偶数,那么我们以任何一点作为根节点遍历得到的决策都是相同的,因为根节点的度数也为奇数,就和其它点一样了,我们不管修改哪一条链,都会产生两个度为偶数的点,这样答案就不优了。所以此时直接输出答案即可。
若 为偶数,则度为偶数的点只有一个,且不一定是钦定的根节点。考虑怎么将该点转移到其他位置来使答案最优。
一个简单的方法就是修改一个点 到根节点的链,这样偶数点就转移到了 。
发现只有原来没被选的边才需要修改,所以我们只考虑为 的边。按编号从小到大考虑每一条边,假设我们当前想要修改 的父边,那么该链的端点一定在 的子树内。我们就只在子树内继续考虑下一个修改的边,且该边的编号一定比当前修改的编号大。
那么我们考虑给所有 边建一个树,定义一个虚拟节点 ,对于每一个边 ,找到从它到根路径上第一个编号小于 的边(设为 ),从 向 连一条边,表示以 为端点的链同时还能覆盖 。如果找不到则向虚拟节点连边,表示以 为端点的链只能覆盖 。
这样我们只需在建好的树上每次选择编号最小的点往下递归,一直到叶子节点,就选出了最优的链顶,最后暴力修改链上的边即可。
“根路径上第一个编号小于 的边” 可以线段树二分,以深度为下标,找最靠右的点即可。
然后就做完了。复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=6e5+10; const int V=9e5+10; int n,m; struct node{ int u,v,w; }ee[V]; int fa[M]; int find(int x){ if(x==fa[x])return x; else return fa[x]=find(fa[x]); } struct EDG1{ int v,nxt,w; }e[M<<1]; int head[M],cnt(0); il void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt; } int deg[M],ans[V]; void dfs(int x,int fat,int edg){ for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fat)continue; dfs(y,x,i); } if(!fat)return; if(deg[x]%2==0){ deg[x]--; deg[fat]--; ans[e[edg].w]=0; } } namespace Segment_Tree{ int t[M<<3]; il void push_up(int k){ t[k]=min(t[k<<1],t[k<<1|1]); } void build(int k,int l,int r){ if(l==r){ t[k]=inf; return; } int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); push_up(k); } void update(int k,int l,int r,int pos,int val){ if(l==r){ t[k]=val; return; } int mid=(l+r)>>1; if(pos<=mid)update(k<<1,l,mid,pos,val); else update(k<<1|1,mid+1,r,pos,val); push_up(k); } int query(int k,int l,int r,int val){ if(l==r)return t[k]; int mid=(l+r)>>1; if(val>t[k<<1|1])return query(k<<1|1,mid+1,r,val); else return query(k<<1,l,mid,val); } } int to[V]; int f[M],pos[M],son[V]; void dfs2(int x,int fat,int edg,int dep){ f[x]=fat; pos[x]=e[edg].w; son[e[edg].w]=x; int fm=Segment_Tree::query(1,1,n,e[edg].w); if(fm==inf)fm=m+1; if(x!=1&&ans[e[edg].w]==0&&e[edg].w<to[fm])to[fm]=e[edg].w; if(fat)Segment_Tree::update(1,1,n,dep,e[edg].w); for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fat)continue; dfs2(y,x,i,dep+1); } if(fat)Segment_Tree::update(1,1,n,dep,inf); } int main(){ freopen("lilac.in","r",stdin); freopen("lilac.out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++)fa[i]=i; fill(ans+1,ans+1+m,1); for(int i=1;i<=m;i++){ ee[i].u=read(),ee[i].v=read(); ee[i].u++; ee[i].v++; ee[i].w=i; deg[ee[i].u]++; #cnblogs_post_body h2 { font-size: px; font-weight: bold; line-height: 1.5; margin: 10px 0; }deg[ee[i].v]++; } int t=0; for(int i=m;i>=1;i--){ int fx=find(ee[i].u),fy=fi#cnblogs_post_body h2 { font-size: px; font-weight: bold; line-height: 1.5; margin: 10px 0; }nd(ee[i].v); if(fx==fy)continue; fa[fx]=fy; add(ee[i].u,ee[i].v,ee[i].w); add(ee[i].v,ee[i].u,ee[i].w); t++; if(t==n-1)break; } dfs(1,0,0); if(n%2==0) for(int i=1;i<=m;i++)cout<<ans[i]; else{ Segment_Tree::build(1,1,n); for(int i=1;i<=m+1;i++)to[i]=inf; dfs2(1,0,0,1); int now=m+1; while(to[now]!=inf) now=to[now]; now=son[now]; while(now){ ans[pos[now]]^=1; now=f[now]; } for(int i=1;i<=m;i++)cout<<ans[i]; } return 0; }
T4 麒麟草
10pts:
修改的时候二维差分修改,查询的时候二维前缀和查询,复杂度 。
正解不会,咕。
11.6 模拟12 A层联测25
100+0+30+0=130pts rk11
T1又是构造。
T3暴力+打表。
T2 T4 都读错题了。
T1 构造
考虑构造
的矩阵,这样能构造出来 个。
然后发现第一行没有用,然后换成 的一行,最多能构造 个。
考虑怎么满足“恰好”
首先先让第一行最优。
然后先填下面的,填到第一个大于 的时候停,停的位置肯定不在 行,所以这一行剩下的位置用 补满。
然后发现第一行每将一个 或 改成 就会让贡献减少 ,一直减到 停即可。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=100; char a[M][M]; il int check(){ int ans=0; for(int i=1;i<=40;i++) for(int j=1;j<=40;j++){ if(j+2<=40&&a[i][j]=='r'&&a[i][j+1]=='y'&&a[i][j+2]=='x')ans++; if(j-2>=1&&a[i][j]=='r'&&a[i][j-1]=='y'&&a[i][j-2]=='x')ans++; if(i+2<=40&&a[i][j]=='r'&&a[i+1][j]=='y'&&a[i+2][j]=='x')ans++; if(i-2>=1&&a[i][j]=='r'&&a[i-1][j]=='y'&&a[i-2][j]=='x')ans++; if(j+2<=40&&i+2<=40&&a[i][j]=='r'&&a[i+1][j+1]=='y'&&a[i+2][j+2]=='x')ans++; if(j+2<=40&&i-2>=1&&a[i][j]=='r'&&a[i-1][j+1]=='y'&&a[i-2][j+2]=='x')ans++; if(j-2>=1&&i+2<=40&&a[i][j]=='r'&&a[i+1][j-1]=='y'&&a[i+2][j-2]=='x')ans++; if(j-2>=1&&i-2>=1&&a[i][j]=='r'&&a[i-1][j-1]=='y'&&a[i-2][j-2]=='x')ans++; } return ans; } il void output(int n,int m){ cout<<n<<" "<<m<<'\n'; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++) cout<<a[i][j]; puts(""); } exit(0); } int main(){ int tot=read(); int n=1,m=0,now=0; for(int i=1;i<=40;i++){ ++m; if(i%4==1)a[1][i]='x'; else if(i%4==2)a[1][i]='y'; else if(i%4==3)a[1][i]='r'; else a[1][i]='y'; if(check()==tot){ output(n,m); return 0; } } m=40; for(int i=2;i<=40;i++){ char s; if(i%4==1||i%4==3)s='y'; else if(i%4==0)s='x'; else if(i%4==2)s='r'; for(int j=1;j<=40;j++){ a[i][j]=s; now=check(); if(now>=tot){ n=i; for(int k=j+1;k<=40;k++)a[i][k]='y'; goto miku; } } } miku: if(now==tot)output(n,m); for(int i=1;i<=n;i++){ a[1][i]='y'; if(check()==tot)output(n,m); } }
T2 游戏
考虑二分答案 ,先考虑满足同学使得被抓人数 。
那么所有人数 的房间就不用管了,对于所有 的的房间,设同学有 的概率进入该房间,那么老师在该房间抓到的期望人数为 。
那对于任意的房间都要满足 ,然后就可以求出每个 所能取到的最小值,最后判断 的是否合法,只需判断 最小值是否 即可。
接下来考虑满足老师抓到的人数 的限制,发现只需满足学生的限制,老师也就满足了,证明如下。
假设最终答案为 ,则有 ,即
考虑学生的最优策略,学生想让期望人数最小,那么他一定会选择人数最多的房间进去,因为若他分出一定概率去别的房间,一定会使期望被抓人数增大。
考虑老师的最优策略,那么老师的期望人数就是 ,其中 为学生进入的房间,因为老师不知道到学生进哪个房间,那么她一定会等期望地进入每个房间,即 ,则 。
那么老师最坏情况下的期望即为:
证毕。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=40; const double eps=1e-10; int a[M]; int n; bool check(double mid){ double sum=0; for(int i=1;i<=n;i++){ if(a[i]<=mid)continue; sum+=(1.0*a[i]-mid)/(1.0*a[i]); } if(sum<=1)return 1; else return 0; } int main(){ n=read(); double R=0; for(int i=1;i<=n;i++)a[i]=read(),R+=a[i]; double L=0; for(int i=1;i<=1000;i++){ double mid=(L+R)/2; if(check(mid))R=mid; else L=mid; } cout<<fixed<<setprecision(10)<<L; return 0; }
T3 数数
首先需要满足 单调不降,且 ,这个比较容易理解。
然后我们考虑DP,从小到大向序列中加入每个数,首先考虑暴力地枚举每个序列的填入状态,那么对于当前填入的数 ,设 到 等于 ,那么需要满足插入这个数之前序列最大 段正好为 ,插入后正好为 ;如果没有 则需要满足插入前后最大 段长度不变。等于 是因为若最大 段小于 ,则所有长度为 的段最小值一定小于 ,若大于 ,则之后一个大于 的数会插入到一个空段里,使得 ,所以只能等于 。然后等于 我们对 同样考虑,是同理的,然后判断符合条件才能转移。
这样做复杂度是 的,考虑怎么优化,发现我们只关注这个序列每个 段的长度,我们只枚举本质不同的状态,这样总状态数不超过 个,然后就可以通过了。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=60; const int N=3e5+10; const int mod=998244353; int a[M],F[M]; int l[M],r[M]; ll dp[N]; int cnt=0; map<vector<int>,int>id; vector<int>pos[N]; int main(){ int n=read(); for(int i=1;i<=n;i++){ F[i]=read(); if(i>1&&F[i]>F[i-1])return !puts("0"); if(F[i]>n-i+1)return !puts("0"); } for(int i=2;i<=n;i++) if(F[i]!=F[i-1]){ l[F[i]]=i; r[F[i-1]]=i-1; } l[F[1]]=1; r[F[n]]=n; vector<int>st; st.emplace_back(n); id[st]=++cnt; pos[cnt]=st; dp[cnt]=1; for(int i=1;i<=cnt;i++){ int siz=pos[i].size(); if(!l[siz]){ for(int j=0;j<siz;j++){ int len=pos[i][j]; for(int x=1;x<=len;x++){ vector<int>now=pos[i]; now.erase(now.begin()+j); now.emplace_back(x-1);now.emplace_back(len-x); sort(now.begin(),now.end()); if(now[siz]==pos[i][siz-1]){ if(id.find(now)==id.end()){ id[now]=++cnt; pos[cnt]=now; } dp[id[now]]=(dp[id[now]]+dp[i])%mod; } } } } else if(pos[i][siz-1]==r[siz]){ for(int j=0;j<siz;j++){ int len=pos[i][j]; for(int x=1;x<=len;x++){ vector<int>now=pos[i]; now.erase(now.begin()+j); now.emplace_back(x-1);now.emplace_back(len-x); sort(now.begin(),now.end()); if(now[siz]==l[siz]-1){ if(id.find(now)==id.end()){ id[now]=++cnt; pos[cnt]=now; } dp[id[now]]=(dp[id[now]]+dp[i])%mod; } } } } } cout<<dp[cnt]; return 0; }
T4
咕
11.7 模拟13 A层联测26
40+0+20+17=77pts rk19
区间问题专题赛,全打的暴力。
T1 完全想偏了,QAQ、
T1 origen
首先把原序列变为前缀异或,即令 ,那么此时的区间 的异或和就变为 。
然后我们考虑求 前与 异或后的平方贡献,按位考虑,设当前考虑的为 两位, 二进制这两位分别为 ,则 前面所有 位分别为 的数都会有一个 的贡献,开桶维护这样的个数即可,时间复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=2e5+10; const int V=20; const int mod=998244353; int a[M]; ll _2[M]; int mp[V][V][2][2]; ll Ans; int main(){ int n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++)a[i]=a[i]^a[i-1]; _2[0]=1; for(int i=1;i<=38;i++)_2[i]=_2[i-1]*2%mod; for(int pos=0;pos<=n;pos++){ for(int i=0;i<=17;i++){ for(int j=0;j<=17;j++){ int x=((a[pos]>>i)&1),y=((a[pos]>>j)&1); Ans=(Ans+(_2[i+j]*mp[i][j][1-x][1-y]%mod))%mod; mp[i][j][x][y]++; } } } cout<<Ans; return 0; }
T2 competition
因为每个人能做出题的范围有交,不好搞,转化为枚举每个题有多少概率被做出来。(虽然有 个题。。)
先设 ,表示从长度为 的区间选出一个左右端点可重合的子区间的方案数。
那么对于每一道题,将所有人拍在一个长度为 的序列上,能做出来的人的编号处为 ,其他位置为 ,那么概率就是 ,其中 是每一个极长 段的长度,答案就是
考虑优化求 的复杂度,考虑动态维护 ,发现从一个题转到下一个题时,只会被一个新的 包含或从一个旧的 跳出来,单次只会改变 的点,对于加入一个 ,就是先删去整段的贡献,再加上分出来的两段的贡献,直接使用 或 查前驱后继动态维护即可,复杂度 。
考虑优化枚举题的复杂度,发现完全没必要一个一个枚举题,你的 改变一定是遇见一个 或者 ,数量级是 的,离散化一下就行,复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } il void write(ll x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } const int inf=1ll<<30; const int M=1e6+10; const int mod=1e9+7; il ll fast_pow(ll x,int a){ ll ans=1; while(a){ if(a&1)ans=(ans*x)%mod; x=x*x%mod; a>>=1; } return ans; } struct node{ ll l,r; int id; }a[M]; ll b[M<<1]; set<int>st; ll f[M]; il bool cmp1(const node &x,const node &y){ return x.l<y.l; } ll Ans=0,tot=0; priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>> >q; main(){ int n=read(); ll m=read(); for(int i=1;i<=n;i++){ a[i].l=read(),a[i].r=read(); a[i].id=i; f[i]=(1ll*i*(i+1)/2)%mod; b[++tot]=a[i].l; b[++tot]=a[i].r; } b[++tot]=m; st.insert(0); st.insert(n+1); ll now=f[n]; sort(a+1,a+1+n,cmp1); sort(b+1,b+1+tot); tot=unique(b+1,b+1+tot)-b-1; int pos=1; for(int i=1;i<=tot;i++){ Ans=(Ans+now*((b[i]-b[i-1]-1)%mod)%mod)%mod; while(a[pos].l==b[i]){ int x=a[pos].id; int pre=*--st.lower_bound(x); int nxt=*st.upper_bound(x); now=(now-f[nxt-pre-1]+mod)%mod; now=(now+f[x-pre-1])%mod; now=(now+f[nxt-x-1])%mod; st.insert(x); q.push(make_pair(a[pos].r,a[pos].id)); ++pos; } Ans=(Ans+now)%mod; while(!q.empty()&&q.top().first==b[i]){ int x=q.top().second; int pre=*--st.lower_bound(x); int nxt=*st.upper_bound(x); now=(now-f[x-pre-1]+mod)%mod; now=(now-f[nxt-x-1]+mod)%mod; now=(now+f[nxt-pre-1])%mod; st.erase(x); q.pop(); } } m%=mod; Ans=((1ll*m*f[n]%mod)-Ans+mod)%mod; cout<<(1ll*Ans*fast_pow((1ll*f[n])%mod,mod-2))%mod; return 0; }
T3 tour
咕
T4 abstract
咕
11.8 模拟14 A层联测27
25+0+0+0=25pts rk39
二刺猿场?
把全部时间押在了 T1 上,前两个小时打点分树套树剖套线段树 ,后两个小时想到 开始打实现,最后差5分钟没调出来,太恼了。。。
T1 kotori(五河琴里)
考虑处理出这些关键点到它们公共 的路径组成的连通块的编号的最小值,设为 ,容易发现是单调不升的,加入每个关键点时,有两种情况:
如果不改变公共 ,就只需把其到 一条链上的编号加入连通块并更新 。
否则需要更改原 和新加入点到新 的两条链。
更改时可以直接暴力跳父亲,然后更新每个节点的 数组,暴力跳的时候如果之前访问过就直接 即可,这样更新的均摊复杂度是 的。
然后查询只需查询该点到当前公共 路径上的最小值,倍增实现,和当前 取最小即为答案。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e6+10; struct node{ int v,nxt; }e[M<<1]; int head[M],cnt(0); il void add(int u,int v){ e[++cnt].v=v; e[cnt].nxt=head[u]; head[u]=cnt; } int n,m; bool vis[M]; int f[M][20],mn[M][20],fa[M]; namespace HLD{ int top[M],dep[M],son[M],siz[M]; void dfs1(int x,int fat){ fa[x]=fat; f[x][0]=fa[x]; mn[x][0]=min(x,fa[x]); for(int i=1;i<=19;i++){ f[x][i]=f[f[x][i-1]][i-1]; mn[x][i]=min(mn[x][i-1],mn[f[x][i-1]][i-1]); } siz[x]=1; dep[x]=dep[fat]+1; for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fat)continue; dfs1(y,x); siz[x]+=siz[y]; if(!son[x]||siz[y]>siz[son[x]])son[x]=y; } } void dfs2(int x,int tpx){ top[x]=tpx; if(!son[x])return; dfs2(son[x],tpx); for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fa[x]||y==son[x])continue; dfs2(y,y); } } int lca(int x,int y){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]])swap(x,y); x=fa[top[x]]; } return dep[x]<dep[y]?x:y; } } int now_ans,pnt,Lca; il int query(int x){ int llca=HLD::lca(Lca,x); int len=HLD::dep[x]-HLD::dep[llca]; int now=x; int xmn=inf; for(int i=0;i<=19;i++){ if((len>>i)&1){ xmn=min(xmn,mn[now][i]); now=f[now][i]; } } len=HLD::dep[Lca]-HLD::dep[llca]; now=Lca; for(int i=0;i<=19;i++){ if((len>>i)&1){ xmn=min(xmn,mn[now][i]); now=f[now][i]; } } return min(xmn,now_ans); } main(){ freopen("kotori.in","r",stdin); freopen("kotori.out","w",stdout); n=read(),m=read(); for(int i=1;i<n;i++){ int u=read(),v=read(); add(u,v); add(v,u); } for(int i=1;i<=n;i++) for(int j=0;j<=19;j++) mn[i][j]=inf; now_ans=inf; HLD::dfs1(1,0); HLD::dfs2(1,1); int las=0; int tot=0; while(m--){ int opt=read(),x=read(); x=(x+las)%n+1; if(opt==1){ pnt++; if(pnt==1){ Lca=x; now_ans=min(now_ans,Lca); vis[x]=1; continue; } int newlca=HLD::lca(Lca,x); now_ans=min(now_ans,x); now_ans=min(now_ans,Lca); if(x!=newlca){ int now=f[x][0]; while(!vis[now]&&now!=newlca){ vis[now]=1; now_ans=min(now_ans,now); now=f[now][0]; } } if(Lca!=newlca){ int now=f[Lca][0]; while(!vis[now]&&now!=newlca){ vis[now]=1; now_ans=min(now_ans,now); now=f[now][0]; } } vis[newlca]=1; now_ans=min(now_ans,newlca); Lca=newlca; } else if(opt==2){ las=query(x); cout<<las<<'\n'; } } return 0; } /* */
T2 charlotte(友利奈绪)
考虑枚举最后被移动到哪个点,设为 ,从 开始 树形 DP,设 表示以 为根的子树中有多少个棋子, 表示 ,表示把子树中的所有棋子都移动到 所需要的步数,我们肯定不会去选择有父子关系的棋子,因为对 贡献为 ,而选择不同的子树的棋子会让 减少 ,所有总操作次数是确定的,为 ,考虑转移。
设 表示 的子树不在 的棋子的个数的最小值,那么如果操作前对于每一个 ,都满足 (加 是因为我们还需要从 跳到 , 个棋子都要再跳 ),那么每一颗子树都可以被删干净,所以最后 就是 的奇偶性( 为奇数时会剩一个棋子没移动到 ,否则都会移动到 )。否则只会有一个不满足条件,则这个子树就还会剩下棋子,这些棋子的个数就是 ,最后若 则用 更新答案,复杂度 。
然后我们考虑换根 DP 优化,就是考虑使用 和 推出来所有点的 和 。
以下设 表示当前节点, 表示 的父亲节点, 和 表示子树里面的 之和的最小值和 之和,用 和 表示 上边那个以 为根的子树的 和 , 和 表示上下所有子树的 和 , 表示 子树中 的最大值, 表示次大值。
我们已知 的所有信息,考虑怎么转移到 。
首先 是好转移的, 子树内的 第一次 的时候已经求出,考虑 以 为根的那颗新树,分为除去 外 的其他子树和 上边的子树,分别计算,然后它们还需要再跳到 ,即:
然后考虑怎么处理 ,发现 在原来的 上只增加了一颗 树,它需要先把 子树剔除除去,然后再把 上面的子树加进来,因此我们除了维护子树最大值外,还要维护次大值,因为我们的最大值可能被当成 删了,拿子树最大(次大)和子树的 取 即可维护出 ,然后就可以用同样的方法维护出 ,然后就换完根了。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long #define int long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<50; const int M=1e6+10; struct node{ int v,nxt; }e[M<<1]; int head[M],cnt(0); il void add(int u,int v){ e[++cnt].v=v; e[cnt].nxt=head[u]; head[u]=cnt; } char ch[M]; int f[M],g[M],siz[M],mx[M],smx[M]; int upg[M],upf[M],totf[M],totg[M]; void dfs(int x,int fa){ siz[x]=ch[x]-'0'; g[x]=f[x]=0; mx[x]=smx[x]=0; for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fa)continue; dfs(y,x); siz[x]+=siz[y]; g[x]+=g[y]+siz[y]; if(f[y]+g[y]+2*siz[y]>mx[x])smx[x]=mx[x],mx[x]=f[y]+g[y]+2*siz[y]; else if(f[y]+g[y]+2*siz[y]>smx[x])smx[x]=f[y]+g[y]+2*siz[y]; } if(mx[x]<g[x])f[x]=g[x]%2; else f[x]=mx[x]-g[x]; } void chg(int x,int fa){ if(x!=1){ upg[x]=g[fa]-g[x]-siz[x]+upg[fa]+siz[1]-siz[x]; totg[x]=upg[x]+g[x]; int umx,udis=totg[fa]-g[x]-siz[x]; if(f[x]+g[x]+2*siz[x]==mx[fa])umx=max(smx[fa],upf[fa]+upg[fa]); else umx=max(mx[fa],upf[fa]+upg[fa]); if(umx<udis)upf[x]=udis%2; else upf[x]=umx-udis; upf[x]+=siz[1]-siz[x]; int totmx=max(mx[x],upf[x]+upg[x]); if(totmx<totg[x])totf[x]=totg[x]%2; else totf[x]=totmx-totg[x]; } for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fa)continue; chg(y,x); } } main(){ int n=read(); cin>>ch+1; for(int i=1;i<n;i++){ int u=read(),v=read(); add(u,v); add(v,u); } int Ans=inf; dfs(1,0); totf[1]=f[1]; totg[1]=g[1]; chg(1,0); for(int i=1;i<=n;i++){ if(totf[i]==0){ Ans=min(Ans,(totg[i])/2); } } if(Ans==inf)puts("-1"); else cout<<Ans; return 0; }
T3 sagiri(和泉纱雾)
咕
T4 chtholly(珂朵莉)
咕
11.9 模拟15 ZR联赛集训 day1
100+25+0+100=225pts rk5
T1 打表找规律列的同余方程,赛时以为假了。
T2 暴力,没往后想了。
T3 。
T4 诈骗题,在 11:57 才优化到 。
T1 数字变换
我们稍微枚举 大力打表 一下就能发现每次序列的状态都是形如 的形式(对 取模),且满足 ,其中 是操作次数。
那么我们就能分别对二元组的两个数列出有解时的同余方程:
二者相加可得 。
则问题有解必须要满足 和 同余。
将 带入第一个方程:
然后 在模数范围内枚举 ,对于每个 , 求出 的最小整数解,判断是否 即可(这样我们就不用管第二个方程啦)。
时间复杂度大概是 的。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; ll _2[100]; ll x,y; void ex_gcd(ll a,ll b){ if(!b){ x=1;y=0; return; } ex_gcd(b,a%b); ll k=x; x=y;y=k-(a/b)*y; return; } int main(){ freopen("a.in","r",stdin); freopen("a.out","w",stdout); int p=read(),q=read(); _2[0]=1; for(int i=1;i<=32;i++)_2[i]=(1ll*_2[i-1]*2); while(q--){ ll a=read(),b=read(),c=read(),d=read(); if((a+b-c-d)%p!=0)puts("-1"); else{ for(int k=0;k<=32;k++){ ll A=(a+b)%p,B=p,C=((c+(_2[k]*b%p)-b-a)%p+p)%p; int gd=__gcd(A,B); if(C%gd!=0)continue; ex_gcd(A,B); x=x*C/gd; ll K=B/gd; ll X=(x%K+K)%K; if(X+1>_2[k])continue; cout<<k<<'\n'; goto miku; } puts("-1"); miku:continue; } } return 0; }
T2 均分财产
(25pts):
直接从前往后枚举每个数,有放入 集合,放入 集合,扔了不管三种选择,然后判断是否合法即可。
(25pts):
枚举集合状态,对于每一个枚举到的状态 ,可以从删去最高位 的状态转移过来,因为这个状态之前肯定求过,这个可以用 求出来,然后转移就是 的了。
然后我们记录每个状态的 ,维护每个和为 有哪几个状态,然后判断如果 并且 即找到合法方案,直接输出即可。
(100pts) :
首先从大到小排序,然后先贪心地把前 个较平均地划分进两个集合中,即若 就加进 集合,否则加入 集合。
这样我们保证最后二者的差的绝对值一定 ,加入的 个数后,差最大不会超过 ,而这 种状态是远大于 的,因为保证数据随机,所以就相当于把许多点均匀撒到一个较小的区间内,每个数都近乎 会被撒到,所以直接对后 个数做 的暴力就有近乎 概率找到状态之差正好等于当前集合之差(绝对值)的两个状态,直接输出答案即可,复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long #define popc(x) __builtin_popcount(x) il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=2e5+10; const int V=33560000; pair<int,int> a[M]; int val[30]; int n,k,N,pre; int cnt1,cnt2; ll pre1[M],pre2[M],prent1(0),prent2(0),presum1(0),presum2(0); int mp[V]; vector<int>vis[(1<<20)+10]; il int upbit(int s){ return (32-__builtin_clz(s)); } il void output(int s,int t){ cnt1=popc(s),cnt2=popc(t); cout<<prent1+cnt1<<' '; for(int i=1;i<=prent1;i++)cout<<a[pre1[i]].second<<' '; for(int i=1;i<=N;i++) if((s>>(i-1))&1)cout<<a[pre+i].second<<' '; puts(""); cout<<prent2+cnt2<<' '; for(int i=1;i<=prent2;i++)cout<<a[pre2[i]].second<<' '; for(int i=1;i<=N;i++) if((t>>(i-1))&1)cout<<a[pre+i].second<<' '; } il void solve(){ for(int s=1;s<(1<<N);s++){ int pos=upbit(s); int T=s-(1<<(pos-1)); if(val[pos]+mp[T]+presum1-presum2>=0) for(auto const&t:vis[val[pos]+mp[T]+presum1-presum2]) if(popc(s)+popc(t)>=N-k&&(!(s&t))){ output(s,t); exit(0); } mp[s]=val[pos]+mp[T]; vis[mp[s]].emplace_back(s); } } int main(){ freopen("b.in","r",stdin); freopen("b.out","w",stdout); n=read(),k=read(); for(int i=1;i<=n;i++){a[i].first=read();a[i].second=i;} if(n<=25){ for(int i=1;i<=n;i++)val[i]=a[i].first; N=n; solve(); puts("-1"); } else{ sort(a+1,a+1+n,greater<pair<int,int>>()); for(int i=1;i<=n-25;i++){ if(presum1<=presum2){ pre1[++prent1]=i; presum1+=a[i].first; } else { pre2[++prent2]=i; presum2+=a[i].first; } } int tot=0; for(int i=n-24;i<=n;i++)val[++tot]=a[i].first; N=25; pre=n-25; solve(); puts("-1"); } return 0; }
T3 查询工资
树形 DP。
首先一个节点如果没有其它的兄弟,且 ,那它就可以被算出来,计算方法是先算一遍它的父亲的子树,再算一遍它的子树,二者作差即可。
然后再考虑一种比较复杂的情况。如果一个节点 的父亲的儿子数量 ,且 兄弟们要么 ,要么 ,且该节点只有一个儿子 ,则这个 节点的权值也是可以算出来的,计算方法是先算一遍它父亲的子树,再算一遍它父亲的儿子,对于每一个兄弟, 不管, 则算一遍它的子树,最后再算一遍 的子树, 就求出来了。
那么转移的时候可以有三种决策:
-
什么都不做,由所有儿子的 和更新。
-
删得只剩一个 的子树,由 更新。
-
如果 ,就可以找到 最小的 的子树,由其他儿子的 和再加 更新。
然后就能转移了,最后的答案就是 ,时间复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=8e5+10; struct node{ int v,nxt; }e[M]; int head[M],cnt(0); il void add(int u,int v){ e[++cnt].v=v; e[cnt].nxt=head[u]; head[u]=cnt; } int n,k; int dp[M],siz[M]; void dfs(int x){ siz[x]=1; int sum=0,mn=inf,son(0); for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; dfs(y); son++; siz[x]+=siz[y]; sum+=dp[y]; if(siz[y]>k)dp[x]=max(dp[x],dp[y]+1); if(siz[y]>=2)mn=min(mn,dp[y]); } dp[x]=max(dp[x],sum); if(son>=k)dp[x]=max(dp[x],sum-mn+1); } int main(){ freopen("c.in","r",stdin); freopen("c.out","w",stdout); int Q=read(); while(Q--){ n=read(),k=read(); for(int i=1;i<=n;i++)dp[i]=head[i]=0; cnt=0; for(int i=2;i<=n;i++){ int fa=read(); add(fa,i); } dfs(1); cout<<dp[1]<<'\n'; } return 0; }
T4 多项式题
多项式个锤子。
考虑 dp,设 表示 区间的子串乘积之和,设 表示 的子串组成的数字。
转移考虑枚举 之前的一个分段点 ,钦定 为一整段,则 可以由 转移过来,即状态转移方程为:
这样直接转移是 考虑前缀和优化。
首先有:
那么:
我们设 ,则 ,可以 转移。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int M=2e5+10; const int mod=998244353; int n; char s[M]; ll dp[M],g[M]; int main(){ n=read(); cin>>s+1; dp[0]=0;g[0]=1; for(int i=1;i<=n;i++){ dp[i]=((dp[i-1]*10)%mod+(g[i-1]*(s[i]-'0'))%mod)%mod; g[i]=(g[i-1]+dp[i])%mod; } cout<<dp[n]; return 0; }
11.10 模拟16 A层联测28
90+100+15+74=279pts rk11
T1 之前一道题的弱化版,数组没开 倍挂 10pts
T2 找性质然后DP,过拍就交了
T3 暴力,结果没判 -1
还挂分了
T4 敲的不带修分块,挺有意思的一道题。
T1 西鸹喝水
发现 以内的因数只有 个,就能想到 的做法。
首先将原序列改为前异或,问题变为找点对,然后直接开桶查前面 的数的个数即可。
注意因为是异或所以可能比 大,桶大小需要开 倍。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define int long long #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e5+10; int frac[M],tot(0); int a[M]; int mp[M<<1]; main(){ int n=read(),k=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++)a[i]^=a[i-1]; for(int i=1;i<=k;i++){ if(k%i==0) frac[++tot]=i; } ll ans=0; for(int i=0;i<=n;i++){ for(int j=1;j<=tot;j++) ans+=mp[frac[j]^a[i]]; mp[a[i]]++; } cout<<ans; return 0; }
T2 青鸹
首先发现一个性质就是选择合并的两个数之间只能中间隔着奇数个数(也就是奇偶性相同),这样才能通过 2
操作不断删除中间元素再合并。
考虑 DP,设 表示前 个数能选出的最大毒瘤系数, 表示取到最大系数的方案数,可以发现再在只考虑前 个数时, 是唯一确定的,然后分奇偶维护前缀 ,转移即可。
然后枚举每个位置,方案数为 ,找到几个毒瘤系数最大的位置,然后方案数取 即可。
时间复杂度
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const ll inf=1ll<<50; const int M=1e6+10; ll a[M]; ll dp[M],g[M]; pair<ll,int> mx1,mx2; int main(){ int n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++){ dp[i]=a[i]; g[i]=((i-1)/2)+1; } g[1]=0; mx1=make_pair(a[1],1); mx2=make_pair(a[2],2); for(int i=3;i<=n;i++){ if(i%2){ if(mx1.first+a[i]>dp[i]){ dp[i]=mx1.first+a[i]; g[i]=g[mx1.second]+(i-mx1.second)/2; } if(dp[i]>mx1.first){ mx1.first=dp[i]; mx1.second=i; } } else{ if(mx2.first+a[i]>dp[i]){ dp[i]=mx2.first+a[i]; g[i]=g[mx2.second]+(i-mx2.second)/2; } if(dp[i]>mx2.first){ mx2.first=dp[i]; mx2.second=i; } } } ll ans=-inf,step=inf; for(int i=1;i<n;i++){ if(dp[i]>ans){ ans=dp[i]; step=g[i]+(n-i)/2+1; } else if(dp[i]==ans){ step=min(step,g[i]+(n-i)/2+1); } } if(dp[n]>ans){ ans=dp[n]; step=g[n]; } else if(dp[n]==ans){ step=min(step,g[n]); } cout<<ans<<'\n'<<step; return 0; }
T3 大眼鸹猫
首先保证 单调不降的性质是没用的,我们可以按照 的大小关系将 划分为若干段,每一段内 都大于(小于),然后每一段都从前往后(从后往前)选,发现段和段之间互不影响,设 。
先把 的删掉,它们不需要操作。然后对于每个数,当划分段数对应时,一定是越平均分越优,我们先按实数考虑,设当前划分为 段,则需要的最小价值为 ,发现代价 是关于 单调递减的函数,且变化量()递减。
考虑贪心,先每个数只划分为 段,此时代价最高,操作次数最小,若此时 则直接判断为无解。
否则每次取变化量最大的一个 来减少代价,并让操作次数加一,可以证明不会存在一次操作选较小的数会出现更优的情况,这样,我们维护一个大根堆,每次取堆顶,再维护每个数 ,表示每个数当前被分成了几段。
然后考虑整数,此时的代价为 ,其他的一样。
时间复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long #define int long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e5+10; const int mod=998244353; int n,m,tot(0); int a[M],b[M],c[M]; ll ans; priority_queue<pair<ll,int>>q; int now[M]; il ll get_val(ll tot,ll cnt){ ll c1=(tot%cnt)*(ceil((1.0*tot)/(1.0*cnt)))*(ceil((1.0*tot)/(1.0*cnt))); ll c2=(cnt-(tot%cnt))*(tot/cnt)*(tot/cnt); return c1+c2; } main(){ n=read(),m=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++)b[i]=read(); for(int i=1;i<=n;i++) if(a[i]!=b[i])c[++tot]=abs(a[i]-b[i]); ll sum=0,ans=0; for(int i=1;i<=tot;i++){ sum++; ans=(ans+(1ll*c[i]*c[i]%mod))%mod; if(c[i]==1)continue; now[i]=2; q.push(make_pair(get_val(c[i],1)-get_val(c[i],2),i)); } if(sum>m)return !puts("-1"); while(!q.empty()&&sum<m){ pair<int,int> x=q.top(); int pos=x.second; q.pop(); ans=(ans-x.first%mod+mod)%mod; sum++; if(now[pos]==c[pos])continue; now[pos]++; q.push(make_pair(get_val(c[pos],now[pos]-1)-get_val(c[pos],now[pos]),pos)); } cout<<ans; return 0; }
T4 小猫吃火龙果
先看不带修的情况,考虑分块。
我们预处理出每个整块从最左边进去一个 分别会出来什么,对于一次询问,我们散块直接暴力,整块直接跳即可。
然后考虑加上修改后咋做。
发现这玩意没啥规律,我们不能快速处理交换完后的答案,发现只有三个字母,交换只会出现 种情况,我们就在最开始预处理出来每种情况的字母交换后从左边塞进去 会出来什么,总共是 种情况,然后修改时对于整块直接打标记,标记 表示当前块的 字母实际上被换成了 字母, 同理。对于散块我们直接暴力重构,复杂度是对的。
有个问题是一个块前后两次打的标记如何合并,这个东西处理比较麻烦,我们直接打个 的表,表示当前的 种标记再加上 种交换情况后分别变成什么,然后就没了,复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int M=2e5+10; int n,m,block,T; char ch[M]; int st[M],ed[M],pos[M]; int a[M][8][4]; int down[4]; struct node{ int A,B,C; }order[10]; node tag[M]; il int jump(int now,int t){ if(tag[t].A==1&&tag[t].B==2&&tag[t].C==3)return a[t][1][now]; else if(tag[t].A==1&&tag[t].B==3&&tag[t].C==2)return a[t][2][now]; else if(tag[t].A==2&&tag[t].B==1&&tag[t].C==3)return a[t][3][now]; else if(tag[t].A==2&&tag[t].B==3&&tag[t].C==1)return a[t][4][now]; else if(tag[t].A==3&&tag[t].B==1&&tag[t].C==2)return a[t][5][now]; else if(tag[t].A==3&&tag[t].B==2&&tag[t].C==1)return a[t][6][now]; } il void swp(int t){ for(int i=st[t];i<=ed[t];i++){ if(ch[i]=='A')ch[i]='A'+tag[t].A-1; else if(ch[i]=='B')ch[i]='A'+tag[t].B-1; else ch[i]='A'+tag[t].C-1; } tag[t]=(node){1,2,3}; } il void upd(int t,char x,char y){ int posx=x-'A'+1,posy=y-'A'+1; if(posx>posy)swap(posx,posy); if(tag[t].A==1&&tag[t].B==2&&tag[t].C==3){ if(posx==1&&posy==2)tag[t]=(node){2,1,3}; else if(posx==1&&posy==3)tag[t]=(node){3,2,1}; else if(posx==2&&posy==3)tag[t]=(node){1,3,2}; }else if(tag[t].A==1&&tag[t].B==3&&tag[t].C==2){ if(posx==1&&posy==2)tag[t]=(node){2,3,1}; else if(posx==1&&posy==3)tag[t]=(node){3,1,2}; else if(posx==2&&posy==3)tag[t]=(node){1,2,3}; }else if(tag[t].A==2&&tag[t].B==1&&tag[t].C==3){ if(posx==1&&posy==2)tag[t]=(node){1,2,3}; else if(posx==1&&posy==3)tag[t]=(node){2,3,1}; else if(posx==2&&posy==3)tag[t]=(node){3,1,2}; }else if(tag[t].A==2&&tag[t].B==3&&tag[t].C==1){ if(posx==1&&posy==2)tag[t]=(node){1,3,2}; else if(posx==1&&posy==3)tag[t]=(node){2,1,3}; else if(posx==2&&posy==3)tag[t]=(node){3,2,1}; }else if(tag[t].A==3&&tag[t].B==1&&tag[t].C==2){ if(posx==1&&posy==2)tag[t]=(node){3,2,1}; else if(posx==1&&posy==3)tag[t]=(node){1,3,2}; else if(posx==2&&posy==3)tag[t]=(node){2,1,3}; }else if(tag[t].A==3&&tag[t].B==2&&tag[t].C==1){ if(posx==1&&posy==2)tag[t]=(node){3,1,2}; else if(posx==1&&posy==3)tag[t]=(node){1,2,3}; else if(posx==2&&posy==3)tag[t]=(node){2,3,1}; } } il void reget(int t){ for(int o=1;o<=6;o++){ int now1=1,now2=2,now3=3; for(int i=st[t];i<=ed[t];i++){ if(ch[i]=='A'){ if(down[now1]!=order[o].A)now1=order[o].A; if(down[now2]!=order[o].A)now2=order[o].A; if(down[now3]!=order[o].A)now3=order[o].A; }else if(ch[i]=='B'){ if(down[now1]!=order[o].B)now1=order[o].B; if(down[now2]!=order[o].B)now2=order[o].B; if(down[now3]!=order[o].B)now3=order[o].B; }else{ if(down[now1]!=order[o].C)now1=order[o].C; if(down[now2]!=order[o].C)now2=order[o].C; if(down[now3]!=order[o].C)now3=order[o].C; } } a[t][o][1]=now1; a[t][o][2]=now2; a[t][o][3]=now3; } } il void update(int l,int r,char x,char y){ if(pos[l]==pos[r]){ swp(pos[l]); for(int i=l;i<=r;i++){ if(ch[i]==x)ch[i]=y; else if(ch[i]==y)ch[i]=x; } reget(pos[l]); } else{ swp(pos[l]); for(int i=l;i<=ed[pos[l]];i++){ if(ch[i]==x)ch[i]=y; else if(ch[i]==y)ch[i]=x; } reget(pos[l]); for(int t=pos[l]+1;t<=pos[r]-1;t++)upd(t,x,y); swp(pos[r]); for(int i=st[pos[r]];i<=r;i++){ if(ch[i]==x)ch[i]=y; else if(ch[i]==y)ch[i]=x; } reget(pos[r]); } } il char query(int l,int r,char x){ int now; if(x=='A')now=1; else if(x=='B')now=2; else now=3; if(pos[l]==pos[r]){ for(int i=l;i<=r;i++){ if(ch[i]=='A') {if(down[now]!=tag[pos[l]].A)now=tag[pos[l]].A;} else if(ch[i]=='B') {if(down[now]!=tag[pos[l]].B)now=tag[pos[l]].B;} else {if(down[now]!=tag[pos[l]].C)now=tag[pos[l]].C;} } } else{ for(int i=l;i<=ed[pos[l]];i++){ if(ch[i]=='A') {if(down[now]!=tag[pos[l]].A)now=tag[pos[l]].A;} else if(ch[i]=='B') {if(down[now]!=tag[pos[l]].B)now=tag[pos[l]].B;} else {if(down[now]!=tag[pos[l]].C)now=tag[pos[l]].C;} } for(int t=pos[l]+1;t<=pos[r]-1;t++)now=jump(now,t); for(int i=st[pos[r]];i<=r;i++){ if(ch[i]=='A') {if(down[now]!=tag[pos[r]].A)now=tag[pos[r]].A;} else if(ch[i]=='B') {if(down[now]!=tag[pos[r]].B)now=tag[pos[r]].B;} else {if(down[now]!=tag[pos[r]].C)now=tag[pos[r]].C;} } } if(now==1)return 'A'; else if(now==2) return 'B'; else return 'C'; } int main(){ freopen("training.in","r",stdin); freopen("training.out","w",stdout); down[1]=2; down[2]=3; down[3]=1; order[1]=(node){1,2,3}; order[2]=(node){1,3,2}; order[3]=(node){2,1,3}; order[4]=(node){2,3,1}; order[5]=(node){3,1,2}; order[6]=(node){3,2,1}; n=read(),m=read(); cin>>ch+1; block=sqrt(n); block/=3; T=n/block; if(n%block)T++; for(int i=1;i<=T;i++){ st[i]=(i-1)*block+1; ed[i]=i*block; } ed[T]=n; for(int i=1;i<=n;i++)pos[i]=(i-1)/block+1; for(int t=1;t<=T;t++){ reget(t); tag[t]=(node){1,2,3}; } while(m--){ int opt=read(),l=read(),r=read(); if(opt==0){ char x,y; cin>>x>>y; if(x!=y)update(l,r,x,y); } else{ char x; cin>>x; cout<<query(l,r,x)<<'\n'; } } return 0; }
11.11 模拟17 洛谷 2023 模拟测试
100+10+52+12=174pts rk46
T1 种树
考虑约束个数定理,将每个数分解质因数,答案为 。
考虑加上一个 ,将其分解质因数后发现它分解出来的每个单独的质因子会使 加上一个 ,此时将这个 加到 上一定最优,每次从前往后扫一遍,贪心加即可。
时间复杂度
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e4+10; const int mod=998244353; int n; int a[M]; bool vis[M]; int pos[M]; int p[1300],tot(0); int c[M][1300]; il void work(int line){ int mn=inf,pos=-1; for(int i=1;i<=n;i++){ if(c[i][line]<mn){ mn=c[i][line]; pos=i; } } c[pos][line]++; } int main(){ n=read(); int w=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=2;i<=10000;i++){ if(!vis[i]){ p[++tot]=i; pos[i]=tot; } for(int j=1;j<=tot&&i*p[j]<=10000;j++){ vis[i*p[j]]=1; if(i%p[j]==0)break; } } for(int i=1;i<=n;i++){ int x=a[i]; for(int j=2;j*j<=x;j++){ if(x%j==0){ while(x%j==0){ c[i][pos[j]]++; x/=j; } } } if(x!=1){ c[i][pos[x]]++; } } for(int i=2;i*i<=w;i++){ if(w%i==0){ while(w%i==0){ work(pos[i]); w/=i; } } } if(w!=1){ work(pos[w]); } ll ans=1; for(int i=1;i<=n;i++){ for(int j=1;j<=tot;j++){ ans=(1ll*ans*(c[i][j]+1))%mod; } } cout<<ans; return 0; }
T2 汪了个汪
奇妙的构造题。
首先在 个格子中有 个相邻的格子,正好对应了 以内任意两个数组成的无序二元组个数,也就是要把无序二元组全都放进去。
然后我们发现差为 的无序二元组个数为 ,差为 的有 个... 差为 的有一个。这正好对应了每一行相邻数的个数。
但是我们显然不能直接放进去,考虑横着从左往右依次放差为 ,差为 到差为 的数,发现这样是可以构造出来形如
的排列,并且不同的 作为开头其合法的排列长度互不相同,长度从 到 排序输出即可。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=4010; int n,t; int main(){ n=read(),t=read(); for(int i=1;i<=n;i++){ int x; if(i%2)x=(2*n+1-i)/2; else x=i/2; int now=1; for(int j=1;j<=i;j++){ cout<<x<<' '; if(j%2)x+=now; else x-=now; now++; } puts(""); } return 0; }
11.13 模拟18 A层联测30
100+0+0+0=100tps rk5
T1 草莓列车
可以直接上线段树打标记(不是吉司机)
维护标记从后往前扫就行。
:
直接上线段树不太行,但是数据随机,所以大概后面的很多次操作没什么用,在线段树上剪个枝能卡过去。(小清新线段树?)
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } il void write(ll x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } const int inf=1ll<<30; const int M=1e5+10; namespace Maker{ unsigned int x0,seed; void init(){scanf("%u%u",&x0,&seed);} inline unsigned int getnum(){ x0=(x0<<3)^x0; x0=((x0>>5)+seed)^x0; return x0; } } int n,m,typ; ll a[M]; ll tag[M]; namespace Segment_Tree{ ll tag[M<<2]; il void push_down(int k){ if(tag[k]){ tag[k<<1]=max(tag[k<<1],tag[k]); tag[k<<1|1]=max(tag[k<<1|1],tag[k]); tag[k]=0; } } void update(int k,int l,int r,int L,int R,ll val){ if(tag[k]>=val)return; if(L<=l&&r<=R){ tag[k]=val; return; } int mid=(l+r)>>1; push_down(k); if(L<=mid)update(k<<1,l,mid,L,R,val); if(R>mid) update(k<<1|1,mid+1,r,L,R,val); } void output(int k,int l,int r){ if(l==r){ write(max(tag[k],a[l])); putchar(' '); return; } int mid=(l+r)>>1; push_down(k); output(k<<1,l,mid); output(k<<1|1,mid+1,r); } } int main(){ freopen("train.in","r",stdin); freopen("train.out","w",stdout); n=read(),m=read(),typ=read(); for(int i=1;i<=n;i++)a[i]=read(); Maker::init(); for(int i=1;i<=m;++i){ int l=Maker::getnum()%n+1,r=Maker::getnum()%n+1; unsigned int v=Maker::getnum(); if(l>r)swap(l,r); if(typ==1)l=1; Segment_Tree::update(1,1,n,l,r,v); } Segment_Tree::output(1,1,n); return 0; }
正解考虑如何做到 进行修改,最后 查询。考虑反向 ST 表,每次修改在 和 处打标记,查询时将标记不断下放,最后让 和 取 即可。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il unsigned int read(){ unsigned int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e5+10; namespace Maker{ unsigned int x0,seed; void init(){scanf("%u%u",&x0,&seed);} inline unsigned int getnum(){ x0=(x0<<3)^x0; x0=((x0>>5)+seed)^x0; return x0; } } int n,m,typ; int a[M]; unsigned int st[20][M]; int lg[M]; int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); freopen("train.in","r",stdin); freopen("train.out","w",stdout); n=read(),m=read(),typ=read(); for(int i=1;i<=n;++i)a[i]=read(); for(int i=1;i<=n;++i)lg[i]=log2(i); Maker::init(); for(int i=1;i<=m;++i){ int l=Maker::getnum()%n+1,r=Maker::getnum()%n+1; unsigned int v=Maker::getnum(); if(l>r)swap(l,r); if(typ==1)l=1; int k=lg[r-l+1]; st[k][l]=max(st[k][l],v); st[k][r-(1<<k)+1]=max(st[k][r-(1<<k)+1],v); } for(int k=19;k>=1;k--){ for(int i=1;i+(1<<k)-1<=n;i++){ st[k-1][i]=max(st[k-1][i],st[k][i]); st[k-1][i+(1<<(k-1))]=max(st[k-1][i+(1<<(k-1))],st[k][i]); } } for(int i=1;i<=n;i++)cout<<max<unsigned int>(st[0][i],a[i])<<' '; // cerr<<'\n'<<1.0*clock()/CLOCKS_PER_SEC; return 0; }
11.14 模拟19 A层联测31
70+50+0+0=120pts rk15
T1 暴力操作
考虑二分答案,对于每个 一定是操作前 小的数最优,将 排序,操作前 个数使其小于 ,每个数的操作次数为 ,需要预处理出除 倍的最小花费,类似于埃氏筛一样,,然后再取后缀 ,注意上界可能超过 ,全加到 处即可,时间复杂度
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e6+10; int n,m,K; int a[M],c[M],cc[M]; ll bck[M],val[M]; bool vis[M]; il bool check(int mid){ ll cst=0; for(int i=1;i<=(n+1)/2;i++){ if(a[i]<=mid)continue; else{ int x=ceil((1.0*(a[i]+1))/(1.0*(mid+1))); cst+=val[x]; } } return cst<=K; } int main(){ freopen("opt.in","r",stdin); freopen("opt.out","w",stdout); n=read(),m=read(),K=read(); for(int i=1;i<=n;i++)a[i]=read(); sort(a+1,a+1+n); for(int i=1;i<=m;i++)c[i]=read(); for(int i=1;i<=m;i++)val[i]=c[i]; val[m+1]=inf; for(int i=1;i<=m;i++){ for(int j=1;j*i<=m;j++){ val[i*j]=min(val[i*j],val[i]+val[j]); } } for(int i=m-1;i>=1;i--)val[i]=min(val[i+1],val[i]); for(int i=1;i<=m;i++){ val[m+1]=min(val[m+1],val[m/i]+val[i]); } for(int i=m;i>=1;i--)val[i]=min(val[i+1],val[i]); int L=0,R=1e9,ans=0; while(L<=R){ int mid=(L+R)>>1; if(check(mid)){ R=mid-1; ans=mid; } else L=mid+1; } cout<<ans<<'\n'; return 0; }
T2 异或连通
分别考虑每一条边,有多少个 是符合要求的,即 ,给所有 建一颗 树,可以发现在每条边二进制的每一位上,如果 的当前位为 ,则可以选择 树上当前位与 相同的子树,它们的前几位都和 相同,当前位和 异或为 ,则它们异或上 后都小于 ,我们把这些子树看成一个个区间,把 树看成以 为轴的线段树,可以发现区间的数量是 级别的,考虑使用线段树分治,将所有询问排序离线下来,拿可撤销并查集维护当前节点的答案即可。
时间复杂度
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e5+10; const int V=35; int n,m,q,K; struct node{ int u,v,w; }e[M]; pair<int,int> que[M]; ll Ans[M]; namespace Segment_Tree{ vector<int>t[M<<2]; ll ans=0; namespace DSU{ struct Node{ int x,y,sizx,sizy; ll sum; int add; }stk[M*V]; int top(0); int fa[M],siz[M],h[M]; il void build(){ iota(fa+1,fa+1+n,1); fill(siz+1,siz+1+n,1); fill(h+1,h+1+n,1); } int find(int x){ if(x==fa[x])return x; else return find(fa[x]); } il void merge(int x,int y){ int fx=find(x),fy=find(y); if(fx==fy)return; ll sv=ans; ans+=1ll*siz[fx]*siz[fy]; if(h[fx]>h[fy])swap(fx,fy); stk[++top]=(Node){fx,fy,siz[fx],siz[fy],sv,(h[fx]==h[fy])}; fa[fx]=fy; siz[fy]+=siz[fx]; if(h[fx]==h[fy])h[fy]++; } il void del(int now){ while(top>now){ fa[stk[top].x]=stk[top].x; siz[stk[top].x]=stk[top].sizx; siz[stk[top].y]=stk[top].sizy; ans=stk[top].sum; h[fa[stk[top].y]]-=stk[top].add; top--; } } } il void update(int k,int l,int r,int L,int R,int x){ if(L<=l&&r<=R){ t[k].emplace_back(x); return; } int mid=(l+r)>>1; if(L<=mid)update(k<<1,l,mid,L,R,x); if(R>mid)update(k<<1|1,mid+1,r,L,R,x); } il void solve(int k,int l,int r){ int sv=DSU::top; for(auto const&x:t[k]){ DSU::merge(e[x].u,e[x].v); } if(l==r)Ans[que[l].second]=ans; else{ int mid=(l+r)>>1; solve(k<<1,l,mid); solve(k<<1|1,mid+1,r); } DSU::del(sv); } } int main(){ freopen("xor.in","r",stdin); freopen("xor.out","w",stdout); n=read(),m=read(),q=read(),K=read(); for(int i=1;i<=m;i++){ e[i].u=read(),e[i].v=read(),e[i].w=read(); } for(int i=1;i<=q;i++)que[i].first=read(),que[i].second=i; sort(que+1,que+1+q); Segment_Tree::DSU::build(); for(int i=1;i<=m;i++){ for(int j=30;j>=0;j--){ if((K>>j)&1){ int now=(K>>(j+1))<<(j+1); int l=((e[i].w^now)>>j)<<j; int r=l+(1<<j)-1; l=lower_bound(que+1,que+1+q,make_pair(l,0))-que; r=lower_bound(que+1,que+1+q,make_pair(r+1,0))-que-1; if(l<=r)Segment_Tree::update(1,1,q,l,r,i); } } } Segment_Tree::solve(1,1,q); for(int i=1;i<=q;i++)cout<<Ans[i]<<'\n'; return 0; }
11.15 模拟20 A层联测32
70+5+25=100pts
T1 flandre
考虑贪心,将 从小到大排序后从后往前选,答案一定是它的一个后缀,因为如果跳过当前的数去选前一个(),它们对后面的贡献是相同的,但是它自己的贡献会变小。从前往后扫一遍即可。
对于相同元素,记录每个元素出现的次数,然后给数组去重即可。
时间复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=1e6+10; int a[M]; ll ans=0; int cnt=0; vector<int> num[M*2+1000]; int pos[M],ant(0); int main(){ freopen("flandre.in","r",stdin); freopen("flandre.out","w",stdout); int n=read(),k=read(); for(int i=1;i<=n;i++){ a[i]=read(); num[a[i]+M].emplace_back(i); } sort(a+1,a+1+n); int len=unique(a+1,a+1+n)-(a+1); for(int i=len;i>=1;i--){ if(1ll*cnt*k+a[i]>=0){ int sz=num[a[i]+M].size(); ans+=1ll*(1ll*cnt*k+a[i])*sz; cnt+=sz; for(auto const&x:num[a[i]+M])pos[++ant]=x; } else break; } cout<<ans<<" "<<ant<<'\n'; while(ant)cout<<pos[ant--]<<' '; return 0; }//你说得对,但是被hack了
T2 meirin
被诈骗了,正解 的。。。
其中 表示同时包含 的区间数。
当 时,,当 时,。
我们预处理出 的前缀和 和 的后缀和 ,则答案为:
可以 计算。
然后考虑怎么修改,发现给一个 加上一个 ,答案会加上 ,维护这玩意的前缀和,每次前缀和差分 修改即可。
时间复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=5e5+10; const int mod=1e9+7; int a[M],b[M]; int pre[M],back[M],as[M]; main(){ freopen("meirin.in","r",stdin); freopen("meirin.out","w",stdout); int n=read(),m=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++)b[i]=read(); ll Ans=0; for(int i=1;i<=n;i++)pre[i]=(pre[i-1]+1ll*a[i]*i%mod)%mod; for(int i=n;i>=1;i--)back[i]=(back[i+1]+1ll*a[i]*(n-i+1)%mod)%mod; for(int i=1;i<=n;i++){ as[i]=(1ll*pre[i]*(n-i+1)%mod+1ll*back[i+1]*i%mod)%mod; Ans=(Ans+1ll*b[i]*as[i]%mod)%mod; as[i]=(as[i]+as[i-1])%mod; } for(int i=1;i<=m;i++){ int l=read(),r=read(),x=read(); Ans=(Ans+1ll*(as[r]-as[l-1])*x%mod)%mod; cout<<(Ans+mod)%mod<<'\n'; } return 0; }
T3 sakuya
首先一条边的贡献是固定的,设该边的子树内的关键点个数为 ,则这条边被选到的概率为:
我喜欢你~
最初当答案为 ,考虑修改,将所有与点 相连的边加上一个 ,答案加上 ,提前处理出每个点的所有与该点相连边的概率和。
时间复杂度 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=5e5+10; struct node{ int w,v,nxt; }e[M<<1]; int head[M],cnt(0); il void add(int u,int v,int w){ e[++cnt].v=v; e[cnt].w=w; e[cnt].nxt=head[u]; head[u]=cnt; } int n,m; bool sp[M]; int siz[M]; const int mod=998244353; ll f[M],Ans=0; il int fast_pow(int x,int a){ int ans=1; while(a){ if(a&1)ans=(1ll*ans*x)%mod; x=(1ll*x*x)%mod; a>>=1; } return ans; } void dfs(int x,int fa){ if(sp[x])siz[x]=1; else siz[x]=0; for(int i=head[x];i;i=e[i].nxt){ int y=e[i].v; if(y==fa)continue; dfs(y,x); siz[x]+=siz[y]; ll tmp=(2ll*siz[y]*(m-siz[y])%mod*fast_pow(m,mod-2))%mod; Ans=(Ans+1ll*e[i].w*tmp%mod)%mod; f[x]=(f[x]+tmp)%mod; f[y]=(f[y]+tmp)%mod; } } int main(){ freopen("sakuya.in","r",stdin); freopen("sakuya.out","w",stdout); n=read(),m=read(); for(int i=1;i<n;i++){ int u=read(),v=read(),w=read(); add(u,v,w); add(v,u,w); } for(int i=1;i<=m;i++)sp[read()]=1; dfs(1,0); int q=read(); while(q--){ int x=read(),k=read(); Ans=(Ans+1ll*k*f[x]%mod)%mod; cout<<Ans<<'\n'; } return 0; }
T4 红楼 ~ Eastern Dream
根号分治+根号重构。
考虑对于询问序列分块,对于每一个询问,能影响到它结果的是所有它前面的修改,对于散块,我们可以直接暴力,对于整块,我们在处理完每个整块后再统一将所有修改加到原序列上。
先开考虑以整块,我们分模数 和 处理。
对于 ,我们在原序列上按 分成若干段,每一段我们只修改前 个,发现此时段的个数小于根号,直接枚举每一个段,维护差分标记即可,这部分的复杂度 的。
对于 ,我们的段数比较多,但是每个段修改的值是一样的。我们可以在每个整块中维护 ,表示将所有 的加上一个 。
- 在修改时直接令
- 在重构整块时,我们不好直接放到原序列上,于是我们维护标记的前缀和 ,(这个前缀和是对于原序列来说的)。对于在段中的一个位置,所有打在它后面的标记会影响它,所以先后缀扫一遍,再累加到前缀标记中。注意重构后标记数组需要清空,但前缀和数组不清空,既所有其之前整块修改的前缀和累加。
- 在查询时,我们可以直接枚 到 枚举 ,对于每个 ,它的答案分为整段和散段,每个整段的贡献是一样的,为 ,统计 至 间的整段个数。然后散段只有两个,分别求一遍即可。
这部分的复杂度也是 的。
然后考虑散块的贡献,类似上面的做法,我们枚举每个散块的修改,这样的修改不超过 个,对于每个修改仍旧分为整块和散块,如上处理即可,这部分的复杂度是 的。
于是我们成功将复杂度压到 。
点击查看代码
#include <bits/stdc++.h> using namespace std; #define il inline #define ll unsigned long long il ll read(){ ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } const int inf=1ll<<30; const int M=2e5+10; const int B=400; ll a[M],prea[M],taga[M]; ll tag[B+10][B+10],pretag[B+10][B+10]; bool vis[B+10]; struct node{ int x,y,k; }q[M]; int tot(0); int main(){ freopen("scarlet.in","r",stdin); freopen("scarlet.out","w",stdout); int n=read(),m=read(); for(int i=1;i<=n;i++)a[i]=read(),prea[i]=prea[i-1]+a[i]; for(int oo=1;oo<=m;oo++){ int opt=read(); if(opt==1){ int x=read(),y=read(),k=read(); y=min(y+1,x); if(x<=B){ tag[x][y]+=k; vis[x]=1; } else{ for(int i=1;i<=n;i+=x)taga[i]+=k; for(int i=1+y;i<=n;i+=x)taga[i]-=k; } q[++tot]=(node){x,y,k}; } else{ int l=read(),r=read(); ll ans=prea[r]-prea[l-1]; for(int x=1;x<=B;x++){ int lB=(l-1)/x,lR=l-lB*x-1; int rB=(r)/x,rR=r-rB*x; ans+=1ll*pretag[x][x]*(rB-lB)+pretag[x][rR]-pretag[x][lR]; } for(int i=1;i<=tot;i++){ int lB=(l-1)/q[i].x,lR=l-lB*q[i].x-1; int rB=(r)/q[i].x,rR=r-rB*q[i].x; ans+=1ll*q[i].k*(1ll*q[i].y*(rB-lB)+min(q[i].y,rR)-min(q[i].y,lR)); } cout<<ans<<'\n'; } if(tot==B){ for(int x=1;x<=B;x++){ if(!vis[x])continue; vis[x]=1; for(int i=x;i>=1;i--)tag[x][i]+=tag[x][i+1]; ll sum=0; for(int i=1;i<=x;i++){ sum+=tag[x][i]; tag[x][i]=0; pretag[x][i]+=sum; } } ll sum=0; for(int i=1;i<=n;i++){ sum+=taga[i]; a[i]+=sum; taga[i]=0; prea[i]=prea[i-1]+a[i]; } tot=0; } } return 0; }
本文来自博客园,作者:CCComfy,转载请注明原文链接:https://www.cnblogs.com/cccomfy/p/17809852.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】