noip模拟测试83
Posted WindZR
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了noip模拟测试83相关的知识,希望对你有一定的参考价值。
考试过程:这次考试,总体来说难度是不大的,我觉得前两题是可做的,首先是第一题,我觉得是个线段树板子题,也没多想,就是数据范围有点大,但是我没什么好方法优化,就打了个线段树走了。
然后是T2,首先想出了\\(o(n\\times log(n))\\)求出以1为根的答案,然后考虑移动。不难发现,当根节点移动一次时,答案相对于上一次的变化量由两部分组成:
1.当前点为根的子树内的点的\\(w\\)小于上一次的根节点的\\(w\\)的数量。
2.除去当前点为根的子树,剩下的点中\\(w\\)小于当前根节点的数量。
但是我在考场上想复杂了,这是我经过优化之后的想法,当时的做法还要考虑每个子树对当前点的贡献,比较复杂,打了个树套树,但是卡在最后一个问题没有解决。就是如何在总复杂度在\\(O(n\\times log(n))\\)左右求出以每个点为根的子树内\\(w\\)值小于当前根节点的点的个数。
考虑解决这个问题:因为主席树可以支持查询区间第\\(k\\)大,那么我们可以在\\(dfs\\)序上建一颗主席树,那么显然我们把\\(w\\)数组经过离散化之后就知道了\\(w\\)的排名,那么我们直接在主席树上查询即可。
剩下的T3,T4没什么时间做了,就打了个暴力。
T1 树上的数
思路:因为5e6的数据带个\\(log\\)达到了\\(1e8\\)级别,在那个超快的评测机上根本过不去,但是线段树无法进行优化,考虑另外的思路。
思考如果我们要打暴力,那么应该是从当前根节点往下递归,统计出当前子树的大小。
考虑优化这个过程,我们可以对经过的节点打上标记,这样我们就不会经过重复的节点,这样的复杂度是\\(o(n+m)\\)的。
代码如下:
AC_code
#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
#define next netetet
#define head heaeaea
using namespace std;
const int N=5e6+10;
int n,m,tot,ans,sum;
int to[N],next[N],head[N],Q[N];
int fa,q,l,r;
bool vis[N];
ii read()
{
int x=0;char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?(x):(-x);
}
iv check(int st)
{
vis[st]=1;--sum;
for(re i=head[st];i;i=next[i]) if(!vis[to[i]]) check(to[i]);
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int a,b,x,y;
n=read(),m=read();
a=read(),b=read();
fa=1;
to[++tot]=2;
next[tot]=head[1];
head[1]=tot;
for(re i=3;i<=n;++i)
{
fa=((1ll*fa*a+b)^19760817)%(i-1)+1;
to[++tot]=i;
next[tot]=head[fa];
head[fa]=tot;
}
q=read(),x=read(),y=read();
sum=n;
for(re i=1;i<=m;i++)
{
if(!sum) break;
if(i!=1) q=(((1ll*q*x+y)^19760817)^(i<<1))%(n-1)+2;
if(!vis[q])
{
l=1,r=0;
Q[++r]=q;
while(l<=r)
{
int now=Q[l++];
vis[now]=1;
--sum;
for(re j=head[now];j;j=next[j])
{
if(!vis[to[j]]) Q[++r]=to[j];
}
}
}
ans^=sum;
}
printf("%d\\n",ans);
return 0;
}
T2 时代的眼泪
思路:首先想出了\\(o(n\\times log(n))\\)求出以1为根的答案,然后考虑移动。不难发现,当根节点移动一次时,答案相对于上一次的变化量由两部分组成:
1.当前点为根的子树内的点的\\(w\\)小于上一次的根节点的\\(w\\)的数量。
2.除去当前点为根的子树,剩下的点中\\(w\\)小于当前根节点的数量。
但是我在考场上想复杂了,这是我经过优化之后的想法,当时的做法还要考虑每个子树对当前点的贡献,比较复杂,打了个树套树,但是卡在最后一个问题没有解决。就是如何在总复杂度在\\(O(n\\times log(n))\\)左右求出以每个点为根的子树内\\(w\\)值小于当前根节点的点的个数。
考虑解决这个问题:因为主席树可以支持查询区间第\\(k\\)大,那么我们可以在\\(dfs\\)序上建一颗主席树,那么显然我们把\\(w\\)数组经过离散化之后就知道了\\(w\\)的排名,那么我们直接在主席树上查询即可。
代码如下:
AC_code
#include<bits/stdc++.h>
#define ll long long
#define re int
#define ii inline int
#define iv inline void
#define f() cout<<"fuck"<<endl
#define next netetetetet
using namespace std;
const int N=1e6+10;
int n,q,tot,cnt,timi,zx;
int w[N],fa[N],lsh[N],dfn[N],size[N],root[N];
ll ans[N],he[N];
int to[N<<1],next[N<<1],head[N];
ii read()
{
int x=0;char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?(x):(-x);
}
iv add(int x,int y)
{
to[++tot]=y;
next[tot]=head[x];
head[x]=tot;
}
struct Segment_Tree
{
#define mid ((l+r)>>1)
int sum[N<<2],lc[N<<2],rc[N<<2];
ii insert(int las,int l,int r,int p)
{
int now=++zx;
lc[now]=lc[las];
rc[now]=rc[las];
if(l==r)
{
sum[now]=sum[las]+1;
return now;
}
if(mid>=p) lc[now]=insert(lc[las],l,mid,p);
else rc[now]=insert(rc[las],mid+1,r,p);
sum[now]=sum[lc[now]]+sum[rc[now]];
return now;
}
ll query(int las,int now,int l,int r,int z)
{
if(l==r) return (l==z)?0:sum[now]-sum[las];
int out=0;
if(mid<z)
return sum[lc[now]]-sum[lc[las]]+query(rc[las],rc[now],mid+1,r,z);
return query(lc[las],lc[now],l,mid,z);
}
#undef mid
}T;
struct Seg
{
int sum[N<<1];
iv add(int x) {for(;x<=cnt;x+=(x&(-x))) ++sum[x];}
iv del(int x) {for(;x<=cnt;x+=(x&(-x))) --sum[x];}
ll query(int x)
{
int out=0;
for(;x;x-=(x&(-x))) out+=sum[x];
return out;
}
}S;
iv dfs(int st,int f)
{
dfn[st]=++timi;
size[st]=1;
root[timi]=T.insert(root[timi-1],1,cnt,w[st]);
S.add(w[st]);
ans[1]+=S.query(cnt)-S.query(w[st]);
for(re i=head[st];i;i=next[i])
{
int p=to[i];
if(p==f) continue;
dfs(p,st);
size[st]+=size[p];
S.del(w[p]);
}
}
iv check(int st,int f)
{
if(st!=1)
{
ll now=ans[f];
now-=T.query(root[dfn[st]-1],root[dfn[st]+size[st]-1],1,cnt,w[f]);
now+=he[w[st]-1]-T.query(root[dfn[st]-1],root[dfn[st]+size[st]-1],1,cnt,w[st]);
ans[st]=now;
}
for(re i=head[st];i;i=next[i])
{
if(to[i]==f) continue;
check(to[i],st);
}
}
signed main()
{
freopen("tears.in","r",stdin);
freopen("tears.out","w",stdout);
n=read(),q=read();
for(re i=1;i<=n;i++) w[i]=read(),lsh[i]=w[i];
sort(lsh+1,lsh+n+1);
cnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(re i=1;i<=n;i++)
{
w[i]=lower_bound(lsh+1,lsh+cnt+1,w[i])-lsh;
++he[w[i]];
}
for(re i=1;i<=cnt;i++) he[i]+=he[i-1];
int x,y;
for(re i=1;i<n;i++) x=read(),y=read(),add(x,y),add(y,x);
dfs(1,0);
check(1,0);
while(q--)
{
x=read();
printf("%lld\\n",ans[x]);
}
return 0;
}
T3 传统艺能
思路,先考虑没有修改的情况,设\\(f_{i,a}\\)表示考虑前\\(i\\)个位置,以\\(a\\)为结尾的方案数,那么转移是这样的\\(f_{i,a}=f_{i-1,a}+f_{i-1,b}+f_{i-1,c}+1,f_{i,b}=f_{i-1,b},f_{i,c}=f_{i-1,c}\\),其余情况同理。
那么这样的转移是\\(o(n)\\)的,考虑优化。不难发现我们的转移可以写成矩阵乘的形式,
以\\(A\\)结尾为例:
1 0 0 0
1 1 0 0
1 0 1 0
1 0 0 1
一到三行分别表示\\(A,B,C\\),最后一行表示\\(1\\).
因为有修改和区间查询,那么我们可以考虑用线段树维护这个东西。
线段树的每个叶子几点都是一个矩阵,上面的节点维护矩阵相乘的结果。那么修改操作就是单点修改。
对于询问操作,我们可以定义一个初始矩阵\\(0 0 0 1\\)表示\\(A,B,C,1\\),将初始矩阵与线段树进行区间查询的矩阵相乘即可。但是我们发现答案就是线段树上矩阵的第四行的前三列的加和,那么我们直接输出即可。
代码如下:
AC_code
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define re int
#define ii inline int
#define iv inline void
#define f() cout<<"fuck"<<endl
using namespace std;
const int N=1e5+10;
const int mo=998244353;
int n,m;
long long ans;
char s[N],c[N];
ii read()
{
int x=0;char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?(x):(-x);
}
struct mat
{
int a[5][5];
};
mat calc(mat x,mat y)
{
mat z;
memset(z.a,0,sizeof(z.a));
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
for(re k=1;k<=4;k++)
{
z.a[i][j]=(z.a[i][j]+1ll*x.a[i][k]*y.a[k][j])%mo;
}
}
}
return z;
}
struct Segment_Tree
{
#define mid ((l+r)>>1)
#define lc (rt<<1)
#define rc (rt<<1|1)
mat sum[N<<2];
iv pp(int rt)
{
sum[rt]=calc(sum[lc],sum[rc]);
}
iv build(int rt,int l,int r)
{
if(l==r)
{
if(s[l]==\'A\')
{
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
if(i==1) sum[rt].a[j][i]=1;
else
{
if(j==i) sum[rt].a[j][i]=1;
else sum[rt].a[j][i]=0;
}
}
}
}
else if(s[l]==\'B\')
{
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
if(i==2) sum[rt].a[j][i]=1;
else
{
if(j==i) sum[rt].a[j][i]=1;
else sum[rt].a[j][i]=0;
}
}
}
}
else
{
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
if(i==3) sum[rt].a[j][i]=1;
else
{
if(j==i) sum[rt].a[j][i]=1;
else sum[rt].a[j][i]=0;
}
}
}
}
return;
}
build(lc,l,mid),build(rc,mid+1,r);
pp(rt);
}
iv change(int rt,int l,int r,int p)
{
if(l==r)
{
if(s[l]==\'A\')
{
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
if(i==1) sum[rt].a[j][i]=1;
else
{
if(j==i) sum[rt].a[j][i]=1;
else sum[rt].a[j][i]=0;
}
}
}
}
else if(s[l]==\'B\')
{
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
if(i==2) sum[rt].a[j][i]=1;
else
{
if(j==i) sum[rt].a[j][i]=1;
else sum[rt].a[j][i]=0;
}
}
}
}
else
{
for(re i=1;i<=4;i++)
{
for(re j=1;j<=4;j++)
{
if(i==3) sum[rt].a[j][i]=1;
else
{
if(j==i) sum[rt].a[j][i]=1;
else sum[rt].a[j][i]=0;
}
}
}
}
return;
}
if(mid>=p) change(lc,l,mid,p);
else change(rc,mid+1,r,p);
pp(rt);
}
mat query(int rt,int l,int r,int L,int R)
{
if(L<=l and r<=R) return sum[rt];
if(mid>=R) return query(lc,l,mid,L,R);
if(mid<L) return query(rc,mid+1,r,L,R);
return calc(query(lc,l,mid,L,R),query(rc,mid+1,r,L,R));
}
#undef mid
#undef lc
#undef rc
}T;
signed main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
n=read(),m=read();
scanf("%s",s+1);
T.build(1,1,n);
int opt,x,y;
while(m--)
{
opt=read();
if(opt==1)
{
x=read();
scanf("%s",c+1);
if(s[x]==c[1]) continue;
s[x]=c[1];
T.change(1,1,n,x);
}
else
{
x=read(),y=read();
mat ans=T.query(1,1,n,x,y);
printf("%lld\\n",(ans.a[4][1]+ans.a[4][2]+ans.a[4][3])%mo);
}
}
return 0;
}
T4 铺设道路
注意是差分的过程,所以要算\\(n+1\\)项。
代码如下:
AC_code
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define re int
#define ii inline int
#define iv inline void
#define f() cout<<"fuck"<<endl
using namespace std;
const int N=3e5+10;
const int mo=1e9+7;
int n,ans,mx,mn;
int d[N],b[N],c[N];
queue<int> q;
stack<int> s;
ii read()
{
int x=0;char ch=getchar(); bool f=1;
while(ch<\'0\' or ch>\'9\')
{
if(ch==\'-\') f=0;
ch=getchar();
}
while(ch>=\'0\' and ch<=\'9\')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return f?(x):(-x);
}
signed main()
{
freopen("road.in","r",stdin);
freopen("road.out","w",stdout);
n=read();
for(re i=1;i<=n;i++) d[i]=read();
for(re i=1;i<=n+1;i++) b[i]=d[i]-d[i-1],c[i]=b[i];
for(re i=1;i<=n+1;i++) ans=ans+max(0ll,b[i]);
printf("%lld\\n",ans);
for(re i=1;i<=n+1;i++)
{
if(b[i]>0) q.push(i),s.push(i);
else
{
if(b[i]==0) continue;
int tmp=-b[i];
while(tmp>0 and q.size())
{
if(b[q.front()]<=tmp)
{
tmp-=b[q.front()];
mx=(mx+(i-q.front())*(i-q.front())%mo*b[q.front()])%mo;
q.pop();
}
else
{
mx=(mx+(i-q.front())*(i-q.front())%mo*(tmp))%mo;
b[q.front()]-=tmp;
break;
}
}
tmp=-c[i];
while(tmp>0 and s.size())
{
if(c[s.top()]<=tmp)
{
tmp-=c[s.top()];
mn=(mn+(i-s.top())*(i-s.top())%mo*c[s.top()])%mo;
s.pop();
}
else
{
mn=(mn+(i-s.top())*(i-s.top())%mo*(tmp))%mo;
c[s.top()]-=tmp;
break;
}
}
}
}
printf("%lld\\n%lld\\n",mn,mx);
return 0;
}
2021.11.18-NOIP模拟测试信心赛
2021.11.18-NOIP模拟信心赛
前言
太蒟蒻了,信心赛打的都快没信心了,giao
T1\\(\\color{green}100\\)题目
T1作为简单的签到题,直接先枚举所有的流量再用dijkstra跑这么多遍就可以了
#include<bits/stdc++.h>
#define M 2100
#define N 1100
#define inf 0x7f7f7f7f
using namespace std;
int n,m,maxx=-15;
int first[M],nex[M],to[M],w[M],f[M],tot;
int dis[N];
bool vis[N];
priority_queue<pair<int,int> >q;
//=======================================================
inline int read(){
int p=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c==\'-\')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-\'0\';c=getchar();}
return p*f;
}
//========================================================
inline void add(int x,int y,int z,int q){
nex[++tot]=first[x];
first[x]=tot;
to[tot]=y;
w[tot]=z;
f[tot]=q;
}
//========================================================
inline void dijkstra(){
for(int x=1;x<=1000;x++){
for(int i=1;i<=n;i++){
dis[i]=inf;
vis[i]=0;
}
q.push(make_pair(0,1));
dis[1]=0;
while(!q.empty()){
int u=q.top().second;
q.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=first[u];i;i=nex[i]){
int v=to[i];
if(f[i]<x)continue;
if(dis[u]+w[i]<dis[v]){
dis[v]=dis[u]+w[i];
if(!vis[v]){
q.push(make_pair(-dis[v],v));
}
}
}
}
if(dis[n]!=inf)
maxx=max(maxx,x*1000000/dis[n]);
}
}
//========================================================
int main(){
n=read(),m=read();
int a,b,c,f;
for(int i=1;i<=m;i++){
a=read(),b=read(),c=read(),f=read();
add(a,b,c,f);
add(b,a,c,f);
}
dijkstra();
cout<<maxx<<endl;
return 0;
}
T2\\(\\color{red}0\\)题目
明明第二天比第一题还简单简单我却做不出来
一开始在哪里想建图然后\\(bfs\\)然后小样例过了,大样例却挂了
结果直接用一个并查集分成两个集合就好了(有两种牛奶)
#include<bits/stdc++.h>
using namespace std;
#define N 100010
int n,m;
char s[N];
int f[N];
int ans[N];
//=====================================================
inline int read(){
int p=0,f=1;
char c;
while(!isdigit(c)){if(c==\'-\')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-\'0\';c=getchar();}
return p*f;
}
//======================================================
int gf(int x){
if(x == f[x]) return x;
return f[x] = gf(f[x]);
}
//======================================================
void hb(int x, int y){
f[gf(x)] = gf(y);
}
//======================================================
int main(){
n=read(),m=read();
scanf("%s",s+1);
int x,y;
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1;i<n;i++){
x=read(),y=read();
if(s[x]==s[y])
hb(x,y);
}
for(int i=1;i<=m;i++){
char c;
x=read(),y=read(),cin>>c;
if(gf(x)==gf(y)&&s[x]!=c)ans[i]=0;
else ans[i]=1;
}
for(int i=1;i<=m;i++)cout<<ans[i];
return 0;
}
T3\\(\\color{red}0\\)题目
第二题都没做出来自然第三题也做不出来
题意:
求一颗树\\(u\\)到\\(v\\)的路径上有无某颜色的点
先跑一遍dfs,再求出\\(u\\)和\\(v\\)的最近公共祖先,这样就可以得到\\(u\\)和\\(v\\)的路径,然后用结构体和vector存储查询的颜色,最近公共祖先以及该点的编号,
最后再来一遍dfs,这一遍相当于把颜色按照节点的深度进行赋值,然后枚举点因为刚才记录了该点所要查询的颜色以及最近公共祖先,而且\\(u\\)和\\(v\\)都记录了,所以如果该点颜色深度大于最近公共祖先的深度,就说明一定能够经过,就把\\(ans[now.id]=1\\)
第三题代码复杂度\\(O\\)(\\(n\\log n\\))
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 100,M = 2e5 + 100;
const int maxdep=24;
int n,m;
int first[M],to[M],nex[M],tot;
int val[N],dep[N],faz[N][maxdep+1],ans[N];
struct mpair{
int col,top,id;
};
vector<mpair> vec[N];
//==============================================================
inline int read(){
int p=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c==\'-\')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-\'0\';c=getchar();}
return p*f;
}
//==============================================================
inline void add(int x,int y){
nex[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
//==============================================================
void dfs1(int u){
for(int i=1;i<=maxdep;i++)faz[u][i]=faz[faz[u][i-1]][i-1];
for(int i=first[u];i;i=nex[i]){
int v=to[i];
if(v==faz[u][0])continue;
dep[v]=dep[u]+1;
faz[v][0]=u;
dfs1(v);
}
}
//==============================================================
inline int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=maxdep;i>=0;i--)
if(dep[faz[x][i]]>=dep[y])x=faz[x][i];
if(x==y)return x;
for(int i=maxdep;i>=0;i--)
if(faz[x][i]!=faz[y][i])x=faz[x][i],y=faz[y][i];
return faz[x][0];
}
//==============================================================
int tmp[N];
void dfs(int u){
int lst=tmp[val[u]];
tmp[val[u]]=dep[u];
for(int i=0;i<vec[u].size();i++){
mpair now=vec[u][i];
if(tmp[now.col]>=dep[now.top])
ans[now.id]=1;
}
for(int i=first[u];i;i=nex[i]){
int v=to[i];
if(v==faz[u][0])continue;
dfs(v);
}
tmp[val[u]]=lst;
}
//==============================================================
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)val[i]=read();
int x,y;
for(int i=1;i<n;i++){
x=read(),y=read();
add(x,y);
add(y,x);
}
dfs1(1);
int c;
for(int i=1;i<=m;i++){
x=read(),y=read(),c=read();
int l=lca(x,y);
vec[x].push_back(mpair{c,l,i});
vec[y].push_back(mpair{c,l,i});
}
memset(tmp,-1,sizeof(tmp));
dfs(1);
for(int i=1;i<=m;i++)cout<<ans[i];
return 0;
}
今天就是集训的最后一天了,明天就是NOIP2021了,祝所有的OIer rp++
以上是关于noip模拟测试83的主要内容,如果未能解决你的问题,请参考以下文章