模拟83 考试总结

千万恨 争不得 朝夕

考试经过

从秦皇岛回来就状态特别不好,晚上躺下就睡,白天身上基本没劲,想一会就容易犯困
开题发现T1一个\(log\)是显然的,但应该卡不过,然而没咋多想,直接上了,预计60pts,感觉脑子不好使就没想正解
T2应该可做,想了会发现不太好维护,然后就开始犯困,好不容易想出来\(n^2\)的换根,十分明显的意识到离正解只差数据结构,然而这时偶然看见旁边JYF在打主席树,感觉要完,感觉自己不能维护,于是直接上的暴力,对着部分分一通乱写
T3的dp已经没有能力思考了,直接状压走人,T4没多看骗10分跑路了
所以估分60+65+25+10=160,结果60+45+25+0=130
T2T4写挂不说,四个题都有人切,所以就垫底了

T1.树上的数

正解看上去像暴力
对于每个修改直接dfs,标记途经的点,遇到标记的点就返回,所以是线性的

#include <bits/stdc++.h>
using namespace std;
const int N=5000050;
struct node{
	int from,to,next;
}a[2*N];
int head[N],mm=1;
inline void add(int x,int y)
{
	a[mm].from=x;a[mm].to=y;
	a[mm].next=head[x];head[x]=mm++;
}
int aa,bb,fa[N],q[N],xx,yy,ans,an;
bool v[N];int n,m;
void dfs(int x)
{
	v[x]=1;ans--;
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(v[y])continue;
		dfs(y);
	}
}
signed main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	cin>>n>>m>>aa>>bb;
	fa[2]=1;add(1,2);ans=n;
	for(int i=3;i<=n;i++)
	{
		fa[i]=((1ll*fa[i-1]*aa+bb)^19760817)%(i-1)+1;
		add(fa[i],i);
	}
	cin>>q[1]>>xx>>yy;
	for(int i=2;i<=m;i++)q[i]=(((1ll*q[i-1]*xx+yy)^19760817)^(i<<1))%(n-1)+2;
	for(int i=1;i<=m;i++)
	{
		int x=q[i];
		if(!v[x])dfs(x);
		an^=ans;
	}
	cout<<an<<endl;
	return 0;
}

T2.时代的眼泪

我考场上竟然忘了还有线段树合并这个东西,更别提神仙的树状数组了
先求出以1为根的答案,每个点贡献就是到根节点路径上大于\(w_x\)的个数
用树状数组维护,在dfs结束时删除,保证了数据结构中只有一条链就能查询了
剩下的就是换根dp,如果把根从\(x\)换到它原本的儿子\(y\),那么贡献相比原来多算了\(y\)子树内小于\(w_x\)的个数,少算了\(y\)子树外小于\(w_y\)的个数,对应加减
考虑怎么快速维护,线段树合并常数较大不容易卡过,积累一个树状数组的技巧
统计\(x\)子树中的答案时,先在进子树前统计一遍,再把子树全插进去,再查一遍二者相减就是答案
对于子树外的答案,可以用全局的查询减去子树内的答案

#include <bits/stdc++.h>
using namespace std;
const int N=1000050;
inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
struct node{
	int from,to,next;
}a[2*N];
int head[N],mm=1;
inline void add(int x,int y)
{
	a[mm].from=x;a[mm].to=y;
	a[mm].next=head[x];head[x]=mm++;
}
int w[N],lsh[N],n,m,cnt;
int p[N],f[N],g[N];long long an[N];
struct bit{
	int b[N];
	inline int lowbit(int x){return x&(-x);}
	inline void add(int p,int v)
	{
		for(int i=p;i<=n;i+=lowbit(i))
			b[i]+=v;
	}
	inline int getsum(int p)
	{
		int s=0;
		for(int i=p;i;i-=lowbit(i))
			s+=b[i];
		return s;
	}
	inline int get(int l,int r)
	{	
		return getsum(r)-getsum(l-1);
	}
}tr;
void dfs1(int x,int fa)
{
	p[x]=tr.get(w[x]+1,cnt);
	tr.add(w[x],1);
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(y==fa)continue;
		dfs1(y,x);
	}
	tr.add(w[x],-1);
}
void dfs2(int x,int fa)
{
	tr.add(w[x],1);
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(y==fa)continue;
		int s1=tr.get(1,w[x]-1),s2=tr.get(1,w[y]-1);
		dfs2(y,x);
		f[y]=tr.get(1,w[x]-1)-s1;
		g[y]=tr.get(1,w[y]-1)-s2;
	}
}
void dfs3(int x,int fa)
{
	for(int i=head[x];i;i=a[i].next)
	{
		int y=a[i].to;
		if(y==fa)continue;
		an[y]=an[x]+g[y]-f[y];
		dfs3(y,x);
	}	
}
signed main()
{
	freopen("tears.in","r",stdin);
	freopen("tears.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++)w[i]=read();
	for(int i=1;i<=n;i++)lsh[i]=w[i];
	sort(lsh+1,lsh+n+1);
	cnt=unique(lsh+1,lsh+1+n)-lsh-1;
	for(int i=1;i<=n;i++)w[i]=lower_bound(lsh+1,lsh+cnt+1,w[i])-lsh;
	for(int i=1;i<n;i++)
	{	
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	dfs1(1,0);
	for(int i=1;i<=n;i++)an[1]+=p[i];
	dfs2(1,0);
	for(int i=1;i<=n;i++)g[i]=tr.get(1,w[i]-1)-g[i];
	dfs3(1,0);	
	for(int i=1;i<=m;i++)
	{
		int x=read();
		printf("%lld\n",an[x]);
	}
	return 0;
}

思维一定不能僵化,不要特地贴标签

T3.传统艺能

其实光dp的话是原题,可以类比43场的那个,多亏我还写了那么长题解
一共只有\(f_1,f_2,f_3\),那么每次转移可以写成矩阵形式
这样不是像原来一样应对极大的\(m\)轮数,而是为了便于支持修改,将动态问题静态化
那么对于修改就可以线段树每个节点维护一个矩阵,pushup的时候就是两个矩阵相乘
所以就可以直接线段树套过来,与之类似的似乎还有之前的那个斐波,不过比这个难

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define pp tr[id].p
#define pl tr[id*2].p
#define pr tr[id*2+1].p
const int N=100050;
const int mod=998244353;
char c[N],s[3];int n,m,an[5],ss[5];
struct tree{
	int l,r;
	int p[5][5];
}tr[4*N],ans;
inline void qi(int id)
{
	memset(pp,0,sizeof(pp));
	for(int i=1;i<=4;i++) 
	 for(int k=1;k<=4;k++)
	  for(int j=1;j<=4;j++)
	   pp[i][j]=(pp[i][j]+pl[i][k]*pr[k][j]%mod)%mod;
}
void build(int id,int l,int r)
{
	tr[id].l=l;tr[id].r=r;
	if(l==r)
	{	
		memset(pp,0,sizeof(pp));
		pp[1][1]=pp[2][2]=pp[3][3]=pp[4][4]=1;
		if(c[l]=='A')pp[2][1]=pp[3][1]=pp[4][1]=1;
		if(c[l]=='B')pp[1][2]=pp[3][2]=pp[4][2]=1;
		if(c[l]=='C')pp[1][3]=pp[2][3]=pp[4][3]=1;
		return;
	}
	int mid=(l+r)>>1;
	build(id*2,l,mid);build(id*2+1,mid+1,r);
	qi(id);
}
void change(int id,int p,int v)
{
	if(tr[id].l==tr[id].r)
	{	
		memset(pp,0,sizeof(pp));
		pp[1][1]=pp[2][2]=pp[3][3]=pp[4][4]=1;
		if(v==1)pp[2][1]=pp[3][1]=pp[4][1]=1;
		if(v==2)pp[1][2]=pp[3][2]=pp[4][2]=1;
		if(v==3)pp[1][3]=pp[2][3]=pp[4][3]=1;
		return;
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(p<=mid)change(id*2,p,v);
	else change(id*2+1,p,v);
	qi(id);
}
tree get(int id,int l,int r)
{
	if(l<=tr[id].l&&r>=tr[id].r)return tr[id];
	int mid=(tr[id].l+tr[id].r)>>1;
	if(r<=mid)return get(id*2,l,r);
	if(l>mid)return get(id*2+1,l,r);
	tree ans,an1=get(id*2,l,mid),an2=get(id*2+1,mid+1,r);
	memset(ans.p,0,sizeof(ans.p));
	for(int i=1;i<=4;i++) 
	 for(int k=1;k<=4;k++)
	  for(int j=1;j<=4;j++)
	   ans.p[i][j]=(ans.p[i][j]+an1.p[i][k]*an2.p[k][j]%mod)%mod;
	return ans;
}
signed main()
{
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	cin>>n>>m;scanf("%s",c+1);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int op;scanf("%lld",&op);
		if(op==1)
		{
			int p;scanf("%lld%s",&p,s+1);
			change(1,p,s[1]-'A'+1);
		}
		else
		{
			int l,r;scanf("%lld%lld",&l,&r);
			ans=get(1,l,r);
			memset(ss,0,sizeof(ss));
			an[1]=an[2]=an[3]=0,an[4]=1;
			for(int i=1;i<=4;i++)
			 for(int j=1;j<=4;j++)
			  ss[i]=(ss[i]+an[j]*ans.p[j][i]%mod)%mod;
			printf("%lld\n",(ss[1]+ss[2]+ss[3])%mod);
		}
	}
	return 0;
}

T4.铺设道路

看着不太可做,但其实还是水题
把序列差分一下,那么每次相当于选择两个位置前面减一后面加一,尽快让所有数变成0
要使得操作数最小,那么选正的减负的加是最优的,所以可以贪心模拟
维护一个双端队列,每次遇见正数把他加进去,负数就考虑找正数和他配对然后删掉
代价最大就是找最左边的,否则是最右边,直接模拟即可

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=300050;
const int mod=1e9+7;
int a[N],b[N],n,ans;
deque <pair<int,int> > q;
signed main()
{
	freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n+1;i++)b[i]=a[i]-a[i-1];
	for(int i=1;i<=n+1;i++)ans+=max(b[i],(int)0);
	int sum1=0,sum2=0;
	for(int i=1;i<=n+1;i++)
	{
		if(!b[i])continue;
		if(b[i]>0)q.push_back(make_pair(i,b[i]));
		else
		{
			int x=b[i];
			while(q.size()&&q.front().second+x<=0)
			{
				sum1=(sum1+(i-q.front().first)*(i-q.front().first)%mod*q.front().second%mod)%mod;
				x+=q.front().second;q.pop_front();
			}
			if(x<0)
			{
				sum1=(sum1+(i-q.front().first)*(i-q.front().first)%mod*abs(x)%mod)%mod;
				int p=q.front().second+x,id=q.front().first;q.pop_front();
				q.push_front(make_pair(id,p));
			}
		}
	}
	assert(q.empty());
	for(int i=1;i<=n+1;i++)
	{
		if(!b[i])continue;
		if(b[i]>0)q.push_back(make_pair(i,b[i]));
		else
		{
			int x=b[i];
			while(q.size()&&q.back().second+x<=0)
			{
				sum2=(sum2+(i-q.back().first)*(i-q.back().first)%mod*q.back().second%mod)%mod;
				x+=q.back().second;q.pop_back();
			}
			if(x<0)
			{
				sum2=(sum2+(i-q.back().first)*(i-q.back().first)%mod*abs(x)%mod)%mod;
				int p=q.back().second+x,id=q.back().first;q.pop_back();
				q.push_back(make_pair(id,p));
			}
		}
	}
	cout<<ans<<endl<<sum2<<endl<<sum1<<endl;
	return 0;
}

考试总结

感觉之前犯的毛病还是在犯,考场犯困这个着实很痛苦,要尽量调整好状态
dp这方面差的太多了,接下来的dp不能再咕了,应该好好练一练
策略这块还是太草率,很多能拿的分没有拿到比较可惜,下次应该要好一点

上一篇:DBCA启动问题 (linux)


下一篇:[CF1601C]Optimal Insertion