#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\) 的出现次数,那么方案数是:
设 \(sum_{i,j}=\sum_{k\geq i} dp_{k,j}\),那么可以写出转移:
由于最优的序列一定是 \(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;
}