2023.9.23测试

\[\text{9.23 NOIP模拟赛} \]

T1 雷老师的正偏态分布

给出一组数据 \(a_1,a_2,\dots,a_n\),统计 \(a\) 的所有子集中平均数小于中位数的子集数量(子集大小为奇数)
\(1\leq n\leq 100\)\(1\leq V\leq 800\)

容易想到排序,然后枚举中位数 \(x\) 和它的左右两边选取数的数量 \(j\)。设左右两边选取的数的和分别是 \(s_1,s_2\),则需满足 \(s_1+s_2<2*j*x\)

因为值域很小,可以左右两边背包,设 \(f_{j,k}/g_{j,k}\) 表示左边/右边选 \(j\) 个数和为 \(k\) 的方案数,\(S=2*j*x\),则贡献为 \(f_{j,k}\times \sum\limits_{t=1}^{S-k-1}g_{j,t}\),做前缀和可以做到 \(O(n^3V)\) 加上枚举的复杂度为 \(O(n^4V)\),考场上这样写拿到 \(70\)

其实发现对于左边来说,每次移动相当于加上一个数,对于右边来说相当于减去一个数(即反悔背包),这样可以做到 \(O(n^3V)\)

code
#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
#define LL long long
using namespace std;

const int N=110,V=810;
const int MOD=998244353;

int n,v,a[N],t,s;
int f[N>>1][N*V],g[N>>1][N*V][2],sum[N>>1][N*V],ans;

void prework()
{
	t=n/2;  s=n*v;
	f[0][0]=g[0][0][0]=1;
	for(int i=1; i<=n; i++)
		for(int j=t; j>=1; j--)
			for(int k=s; k>=0; k--)
				(g[j][k][0]+=g[j-1][k-a[i]][0])%=MOD;
}

void solve1(int i)
{
	if(i<1)
		return;
	for(int j=t; j>=1; j--)
		for(int k=s; k>=a[i]; k--)
			(f[j][k]+=f[j-1][k-a[i]])%=MOD;
}

void solve2(int i)
{
	if(i<1)
		return;
	g[0][0][1]=1;
	for(int j=1; j<=t; j++)
	{
		for(int k=0; k<=s; k++)
		{
			if(k<a[i])
				g[j][k][1]=g[j][k][0];
			else
				g[j][k][1]=((g[j][k][0]-g[j-1][k-a[i]][1])%MOD+MOD)%MOD;
				g[j][k][0]=g[j][k][1];
				sum[j][k]=(sum[j][k-1]+g[j][k][0])%MOD;
		}
	}		
}

int main()
{
//	freopen("end010.in","r",stdin);
//	freopen("a.out","w",stdout);
	
	scanf("%d%d",&n,&v);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
		
	sort(a+1,a+1+n);
	
	prework();
	
	for(int i=1; i<=n; i++)
	{
		solve1(i-1);  solve2(i);
		
		for(int j=1; j<=min(i-1,n-i); j++)
			for(int k=1,S=j*2*a[i]; k<S; k++)
				(ans+=1LL*f[j][k]*sum[j][S-k-1]%MOD)%=MOD;
	}
	
	printf("%d",ans);

	return 0;
}

T2 假期计划Ⅱ

P8906 [USACO22DEC] Breakdown P

有点像 CSP-2022 T1 加强版

首先删边转加边,经典套路

之后也是经典的折半搜,设 \(h_i\) 表示 \(1\)\(k\) 经过 \(k\) 条边的最短路,其中 \(k\leq 4\)。这里只考虑 \(k=4\)

考虑维护任意两点间最短路 \(f_{i,j}\)。加边时只会对 \(i=u\)\(j=v\) 造成贡献,复杂度 \(O(n)\)。对于 \(h_i\),枚举中转站 \(j\),计算 \(f_{1,j}+f_{j,i}\)。注意到只有 \(i/j=u/v\) 时会产生贡献度,复杂度 \(O(n)\)

注意特判 \(u=1\) 的情况,总复杂度 \(O(n^3)\)

code
#include<bits/stdc++.h>
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;

const int N=310,M=90010,INF=1e9;

int n,k,m,ans[M];
int e[N][N],u[M],v[M];

struct GR
{
	int k,st,e[N][N],f[N][N],h[N];
	
	void init(int _k,int _st)
	{
		k=_k;  st=_st;
		memset(e,0x3f,sizeof(e));
		memset(f,0x3f,sizeof(f));
		memset(h,0x3f,sizeof(h));
		if(!k)
			h[st]=0;
	}
	
	void add(int x,int y,int z)
	{
		e[x][y]=z;
		if(!k)
			return;
		if(k==1)
		{
			if(x==st)
				h[y]=z;
			return;
		}
		
		for(int i=1; i<=n; i++)
		{
			f[i][y]=min(f[i][y],e[i][x]+z);
			f[x][i]=min(f[x][i],z+e[y][i]);
		}
		
		if(k==2)
		{
			for(int i=1; i<=n; i++)
				h[i]=f[st][i];
			return;
		}
		
		auto p=(k==3)? e:f;
		if(x==st)
		{
			for(int i=1; i<=n; i++)
				for(int j=1; j<=n; j++)
					h[j]=min(h[j],f[x][i]+p[i][j]);
		}
		for(int i=1; i<=n; i++)
		{
			h[i]=min(h[i],f[st][x]+p[x][i]);
			h[i]=min(h[i],f[st][y]+p[y][i]);
			h[x]=min(h[x],f[st][i]+p[i][x]);
			h[y]=min(h[y],f[st][i]+p[i][y]);
		}
	}
}k1,k2;

int main()
{
	memset(ans,0x3f,sizeof(ans));
	
	scanf("%d%d",&n,&k);
	m=n*n;
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++)
			scanf("%d",&e[i][j]);
	for(int i=1; i<=m; i++)
		scanf("%d%d",&u[i],&v[i]);
		
	int lcnt=k/2,rcnt=k-lcnt;
	k1.init(lcnt,1);  k2.init(rcnt,n);
		
	for(int i=m; i>=1; i--)
	{
		for(int j=1; j<=n; j++)
			ans[i]=min(ans[i],k1.h[j]+k2.h[j]);
		k1.add(u[i],v[i],e[u[i]][v[i]]);
		k2.add(v[i],u[i],e[u[i]][v[i]]);
	}
	
	for(int i=1; i<=m; i++)
	{
		if(ans[i]>=INF)
			printf("-1\n");
		else
			printf("%d\n",ans[i]);
	}

	return 0;
}

T3 永不加班

P8476 「GLR-R3」惊蛰

很蓝的啦

考虑朴素的 DP,设 \(g_{i,j}\) 表示 \(b_i=j\) 时的最小答案,则有转移 \(g_{i,j}=\min\limits_{k\ge j}g_{i-1,k}+f(j,a_i)\),复杂度 \(O(nV)\)

\(g_{i-1}\) 做一遍后缀 \(\rm min\),然后令 \(g_{i,j}\leftarrow g_{i-1,j}+f(j,a_i)\)。后缀 \(\min\)\(f\) 优秀的性质使得我们可以快速维护。具体的,对 \(g_{i-1}\) 做后缀 \(\min\) 之后,\(g_{i-1}\) 具有单调性,\(g_{i-1,j}\leq g_{i-1,j+1}\)。而 \(f(j,a_i)\) 是关于 \(j\) 的分段函数,当 \(j<a_i\) 时,相当于对 \(g_{i-1,j}\) 区间加 \(C\)。当 \(j\ge a_i\) 时相当于对 \(g_{i-1,j}\) 加上 \(j-a_i\)。两部分分别具有单调性,所以操作结束后 \(g_{i,j}\)\(a_i\) 为分割线变成两段关于 \(j\) 具有单调性的序列

因此,操作时只需线段树二分出 \(<a_i\) 的位置中最后一个使得 \(g_{i,j}>g_{i,a_i}\) 的位置 \(j\),并将 \(g_{i,j}\sim g_{i,a_i-1}\) 区间赋值成 \(g_{i,a_i}\)

仔细思考一下可以得知 \(\forall b_i\),存在 \(a_j=b_i\)。将 \(a\) 离散化,设 \(g_{i,j}\),表示 \(b_i=a_j\) 的最小答案,需要支持下列操作:

  • 区间加法

  • 区间赋值

  • 线段树二分出 \(<p\) 的位置最后一个 \(>v\) 的位置 \(q\),保证 \(<p\) 的位置具有单调性

线段树维护即可

code
#include<bits/stdc++.h>
#define LL long long
#define lc(p) p<<1
#define rc(p) p<<1|1 
using namespace std;

const int N=1e6+10;

int n,m,C;
int a[N],b[N];

struct Seg
{
	LL dat,fu,ad1,ad2;
	#define dat(x) tree[x].dat
	#define fu(x) tree[x].fu
	#define ad1(x) tree[x].ad1
	#define ad2(x) tree[x].ad2
	
	void add(LL v1,LL v2,int l)
	{
		dat+=v1+1LL*v2*b[l];  
		ad1+=v1;  ad2+=v2;
	}
	
	void cov(LL v)
	{
		dat=fu=v;
		ad1=ad2=0;
	}
}tree[N<<2];

void pushup(int p)
{
	dat(p)=max(dat(lc(p)),dat(rc(p)));
}

void spread(int p,int l,int r)
{
	int mid=(l+r)>>1;
	if(fu(p)!=-1)
	{
		tree[lc(p)].cov(fu(p));
		tree[rc(p)].cov(fu(p));
		fu(p)=-1;
	}
	if(ad1(p) || ad2(p))
	{
		tree[lc(p)].add(ad1(p),ad2(p),mid);
		tree[rc(p)].add(ad1(p),ad2(p),r);
		ad1(p)=ad2(p)=0;
	}
}

void build(int p,int l,int r)
{
	fu(p)=-1;
	dat(p)=ad1(p)=ad2(p)=0;
	if(l==r)
		return;
	int mid=(l+r)>>1;
	build(lc(p),l,mid);
	build(rc(p),mid+1,r);
	pushup(p);
}

void add(int p,int l,int r,int ql,int qr,LL v1,LL v2)
{
	if(ql<=l && qr>=r)
	{
		tree[p].add(v1,v2,r);
		return;
	}
	spread(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid)
		add(lc(p),l,mid,ql,qr,v1,v2);
	if(qr>mid)
		add(rc(p),mid+1,r,ql,qr,v1,v2);
	pushup(p);
}

void cov(int p,int l,int r,int ql,int qr,LL v)
{
	if(ql<=l && qr>=r)
	{
		tree[p].cov(v);
		return;
	}
	spread(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid)
		cov(lc(p),l,mid,ql,qr,v);
	if(qr>mid)
		cov(rc(p),mid+1,r,ql,qr,v);
	pushup(p); 
}

LL ask(int p,int l,int r,int pos)
{
	if(l==r)
		return dat(p);
	spread(p,l,r);
	int mid=(l+r)>>1;
	if(pos<=mid)
		return ask(lc(p),l,mid,pos);
	return ask(rc(p),mid+1,r,pos);
}

int find(int p,int l,int r,int ql,int qr,LL v)
{
	if(dat(p)<v)
		return -1;
	if(l==r)
		return l;
	spread(p,l,r);
	int mid=(l+r)>>1;
	if(ql<=mid && dat(lc(p))>=v)
		return find(lc(p),l,mid,ql,qr,v);
	if(qr>mid)
		return find(rc(p),mid+1,r,ql,qr,v);
}

int main()
{
	scanf("%d%d",&n,&C);
	for(int i=1; i<=n; i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	
	sort(b+1,b+1+n);
	m=unique(b+1,b+1+n)-(b+1);
	
	build(1,1,m);
	
	for(int i=1; i<=n; i++)
	{
		int x=lower_bound(b+1,b+1+m,a[i])-b;
		if(x+1<=m)
			add(1,1,m,x+1,m,-a[i],1);
		if(x-1>=1)
		{
			add(1,1,m,1,x-1,C,0); 
			LL tmp=ask(1,1,m,x);
			int pos=find(1,1,m,1,x-1,tmp);
			if(pos!=-1)
				cov(1,1,m,pos,x-1,tmp);
		}
	} 
	
	printf("%lld",ask(1,1,m,1));

	return 0;
}

\(70+0+25+0=95\)\(\rm rk19\)

posted @ 2023-09-25 22:00  xishanmeigao  阅读(21)  评论(0)    收藏  举报