@4 UOJ386 & UOJ388 & UOJ390

鸽子固定器

题目描述

点此看题

解法

考虑这样一种暴力的想法:我们把物品按照 \(s\) 排序,枚举 \(\max s\),然后扫描 \(\min s\),过程中维护前 \(m\) 大的 \(v\)

考虑优化这个暴力,对于选取个数 \(<m\) 的情况,一定选取了一段连续的区间,暴力枚举所有这样的区间即可,那么我们只需要考虑选取个数恰好 \(=m\) 的情况。

关键的 \(\tt observation\) 是:\(\min s\) 减小的目的是为了达到 \(v\) 的替换。以前的暴力是显式地让 \(\min s\) 减少,不妨切换枚举主体,枚举的过程变为显式地替换 \(v\)

所以我们把所有物品按照 \(v\) 排序,从小到大地删除物品。设现在删除的物品为 \(x\),我们只需要取出 \(x\) 的前面 \(m-1\) 个物品 \(/\) 后面 \(m-1\) 个物品,枚举长度为 \(m\) 的区间计算,这代表了替换 \(x\) 的过程。

用链表实现数的删除和取出前驱后继,时间复杂度 \(O(nm)\)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,A,B,ans,s[M],l[M],r[M],b[M],id[M];
struct node{int x,y;}a[M];
int get(int x,int y) {return y==1?x:x*x;}
int calc(int a,int b) {return get(a,A)-get(b,B);}
signed main()
{
	n=read();m=read();B=read();A=read();
	for(int i=1;i<=n;i++)
		a[i].x=read(),a[i].y=read();
	sort(a+1,a+1+n,[&](node a,node b){return a.x<b.x;});
	for(int i=1;i<=n;i++)
		s[i]=s[i-1]+a[i].y,id[i]=i;
	for(int l=1;l<=m;l++)
		for(int i=1;i+l-1<=n;i++)
			ans=max(ans,calc(s[i+l-1]-s[i-1],a[i+l-1].x-a[i].x));
	sort(id+1,id+1+n,[&](int u,int v){return a[u].y<a[v].y;});
	for(int i=1;i<n;i++) r[i]=i+1,l[i+1]=i;
	for(int i=1;i<=n;i++)
	{
		int u=id[i],t=0;
		for(int j=1,p=l[u];j<m && p;j++,p=l[p])
			b[++t]=p;
		reverse(b+1,b+1+t);
		for(int j=1,p=r[u];j<m && p;j++,p=r[p])
			b[++t]=p;
		for(int j=1;j<=t;j++) s[j]=s[j-1]+a[b[j]].y;
		for(int j=1;j+m-1<=t;j++)
			ans=max(ans,calc(s[j+m-1]-s[j-1],a[b[j+m-1]].x-a[b[j]].x));
		r[l[u]]=r[u];
		l[r[u]]=l[u];
	}
	printf("%lld\n",ans);
}

配对树

题目描述

点此看题

解法

匹配的策略是很清晰的,因为我们想让匹配两点的 \(\tt lca\) 越深越好。进一步分析,我们自底向上地来做,子树内的点是能匹配则匹配,一个子树给父亲的未匹配点至多有一个。

高妙的算贡献方式:考虑每一条边,这一条边会贡献当且仅当这条边链接的子树里有奇数个选出的点。那么问题转化成对每一条边,有多少个长度为偶数的区间使得子树内有奇数个区间内的点。

对于一棵子树,把在子树中的点设置为 \(1\),不在子树中的点设置为 \(0\),问题转化成区间和为奇数的偶区间有多少个。写出条件,其实就是:\(i=j\bmod 2,s[j]-s[i]=1\bmod 2\)

考虑分奇偶位置维护,统计 \(s\)\(0/1\) 的个数,就可以很方便地计算答案。推广到多棵子树的情况,我们维护一棵差分标记的线段树,按照题目给的序列初始化,做线段树合并即可,时间复杂度 \(O(n\log n)\)

总结

注意贡献的主体,比如本题如果计算 \(\tt lca\) 处的贡献是不好做的。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
const int N = 20*M;
const int MOD = 998244353;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,tot,F[M],rt[M];
int cnt,ls[N],rs[N],f[N][2][2],s[N];
struct edge{int v,c,next;}e[M<<1];
void up(int x,int l,int r)
{
	s[x]=s[ls[x]]+s[rs[x]];
	int k=s[ls[x]]&1,mid=(l+r)>>1;
	for(int i=0;i<2;i++)
		for(int j=0;j<2;j++)
			f[x][i][j]=f[ls[x]][i][j]+
			f[rs[x]][i^k][j];
	if(!ls[x]) f[x][0][0]+=mid/2-(l-1)/2,
		f[x][0][1]+=(mid+1)/2-l/2;
	if(!rs[x]) f[x][k][0]+=r/2-mid/2,
		f[x][k][1]+=(r+1)/2-(mid+1)/2;
}
void ins(int &x,int l,int r,int p)
{
	if(!x) x=++cnt;
	f[x][0][0]=r/2-(l-1)/2;
	f[x][0][1]=(r+1)/2-l/2;
	if(l==r) {s[x]++;return ;}
	int mid=(l+r)>>1;
	if(mid>=p) ins(ls[x],l,mid,p);
	else ins(rs[x],mid+1,r,p);
	up(x,l,r);
}
int merge(int x,int y,int l,int r)
{
	if(!x || !y) return x+y;
	int mid=(l+r)>>1;
	ls[x]=merge(ls[x],ls[y],l,mid);
	rs[x]=merge(rs[x],rs[y],mid+1,r);
	up(x,l,r);return x;
}
void dfs(int u,int fa)
{
	for(int i=F[u];i;i=e[i].next)
	{
		int v=e[i].v,c=e[i].c;
		if(v==fa) continue;
		dfs(v,u);
		ans=(ans+(ll)f[rt[v]][0][0]*
		f[rt[v]][1][0]%MOD*c)%MOD;
		ans=(ans+(ll)f[rt[v]][0][1]*
		f[rt[v]][1][1]%MOD*c)%MOD;
		rt[u]=merge(rt[u],rt[v],1,m+1);
	}
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read(),c=read();
		e[++tot]=edge{v,c,F[u]},F[u]=tot;
		e[++tot]=edge{u,c,F[v]},F[v]=tot;
	}
	for(int i=1;i<=m;i++)
		ins(rt[read()],1,m+1,i);
	dfs(1,0);
	printf("%d\n",ans);
}

百鸽笼

题目描述

点此看题

解法

主动使用容斥,假设现在我们要让 \(i\) 留到最后,枚举集合 \(S\) 都在 \(i\) 之后消失,那么答案是:

\[\sum_{S} (-1)^{|S|}\cdot g(i,S) \]

其中 \(g(i,S)\) 表示集合 \(S\) 都在 \(i\) 之后消失的概率,由于容斥的特性,我们不需要考虑 \(S\cup \{i\}\) 以外的元素,所以每一步选取的概率都是固定的 \(\frac{1}{|S|+1}\),设选取总数为 \(L\),那么总概率是 \(\frac{1}{(|S|+1)^L}\)

现在就变得很好做了,设 \(f(i,j)\) 表示 \(S\) 的大小是 \(i\)\(L=j\) 的容斥系数。加入一个元素时,讨论这个元素是否出现在集合 \(S\) 中,已经被选取了多少个(小于 \(a_x\) 个),暴力做时间复杂度 \(O(n^6)\)

发现可以退背包,时间复杂度 \(O(n^5)\)

#include <cstdio>
const int M = 35;
const int N = 1005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],f[M][N],C[N][N];
void add(int &x,int y) {x=(x+y)%MOD;}
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
int inv(int x) {return qkpow(x,MOD-2);}
void ins(int n,int m,int k)
{
	for(int i=n-1;i>=0;i--)
		for(int j=m;j>=0;j--) if(f[i][j])
			for(int t=0;t<k;t++)
				add(f[i+1][j+t],MOD-f[i][j]*C[j+t][t]%MOD);
}
void del(int n,int m,int k)
{
	for(int i=0;i<n;i++)
		for(int j=0;j<=m;j++) if(f[i][j])
			for(int t=0;t<k;t++)
				add(f[i+1][j+t],f[i][j]*C[j+t][t]%MOD);
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		m+=(a[i]=read());
	for(int i=0;i<N;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
	f[0][0]=1;
	for(int i=1,s=0;i<=n;i++)
		ins(i,s,a[i]),s+=a[i];
	for(int i=1;i<=n;i++)
	{
		del(n,m,a[i]);int r=0;
		for(int j=0;j<n;j++)
			for(int k=0;k<=m-a[i];k++) if(f[j][k])
				add(r,f[j][k]*qkpow(inv(j+1),k+a[i])
				%MOD*C[k+a[i]-1][a[i]-1]);
		printf("%lld ",(r+MOD)%MOD);
		ins(n-1,m-a[i],a[i]);
	}
}
posted @ 2022-07-13 21:46  C202044zxy  阅读(161)  评论(0编辑  收藏  举报