• #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;
    }
    
  • 相关阅读:
    数据清洗SQL,一次性的工作
    PGSQL将字段改为不能为空
    C# WINFORM中splitcontainer调整列宽的方法
    【其他】etcd
    正则贪婪匹配
    MarsGIS for Cesium三维地图框架建设方案
    mysql 查询分区表中各个分区的数据量
    推送远程仓库(github/gitlab) 报错:Host key verification failed. fatal: Could not read from remote repository.
    sshkeygen t rsa C xxxx@xxxx.com解释
    DNS、CDN加速和域名解析之间的关系
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16299969.html
Copyright © 2020-2023  润新知