@2 UOJ191 & UOJ422 & UOJ425

[集训队互测2016] Unknown

题目描述

点此看题

解法

果然是论文题啊,还是很有分量。虽然茴字有四种写法,但是我还是讲解最简单的那一种吧。

看到叉积本能的害怕了,但是数据范围说 \(x\geq 1\),并且根据以前的做题经验,可以转化为这个形式:

\[(x_1,y_1)\times (x_2,y_2)=x_1y_2-x_2y_1=x_1\cdot(-\frac{y_1}{x_1}x_2+y_2) \]

问题转化成平面上有若干个点 \((x_i,y_i)\),每次询问用一条斜率为 \(k=\frac{y}{x}\) 的直线去截取这些点,所得到的最大截距乘 \(x\) 就是答案。所以本题的关键就是维护凸包,在凸包上二分就可以得到答案。

考虑这样一种部分分:无2操作,3操作的询问为全部区间,可以直接用二进制分组的方法。即我们类似栈一样维护若干个组,每次插入就向末尾加入一个大小为 \(1\) 的组,如果末尾两个组的大小相同就合并它们。

这样任何时刻场上都只有 \(O(\log n)\) 个组,合并的复杂度为 \(O(n\log n)\),查询的复杂度为 \(O(n\log ^2n)\)


考虑把二进制分组搬到线段树上,也就是说,当左右儿子都满的时候才合并得到这个点的凸包

但是删除操作很不好搞啊,每次似乎要重构一条链上的凸包,复杂度不可接受。考虑做出这样的改动:对于每一层,我们都允许一个点,在它达到构建的条件时不被构建

这样对于删除操作,我们直接在包含它的链上打上不能使用的标记;对于加入操作,如果某个点达到了构建的条件,那么我们构建它的上一个点(这样保证每一层都留出一个点不被构建);对于询问操作,如果访问到的点没有被构建,那么直接递归它的儿子。

用势能法证明重构的时间复杂度,设 \(len_i\) 表示第 \(i\) 层带标记组的总长度。打标记的操作会使 \(len_i\)\(0\) 变成 \(2^i\),重构的操作会使得 \(len_i\)\(2^{i+1}\) 变成 \(2^i\);单次插入和删除对于 \(len_i\) 的改变幅度都是 \(1\)

令势能函数 \(E(i)=|len_i-2^i|\),插入删除只会至多有 \(1\) 的贡献,而无论是打标记还是重构都会减少 \(2^i\) 的势能,所以两次重构之间必须要有 \(2^i\) 次插入或删除,那么单层的均摊复杂度是 \(O(n)\),总的均摊复杂度是 \(O(n\log n)\)

对于询问,考虑区间 \([l,r]\) 至多被拆分到一个被打标记的点,此时会被分解为 \(O(\log n)\) 个点,所以时间复杂度还是 \(O(n\log ^2n)\)

总时间复杂度 \(O(n\log^2 n)\),使用 \(\tt zkw\) 线段树实现较为方便。空间复杂度 \(O(n\log n)\),需要用指针分配来精细实现。

总结

对于只在末尾插入\(/\)末尾删除的题目,把二进制分组放在线段树上是通用做法。

#include <cstdio>
#include <iostream>
using namespace std;
const int mx = 1<<19;
const int M = 300005;
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;
}
struct node
{
	int x,y;
	node(int X=0,int Y=0) : x(X) , y(Y) {}
	friend ll operator * (node a,node b)
		{return (ll)a.x*b.y-(ll)b.x*a.y;}
	friend node operator - (node a,node b)
		{return node(a.x-b.x,a.y-b.y);}
}nd[20][M],*h[mx+M],*a;
int n,m,ans,tp,siz[mx+M];
void ins(node t)
{
	if(tp==1 && t.x==a->x)
	{
		if(t.y<=a->y) return ;
		tp--;
	}
	while(tp>1 && (a[tp-1]-a[tp-2])*(t-a[tp-1])>=0)
		tp--;
	a[tp++]=t;
}
void build(int x)
{
	if(x<=0 || siz[x]) return ;//has been built
	a=h[x];tp=0;
	node *px=h[x<<1],*A=px+siz[x<<1];
	node *py=h[x<<1|1],*B=py+siz[x<<1|1];
	while(px<A && py<B)
	{
		if(px->x<=py->x) ins(*px),px++;
		else ins(*py),py++;
	}
	while(px<A) ins(*px),px++;
	while(py<B) ins(*py),py++;
	siz[x]=tp;
}
void push()
{
	int x=read(),y=read(),u=(++n)+mx;
	siz[u]=1;*h[u]=node(x,y);
	for(int p=u;p&1;p>>=1,build(p-1));
	//p should not be built , p-1 should be built
}
void pop()//just tag it
{
	for(int p=(n--)+mx>>1;siz[p]>0;p>>=1) siz[p]=0;
}
ll get(int x,node t)
{
	if(siz[x]<=0) return max(get(x<<1,t),get(x<<1|1,t));
	int l=0,r=siz[x]-1;ll res=-(1ll<<60);a=h[x];
	while(l+4<r)
	{
		int mid=(l+r)>>1;
		ll c1=t*a[mid],c2=t*a[mid+1];
		res=max(res,max(c1,c2));
		if(c1<=c2) l=mid+1;
		else r=mid-1;
	}
	while(l<=r) res=max(res,t*a[l]),l++;
	return res;
}
int ask()
{
	ll res=-(1ll<<60);node t;
	int l=read()+mx-1,r=read()+mx+1;
	t.x=read();t.y=read();
	for(;l^r^1;l>>=1,r>>=1)
	{
		if((l&1)==0) res=max(res,get(l^1,t));
		if(r&1) res=max(res,get(r^1,t));
	}
	return (res%MOD+MOD)%MOD;
}
void init()
{
	for(int l=mx,r=mx+min(M-5,m),d=0;l;l>>=1,r>>=1,d++)
		for(int i=l;i<=r;i++)//distribute size
			h[i]=nd[d]+(i-l<<d),siz[i]=0;
	for(int i=mx;i;i>>=1) siz[i]=-1;//ban
}
signed main()
{
	read();
	while(m=read())
	{
		init();ans=n=tp=0;
		while(m--)
		{
			int op=read();
			if(op==1) push();
			if(op==2) pop();
			if(op==3) ans^=ask();
		}
		printf("%d\n",ans);
	}
}

[集训队作业2018] 小Z的礼物

题目描述

点此看题

解法

考虑 \(\min-\max\) 反演:

\[E(\max(S))=\sum_{T\subseteq S,T\not=\varnothing} (-1)^{|T|+1}\cdot E(\min(T)) \]

问题在于求出带容斥系数的 \(E(\min(T))\),设 \(c(T)\) 表示只考虑 \(T\) 集合中的物品,有多少种选取方法可以选到这些物品,设 \(t=2nm-n-m\) 表示可能的选取方式,那么 \(E(\min(T))=\frac{c(T)}{t}\)

直接 \(dp\) 计算容斥系数,设 \(f(i,j,s,k)\) 表示考虑到点 \((i,j)\),现在轮廓线的状态是 \(s\),已经积累的选取方案数是 \(k\),转移讨论这个点选还是不选,如果选的话带上 \(-1\) 的容斥系数,可以结合下图来理解转移过程:

时间复杂度 \(O(n^2m^2\cdot 2^n)\)所以遇见这种简单的插头 dp 就不要怕,直接上

#include <cstdio>
#include <cstring>
const int MOD = 998244353;
const int M = 1205;
#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,t,ans,inv[M];char p[M][M];
int f[1<<6][M],g[1<<6][M];
void add(int &x,int y) {x=(x+y)%MOD;}
signed main()
{
	n=read();m=read();t=2*n*m-n-m;
	for(int i=1;i<=n;i++) scanf("%s",p[i]+1);
	inv[0]=inv[1]=1;
	for(int i=2;i<=t;i++)
		inv[i]=(ll)inv[MOD%i]*(MOD-MOD/i)%MOD;
	f[0][0]=MOD-1;
	for(int i=1;i<=m;i++) for(int j=1;j<=n;j++)
	{
		memcpy(g,f,sizeof f);
		memset(f,0,sizeof f);
		for(int s=0;s<(1<<n);s++)
			for(int k=0;k<=t;k++) if(g[s][k])
			{
				int ns=s&((1<<n)-1-(1<<j-1));
				add(f[ns][k],g[s][k]);
				if(p[j][i]=='.') continue;
				ns|=(1<<j-1);int nk=k;
				nk+=(i<m);nk+=(j<n);
				if(i>1 && !(s>>(j-1)&1)) nk++;
				if(j>1 && !(s>>(j-2)&1)) nk++;
				add(f[ns][nk],MOD-g[s][k]);
			}
	}
	for(int s=0;s<(1<<n);s++)
		for(int i=1;i<=t;i++)
			add(ans,(ll)f[s][i]*inv[i]%MOD);
	ans=(ll)ans*t%MOD;
	printf("%d\n",ans);
}

[集训队作业2018] strings

题目描述

点此看题

解法

考虑折半搜索,对于前半段 \(m=\lfloor\frac{n}{2}\rfloor\),我们处理出每一种可以对应哪些模板串,时间复杂度 \(O(q\cdot m\cdot 2^m)\)

对于每一个模板串,我们处理出它对应哪些后半段,时间复杂度 \(O(q\cdot m\cdot 2^m)\)

考虑套上 bitset,即枚举每一个前半段,再枚举匹配到的模式串,用 bitset 储存对应的后半段,时间复杂度 \(O(\frac{q\cdot 2^n}{w})\)

复杂度瓶颈是枚举模式串,考虑类似四毛子一样分块,设块长为 \(B\),每一块内预处理出块内模式串的所有子集,对应的 bitset,时间复杂度 \(O(\frac{q\cdot 2^{m+B}}{w})\)

那么我们就不需要枚举模式串,对于每个块,处理出子集后用处理的结果查询即可,时间复杂度 \(O(\frac{q\cdot 2^n}{wB})\)

\(B=10\),总时间复杂度 \(O(\frac{q\cdot 2^{n}}{10\cdot w})\)

#include <cstdio>
#include <bitset>
#include <iostream>
using namespace std;
const int M = 105;
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,m1,m2,q,s,ans;char c[M][M];
bitset<1<<15> a[M],b[10][1<<10],nw;
int equal(char c,int x) {return c==x+'0' || c=='?';}
signed main()
{
	n=read();q=read();
	m1=n>>1;m2=n-m1;s=10;
	for(int i=0;i<q;i++)
		scanf("%s",c[i]);
	for(int i=0;i<q;i++) for(int j=0;j<(1<<m2);j++)
	{
		int f=1;
		for(int k=0;k<m2;k++)
			f&=equal(c[i][m1+k],j>>k&1);
		a[i][j]=f;
	}
	for(int i=0;i*s<q;i++)
	{
		int l=i*s,r=min(l+s,q);
		for(int j=0;j<(1<<s);j++)
			for(int k=l;k<r;k++)
				if(j>>(k-l)&1) b[i][j]|=a[k];
	}
	//
	for(int j=0;j<(1<<m1);j++)
	{
		nw.reset();
		for(int i=0;i*s<q;i++)
		{
			int l=i*s,r=min(l+s,q),t=0;
			for(int k=l;k<r;k++)
			{
				int f=1;
				for(int x=0;x<m1;x++)
					f&=equal(c[k][x],j>>x&1);
				t|=f<<(k-l);
			}
			nw=nw|b[i][t];
		}
		ans+=nw.count();
	}
	printf("%d\n",ans);
}
posted @ 2022-07-07 09:36  C202044zxy  阅读(384)  评论(0编辑  收藏  举报