#18 CF494E & CF1039E & CF1158F

Sharti

题目描述

点此看题

解法

话说这题的证明我真的是一个都不会,只能见识一下这题的思路了

首先游戏可以拆分成子游戏,我们只需要求出 \(dp[i][j]\) 表示位置 \((i,j)\) 白棋的 \(\tt sg\) 值。

忽略 \(k\) 的限制,考虑对于一维问题有这样一个结论:\(dp[i]=lowbit(i)\);所以推广到二维的情况,加上 \(k\) 的限制,并且结合打表可以发现这样的结论:\(dp[i][j]=\min(lowbit(i),lowbit(j),greatbit(k))\)

考虑到 \(dp[i][j]\) 的取值很少,枚举 \(2^c\),求出 \(dp[i][j]\geq 2^c\) 的个数,简单差分就可以还原每一种 \(dp[i][j]\) 的个数。求出这个个数可以把矩形写成 \((\lceil\frac{a}{x}\rceil,\lceil\frac{b}{x}\rceil,\lfloor\frac{c}{x}\rfloor,\lfloor\frac{d}{x}\rfloor)\) 的形式(类似数论中的一些小技巧),然后做矩形面积并即可。

时间复杂度 \(O(m\log^2m)\)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
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,k,t,h,a[M],b[M],c[M],d[M],g[M];
int ans[35],cov[M<<2],sum[M<<2];
struct node
{
	int l,r,x,f;
	bool operator < (const node &b) const
		{return x<b.x;}
}s[M];
void upd(int i,int l,int r,int L,int R,int f)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		cov[i]+=f;
		if(cov[i]) sum[i]=g[r]-g[l-1];
		else sum[i]=sum[i<<1]+sum[i<<1|1];
		return ;
	}
	int mid=(l+r)>>1;
	upd(i<<1,l,mid,L,R,f);
	upd(i<<1|1,mid+1,r,L,R,f);
	if(cov[i]) sum[i]=g[r]-g[l-1];
	else sum[i]=sum[i<<1]+sum[i<<1|1];
}
int work(int x)
{
	t=0;int r=0;
	for(int i=1;i<=m;i++)
	{
		int lx=(a[i]-1)/x+1,ly=(b[i]-1)/x+1;
		int rx=c[i]/x,ry=d[i]/x;
		if(lx>rx || ly>ry) continue;
		s[++t]=node{ly,ry,lx,1};g[t]=ly-1;
		s[++t]=node{ly,ry,rx+1,-1};g[t]=ry;
	}
	if(!t) return 0;
	sort(g+1,g+1+t);
	int nt=unique(g+1,g+1+t)-g-1;
	for(int i=1;i<=t;i++)
	{
		s[i].l=lower_bound(g+1,g+1+nt,s[i].l)-g;
		s[i].r=lower_bound(g+1,g+1+nt,s[i].r)-g;
	}
	sort(s+1,s+1+t);
	for(int i=1;i<=4*nt;i++) sum[i]=cov[i]=0;
	for(int i=1;i<=t;i++)
	{
		r^=(s[i].x-s[i-1].x)%2*sum[1]%2;
		upd(1,1,nt,s[i].l,s[i].r,s[i].f);
	}
	return r;
}
signed main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=m;i++)
		a[i]=read(),b[i]=read(),c[i]=read(),d[i]=read();
	for(int i=0;i<=30;i++)
		ans[i]=work(1<<i);
	while((1ll<<h+1)<=k) h++;
	ans[h+1]=0;
	for(int i=0;i<=h;i++)
		if((ans[i+1]-ans[i])%2)
		{
			puts("Hamed");
			return 0;
		}
	puts("Malek");
	return 0;
}

Summer Oenothera Exhibition

题目描述

点此看题

解法

显然的贪心是:能划段就划段。

做法 \(1\):维护 \(i\) 的后继 \(t_i\),表示如果以 \(i\) 为段头,那么将会划段 \([i,t_i)\);我们从小到大扫描每个询问(极差限制从紧到松),在这个过程中 \(t_i\) 是不下降的,瓶颈在于维护 \(t_i\) 的变化。

做法 \(2\):用倍增的方法暴力找到当前点的后继,瓶颈在于可能要跳很多次。

结合这两种做法:如果 \(t_i\leq \sqrt n\),我们维护 \(t_i\) 的变化,否则在遇到 \(i\) 的时候暴力跳跃。

回忆 弹飞绵羊,可以通过 \(\tt lct\) 来维护每个点的后继,这样跳跃就只需要 findroot,并且可以很方便地维护步数。

复杂度分析:每个点至多带来 \(O(\sqrt n)\)\(\tt lct\) 上的修改,时间复杂度 \(O(n\sqrt n\log n)\),实现时需要存下后继变化对应的询问是谁;\(\tt lct\) 跳跃和倍增跳跃轮换进行,时间复杂度 \(O(n\sqrt n\log n)\),如果被卡了可以适当调块长。

总结

对于此类序列上跳跃问题,可能不需要深入分析"跳跃的性质,只需要在跳跃次数上下功夫即可。

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int M = 100005;
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,w,m,B,a[M],t[M],ans[M];vector<int> b[M];
struct node
{
	int x,id;
	bool operator < (const node &b) const
		{return x==b.x?id<b.id:x<b.x;}
}q[M];
//part I : st-table
int mx[M][17],mi[M][17],lg[M];
void build()
{
	for(int i=1;i<=n;i++)
		mx[i][0]=mi[i][0]=a[i];
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]);
			mi[i][j]=min(mi[i][j-1],mi[i+(1<<j-1)][j-1]);
		}
}
int get(int x,int k)
{
	int A=0,B=1e9;
	for(int i=16;i>=0;i--) if(x+(1<<i)<=n+1)
	{
		int tx=max(mx[x][i],A);
		int ty=min(mi[x][i],B);
		if(tx-ty<=k) A=tx,B=ty,x+=1<<i;
	}
	return x;
}
int ask1(int l,int r)
{
	int k=lg[r-l+1];
	return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
int ask2(int l,int r)
{
	int k=lg[r-l+1];
	return min(mi[l][k],mi[r-(1<<k)+1][k]);
}
//part II : link-cut-tree
int fa[M],ch[M][2],d[M];
void up(int x)
{
	d[x]=d[ch[x][0]]+d[ch[x][1]]+1;
}
int nrt(int x)
{
	return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
int chk(int x)
{
	return ch[fa[x]][1]==x;
}
void rotate(int x)
{
	int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
	ch[y][k]=w;fa[w]=y;
	if(nrt(y)) ch[z][chk(y)]=x;fa[x]=z;
	ch[x][k^1]=y;fa[y]=x;
	up(y);up(x);
}
void splay(int x)
{
	while(nrt(x))
	{
		int y=fa[x];
		if(nrt(y))
		{
			if(chk(x)==chk(y)) rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
void access(int x)
{
	for(int y=0;x;x=fa[y=x])
		splay(x),ch[x][1]=y,up(x);
}
void link(int u,int v)//u<v u->v
{
	access(u);splay(u);fa[u]=v;
}
void cut(int u,int v)
{
	access(u);splay(v);
	fa[u]=ch[v][1]=0;up(v);
}
int findrt(int x)
{
	access(x);splay(x);
	while(ch[x][0]) x=ch[x][0];
	splay(x);return x;
}
//part III : process the queries
void upd(int i,int x)
{
	if(t[i]) cut(i,t[i]);
	t[i]=get(i,x);
	if(t[i]-i<=B)
	{
		link(i,t[i]);
		if(t[i]>n) return ;
		int nxt=ask1(i,t[i])-ask2(i,t[i]);
		nxt=lower_bound(q+1,q+1+m,node{nxt,0})-q;
		if(nxt<=m) b[nxt].push_back(i);
	}
}
int work(int i,vector<int> &b)
{
	for(int x:b) upd(x,q[i].x);
	int r=0;b.clear();
	for(int p=1;p<=n;)
	{
		p=findrt(p);
		r+=d[p]-1;
		if(p>n) return r;
		p=get(p,q[i].x);r++;
	}
	return r;
}
signed main()
{
	n=read();w=read();m=read();B=200;
	for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++)
		q[i].x=w-read(),q[i].id=i;
	sort(q+1,q+1+m);
	build();
	for(int i=1;i<=n;i++)
		d[i]=1,b[1].push_back(i);
	for(int i=1;i<=m;i++)
		ans[q[i].id]=work(i,b[i]);
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]-1);
}

Density of subarrays

题目描述

点此看题

解法

好题,好题,超级好题!!!

首先考虑如何计算序列 \(s\) 的密度。如果密度为 \(\geq x\),那么满足开头为 \(1,2...c\),后面部分为任意长度为 \(x-1\) 的序列,都在这个序列中出现过。我们找到序列 \(s\)\(1,2...c\) 第一次出现的位置 \(p_1,p_2...p_c\),那么满足任意 \(p_i\)\(p_i\) 后面的串的密度 \(\geq x-1\)这等价于 \(\max p_i+1\) 后面的串密度 \(\geq x-1\),所以我们归纳到了子问题 \((s[\max p_i+1,n],x-1)\),递归判断即可

由于判断方法是递归的形式,可以直接魔改成计数 \(dp\),设 \(dp[i][j]\) 表示以 \(i\) 开头的子序列(必须包含 \(i\)),密度 \(\geq j\) 的有多少个。转移考虑枚举 \([i,k]\) 包含一段 \(1,2...c\),为了不算重我们强制 \(k\) 为对应颜色的唯一位置,设 \(cnt_i\) 表示颜色 \(i\) 的出现次数,那么方案数是:

\[f_{l,r}=[a_l\not=a_r]2^{cnt_{a_l}-1}\prod_{i\not=a_l\and i\not=a_r} (2^{cnt_i}-1) \]

\(sum_{i,j}=\sum_{k\geq i} dp_{k,j}\),那么可以写出转移:

\[dp_{i,j}=\sum_{k} f_{i,k}\cdot sum_{k+1,j-1} \]

由于最优的序列一定是 \(1,2...c\) 的重复,所以这限制了 \(j\) 的范围在 \(\frac{n}{c}\) 以内,所以该做法的时间复杂度为 \(O(\frac{n^3}{c})\),但是它在 \(c\) 较小的时候无法通过,我们需要再想一种能解决较小 \(c\) 得到方法。

可以依次考虑每个元素选不选取,设 \(dp[i][j][s]\) 表示考虑了前 \(i\) 个元素,组成子序列的密度是 \(j\),现在集合拼凑到了 \(s\) 的方案数。转移的时候如果 \(s\) 达到全集,就让 \(j\leftarrow j+1,s\leftarrow 0\),时间复杂度 \(O(\frac{n^2}{c}2^c)\)

如果 \(c\leq \log_2 n\),执行状压的方法,时间复杂度 \(O(\frac{n^3}{\log n})\);否则执行第一种方法,时间复杂度 \(O(\frac{n^3}{\log n})\),达到了平衡状态,如果精细实现的话就可以通过,注意时刻卡好循环枚举的上界以保证复杂度。

总结

如果限制的主体数量级巨大(比如集合、子序列),那么可以考虑归纳、递归的方法描述限制。并且使用这种方法还有一种好处,就是递归子问题很容易拓展到 \(dp\) 的形式。

#include <cstdio>
const int M = 3005;
const int MOD = 998244353;
#define ll long long
ll read()
{
	ll 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;
}
ll n,c,a[M],cnt[M],pw[M],inv[M];
int dp[M][M],sum[M][M],z[M][M],f[2][M][M];
void add(int &x,ll y) {x=(x+y)%MOD;}
ll qkpow(ll a,ll b)
{
	ll r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
void work1()
{
	sum[n+1][0]=pw[0]=inv[0]=1;
	for(ll i=1;i<=n;i++)
	{
		pw[i]=pw[i-1]*2%MOD;
		inv[i]=qkpow(pw[i]-1,MOD-2);
	}
	for(ll l=1;l<=n;l++)
	{
		for(ll i=1;i<=c;i++) cnt[i]=0;
		cnt[a[l]]=1;ll nw=1,tot=1;
		for(ll r=l+1;r<=n;r++)
		{
			if(a[r]==a[l]) nw=nw*2%MOD;
			else
			{
				nw=nw*inv[cnt[a[r]]]%MOD;
				cnt[a[r]]++;
				nw=nw*(pw[cnt[a[r]]]-1)%MOD;
				if(cnt[a[r]]==1) tot++;
			}
			if(tot==c && a[l]!=a[r])
				z[l][r]=nw*inv[cnt[a[r]]]%MOD;
		}
	}
	for(ll i=n;i>=1;i--)
	{
		dp[i][0]=pw[n-i];
		for(ll j=1;j<=(n-i+1)/c;j++)
		{
			for(ll k=i+c-1;k<=n && sum[k+1][j-1];k++)
				add(dp[i][j],1ll*z[i][k]*sum[k+1][j-1]);
			if(!dp[i][j]) break;
		}
		for(ll j=0;j<=n-i+1;j++)
			sum[i][j]=(sum[i+1][j]+dp[i][j])%MOD;
	}
	sum[1][0]--;
	for(ll i=0;i<=n;i++)
		printf("%lld ",(sum[1][i]-sum[1][i+1]+MOD)%MOD);
	puts("");
}
void work2()
{
	f[0][0][0]=1;
	for(ll i=1,w=1;i<=n;i++,w^=1)
	{
		for(ll j=0;j<=i/c;j++)
			for(ll s=0;s<(1<<c);s++)
				f[w][j][s]=0;
		for(ll j=0;j<=i/c;j++)
			for(ll s=0;s<(1<<c);s++) if(f[w^1][j][s])
			{
				//do not choose
				add(f[w][j][s],f[w^1][j][s]);
				//choose
				ll t=s|(1<<a[i]-1);
				if(t==(1<<c)-1) add(f[w][j+1][0],f[w^1][j][s]);
				else add(f[w][j][t],f[w^1][j][s]);
			}
	}
	for(ll i=0;i<=n;i++)
	{
		int ans=(i==0)?MOD-1:0;
		for(ll s=0;s<(1<<c);s++)
			add(ans,f[n&1][i][s]);
		printf("%d ",ans);
	}
	puts("");
}
signed main()
{
	n=read();c=read();
	for(ll i=1;i<=n;i++) a[i]=read();
	if(c>11) work1();
	else work2();
	return 0;
}
posted @ 2022-05-23 08:58  C202044zxy  阅读(500)  评论(2编辑  收藏  举报