2023/2/1 考试总结

题单贴贴

T1.P3195 [HNOI2008]玩具装箱

  • 斜率优化 \(\mathtt{DP}\) 板题;虽然这是板题但签到题就是紫的是否有些过分?

  • \(f_i\) 表示把 \(i\) 及以前的都打包的最小花费。
    朴素 \(DP\) 式子:\(f_i=\min\limits_{j=1}^{i-1}\{f_i,f_j+(sum_i-sum_j+i-j-L)^2\}\)\(sum\) 是长度的前缀和。这里为了方便处理,先把所有长度以及 \(L+1\),这样后面的平方里面就可以化简为 \(sum_i-sum_j-L\)

  • 转化:\(f_i=f_j+sum_i^2+sum_j^2+L^2-2sum_i sum_j-2sum_i L+2sum_j L\)
    把含 \(i\) 项视作常数,然后与 \(k\) 相减得 \(f_j-f_k+sum_j^2-sum_k^2-2sum_i sum_j+2sum_i sum_k+2sum_j L-2sum_k L=0\)
    于是设 \(y_i=f_i+sum_i^2\)\(x_i=sum_i\)
    于是斜率 \(=\frac{y_j-y_k}{x_j-x_k}\),需要与 \(2sum_i-2L\) 比较。

AC code
#include<bits/stdc++.h>
using namespace std;

#define ll long long

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=5e4+10;

int n,L;
ll c[N];
ll f[N];
int q[N],l,r;

#define y(i) (f[i]+c[i]*c[i])

double g(int a,int b){
	return (double)(y(a)-y(b))/(double)(c[a]-c[b]);
}

int main(){
	n=read(),L=read()+1;
	for(int i=1;i<=n;++i)
		c[i]=read()+1;
	for(int i=1;i<=n;++i)
		c[i]+=c[i-1];
	l=r=1;
	for(int i=1;i<=n;++i){
		while(l<r && g(q[l],q[l+1])<0ll+1ll*2*c[i]-1ll*2*L)
			++l;
		f[i]=f[q[l]]+1ll*(0ll+L-c[i]+c[q[l]])*(0ll+L-c[i]+c[q[l]]);
//		cerr<<i<<" "<<q[l]<<endl;
		while(l<r && g(q[r-1],q[r])>=g(q[r],i))
			--r;
		q[++r]=i;
	}
	printf("%lld",f[n]);
	return 0;
}
/*

5 4
3
4
2
1
4

*/

T2.P7118 Galgame

  • \(Catalan\) 数+树形 \(\mathtt{DP}\)

  • 很容易想到,只要整棵树的节点数少于当前这棵树,那么不管它是什么形状,它都不会更有趣。
    因此,树形 \(\mathtt{DP}\) 时只需要考虑节点数相同的情况。

  • \(f_i\) 表示,在 \(i\) 节点及其子树上不更有趣的方案数。
    由于我们知道,\(Cat_n\) 可以表示一棵 \(n\) 个节点的二叉树的不同结构数,所以可以先预处理出一个 \(Catalan\) 数列(我使用了公式 \(Cat_n=\frac{4n-2}{n+1}Cat_{n-1}\),含有除法,所以先写了一个线性求逆元)
    然后对于具体的 \(i\),由于是先判断左子树再看右子树,所以分为以下几种情况:

    • \(i\) 的左子树和原树上对应节点的大小相同,区别出现在再下面的地方(即 \(f_{ls_i}\));
    • \(i\) 的左子树比原树上对应节点小,区别出现在这里;
    • \(i\) 的左子树与原树一样,区别出现在右子树(即 \(f_{rs_i}\));
      对于第一种情况,在左子树中发现区别后就不会再看右子树,所以右子树可以是任何形状,\(f_i+=f_{ls_i}Cat_{size_{rs_i}}\)
      对于第二种情况,只需要保持 \(i\) 及其子树大小恒定,\(f_i+=\sum\limits_{j=0}^{size_{ls_i}-1}Cat_i Cat_{size_i-j}\)
      对于第三种情况,\(f_i+=f_{rs_i}\)
  • 优化:第二种情况中,有一种可能是左子树大小比右子树大,又有 \(Cat_n=\sum\limits_{i=0}^{n-1}Cat_i Cat_{n-i-1}\),所以可以用这个性质进行优化,当左子树比右子树大时,就用右子树来算;
    时间复杂度从 \(\mathtt{O(n^2)}\)\(\mathtt{O(n\log n)}\)

AC code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

#define ll long long

const int N=1e6+10;
const int mod=998244353;

int n;
int ls[N],rs[N];
ll f[N],siz[N];
ll cat[N],sumc[N];
ll inv[N];

void dfs(int p){
	if(!p) return ;
	dfs(ls[p]);
	dfs(rs[p]);
	siz[p]=siz[rs[p]]+siz[ls[p]];
	if(ls[p]){
		if(siz[ls[p]]<=siz[rs[p]])
			for(int i=0;i<siz[ls[p]];++i)
				f[p]+=cat[i]*cat[siz[p]-i]%mod;
		else{
			(f[p]+=cat[siz[p]+1])%=mod;
			for(int i=siz[p];i>=siz[ls[p]];--i)
				f[p]+=mod-cat[i]*cat[siz[p]-i]%mod;
		}
		(f[p]+=f[ls[p]]*cat[siz[rs[p]]]%mod)%=mod;
	}
	(f[p]+=f[rs[p]])%=mod;
	++siz[p];
	return ;
}

int main(){
	n=read();
	for(int i=1;i<=n;++i)
		ls[i]=read(),rs[i]=read();
	inv[1]=1;
	for(int i=2;i<=n+1;++i)
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	cat[0]=cat[1]=1;
	for(int i=2;i<=n;++i)
		cat[i]=1ll*cat[i-1]*(4*i-2)%mod*inv[i+1]%mod;
	sumc[0]=1;
	for(int i=1;i<=n;++i)
		sumc[i]=(sumc[i-1]+cat[i])%mod;
	dfs(1);
	(f[1]+=sumc[n-1]-1)%=mod;
	printf("%lld",f[1]);
	return 0;
}
/*

9
2 3
4 5
0 0
0 0
6 7
0 0
8 9
0 0
0 0

*/

T3.P1399 [NOI2013] 快餐店

  • 基环树;

  • 感觉我的树和图的知识都需要重修一次,难道这就是当年翘课去物竞的后遗症吗,恐怖如斯

  • 一个基环树+spfa 嫖了 50pts 这是可以说的吗

  • 参考题解

  • 如果原图是一棵树,那么问题很好解决:找到直径,然后 \(/2\)
    因此,问题可以转化为:对一棵基环树找到最小直径。
    然后分两种情况讨论:

    • 该直径不经过环。
      找到环,枚举环上每个点,然后对点不包括环的子树进行遍历,找直径即可。
    • 该直径经过环。
      一种朴素的办法是枚举环上的断边,然后暴力求解,但时间复杂度 \(\mathtt{O(n^2)}\),过不了。
      我们先找到环,然后把环上的点重新从 \(1\sim k\) 编一下号。
      设置 \(4\) 个数组(代码中的 \(cnt_{0\sim 3}\))。
AC code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=1e5+10;

int n;
int head[N],ver[N<<1],nxt[N<<1],tot=1;
double edge[N<<1];

void add(int x,int y,double z){
	ver[++tot]=y,edge[tot]=z;
	nxt[tot]=head[x],head[x]=tot;
	return ;
}

bool vis[N],is[N];
int pt[N],k=0;
int f[N],w[N],c[N];

bool find(int p,int fa){
	vis[p]=1;
	for(int i=head[p];i;i=nxt[i]){
		if(ver[i]==fa) continue;
		w[ver[i]]=edge[i];
		f[ver[i]]=p;
		if(!vis[ver[i]]){
			if(find(ver[i],p)) 
				return 1;
		}
		else{
			for(int i=p;i;){
				pt[++k]=i;
				is[i]=1;
				c[k]=w[i];
				i=f[i];
				if(i==p) break;
			}
			return 1;
		}
	}
	return 0;
}

double ans[2],dep[N];

void dfs(int p,int fa){
	for(int i=head[p];i;i=nxt[i]){
		if(ver[i]==fa || is[ver[i]]) continue;
		dfs(ver[i],p);
		ans[0]=max(ans[0],(double)dep[p]+dep[ver[i]]+edge[i]);
		dep[p]=max(dep[p],dep[ver[i]]+edge[i]);
	}
	return ;
}

double cnt[5][N];

int main(){
//	freopen("2.in","r",stdin);
	n=read();
	int a,b;
	double l;
	for(int i=1;i<=n;++i){
		a=read(),b=read(),l=read();
		add(a,b,l);
		add(b,a,l);
	}
	find(1,0);
	for(int i=1;i<=k;++i)
		dfs(pt[i],0);
	double sum=0,mx=0;
	for(int i=1;i<=k;++i){
		sum+=c[i-1];
		cnt[0][i]=max(cnt[0][i-1],sum+dep[pt[i]]);
		cnt[1][i]=max(cnt[1][i-1],sum+mx+dep[pt[i]]);
		mx=max(mx,dep[pt[i]]-sum);
	}
	sum=mx=0;
	int cw=c[k];
	c[k]=0;
	for(int i=k;i;--i){
		sum+=c[i];
		cnt[2][i]=max(cnt[2][i+1],sum+dep[pt[i]]);
		cnt[3][i]=max(cnt[3][i+1],sum+mx+dep[pt[i]]);
		mx=max(mx,dep[pt[i]]-sum);
	}
	double ans1=1e18;
	for(int i=1;i<=k;++i){
		ans[1]=max(cnt[1][i],cnt[3][i+1]);
		ans[1]=max(ans[1],cnt[0][i]+cnt[2][i+1]+cw);
		ans1=min(ans1,ans[1]);
	}
	ans1=min(ans1,cnt[1][k]);
	printf("%.1f",max(ans1,ans[0])/2.0);
	return 0;
}

T4.P5607 [Ynoi2013] 无力回天 NOI2017

  • 好名字

  • 线段树+线性基;

  • 区间查询,很显然的树状数据结构。可以维护线性基,然后每次暴力合并。问题是如何维护区间异或操作。
    线性基难以支持修改操作,所以我们考虑把区间修改转换成单点修改,对原数列进行差分。
    易证,差分后的序列和原序列的线性基等效。
    因此,把差分后的序列塞进线段树套线性基里,然后另开一个树状数组支持单点修改和区间查询。然后再每次查询时把序列第一个前缀起来放进线性基,就可以正常查询了。

  • 不过本题有点卡常,线性基的数组但凡再开大都要 \(T\)(也可能我的写法有些地方不太优秀吧);

AC code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

#define re register

const int N=5e4+10;

int n,m;
int a[N];
int c[N];

struct memr{
	int l,r;
	int q[30];
}tr[N<<3];
//树状数组
void ax(int x,int v){
	for(;x<=n;x+=x&-x)
		c[x]^=v;
	return ;
}

int asx(int x){
	int cnt=0;
	for(;x;x-=x&-x)
		cnt^=c[x];
	return cnt;
}
//
void check(int *x,int y){
	for(re int i=29;i>-1;--i){
		if(!((y>>i))&1) continue;
		if(!x[i]){
			x[i]=y;
			break;
		}
		y^=x[i];
	}
	return ;
}

void merge(int *x,int *y){
	for(re int i=29;i>-1;--i){
		if(!x[i]) continue;
		check(y,x[i]);
	}
	return ;
}

void pushup(int p){
	for(re int i=0;i<30;++i)
		tr[p].q[i]=tr[p<<1].q[i];
	merge(tr[p<<1|1].q,tr[p].q);
	return ;
}

void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	if(l==r){
		check(tr[p].q,a[l]);
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
	return ;
}

void change(int p,int x,int v){
	if(x<=tr[p].l && tr[p].r<=x){
		memset(tr[p].q,0,sizeof(tr[p].q));
		check(tr[p].q,a[x]);
		return ;
	}
	int mid=(tr[p].l+tr[p].r)>>1;
	if(x<=mid) change(p<<1,x,v);
	else change(p<<1|1,x,v);
	pushup(p);
	return ;
}

void ask(int p,int l,int r,int *x){
	if(l<=tr[p].l && tr[p].r<=r){
		merge(tr[p].q,x);
		return ;
	}
	int mid=(tr[p].l+tr[p].r)>>1;
	if(l<=mid) ask(p<<1,l,r,x);
	if(mid<r) ask(p<<1|1,l,r,x);
	pushup(p);
	return ;
}

int g(int *x,int y){
	for(re int i=29;i>-1;--i)
		if((y^x[i])>y)
			y^=x[i];
	return y;
}

int ac[30];

int main(){
	n=read(),m=read();
	for(re int i=1;i<=n;++i)
		a[i]=read();
	for(re int i=n;i;--i){
		a[i]^=a[i-1];
		ax(i,a[i]);
	}
	build(1,1,n+1);
	int opt,l,r,v;
	while(m--){
		opt=read(),l=read(),r=read(),v=read();
		if(opt==1){
			ax(l,v);
			ax(r+1,v);
			a[l]^=v,a[r+1]^=v;
			change(1,l,v);
			change(1,r+1,v);
		}
		else{
			int t=asx(l);
			if(l==r){
				printf("%d\n",max(v,v^t));
				continue;
			}
			memset(ac,0,sizeof(ac));
			ask(1,l+1,r,ac);
			check(ac,t);
			printf("%d\n",g(ac,v));
		}
	}
	return 0;
}
posted @ 2023-02-01 19:19  Star_LIcsAy  阅读(35)  评论(0)    收藏  举报