NOI ONLINE 2021题解
NOI Online 2021 提高组 愤怒的小N
description
给出\(n\) 和一个\(k-1\) 次多项式\(f(x)\) ,求\(\sum_{i=0}^{n-1}[pop(i)\equiv1\bmod 2]f(i)\)
其中\(pop(i)\) 表示\(i\) 在二进制表示中\(1\) 的个数。
data range
\(\log_2n\le 5\times 10^5,k\le 500\)
solution
题解和官方题解如有雷同,纯属抄袭
此题等于99%的汗水加上1%的灵感,但往往1%的灵感比99%的汗水更重要。
来,我作为一个憨批,应该如何思考呢?
肯定是先打表。考虑\([0,16)\) 符合条件的数有
容易发现恰好有\(8\) 个数(\(16\) 的一半)
根据题目所求,稍微往高次推推?
妙哉!然而到了\(4\) 次方似乎就不对劲了。。。
没关系。注意到\(16=2^4\) ,也就是说严格低于\(2\) 的幂次的次方应该都有这个结论。用数学语言表达下
考虑证明这个结论。首先要有一个递推式,不难发现:
采用数学归纳法证明,归纳基础\(b=0\) 时结论显然成立,假设\(b<h\) 时结论均成立,下面对\(b=h\) 进行归纳:
证毕。现在我们已经得到了一个很好的性质,让我们从头开始推,看能不能用上。
考虑减号前的式子,将\(f(i)\) 展开之后交换和式就是一个自然数幂和,这个东西怎么做都行,这里不再赘述。
主要考虑后面的式子。我们想要让它尽量往\(g_{b,c}\) 上靠,但\(n\) 不一定是\(2\) 的正整数次幂。于是我们可以考虑\(b_0<b_1<\cdots<b_{t-1}\) 满足
然后类似数位\(dp\) 考虑第一位小于\(n\) 的位置,那么
然后就可做了。根据性质,只有\(\mathcal O(k^2)\) 个\(g\) 需要处理,处理每一个需要\(\mathcal O(k)\) ,这样是\(\mathcal O(k^3)\) 的。然后枚举\(i,c,j\) 也只有\(\mathcal O(k^3)\) (因为只用枚举\(b_c\le j\) 的\(c\))。于是就做完了。
time complexity
\(\mathcal O(\log_2n+k^3)\)
code
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7,N=5e5+5,K=505;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline void inc(int&x,int y){x=add(x,y);}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline void rec(int&x,int y){x=dec(x,y);}
char ch[N];int n,k,a[K],pw[N],s[K],c[K][K],g[K][K],tmp[K],p[N],cnt,sum[N];
inline int qpow(int x,int y)
{
int ret=1;
for(;y;y>>=1,x=1ll*x*x%mod)
if(y&1)ret=1ll*ret*x%mod;
return ret;
}
int main()
{
scanf("%s",ch+1);
n=strlen(ch+1);int now=0;
for(int i=1;i<=n;++i)
{
now=add(add(now,now),(ch[i]=='1'));
if(ch[i]=='1')p[cnt++]=n-i;
}
reverse(p,p+cnt);
scanf("%d",&k);
for(int i=0;i<k;++i)scanf("%d",a+i);
pw[0]=1;for(int i=1;i<=k;++i)pw[i]=1ll*pw[i-1]*now%mod;
c[0][0]=1;
for(int i=1;i<=k;++i)
{
c[i][0]=1;
for(int j=1;j<=i;++j)
c[i][j]=add(c[i-1][j],c[i-1][j-1]);
}
s[0]=now;
for(int i=1;i<k;++i)
{
s[i]=pw[i+1];
for(int j=0;j<i;++j)
rec(s[i],1ll*c[i+1][j]*s[j]%mod);
s[i]=1ll*s[i]*qpow(i+1,mod-2)%mod;
}
pw[0]=1;for(int i=1;i<=max(n,k);++i)pw[i]=add(pw[i-1],pw[i-1]);
g[0][0]=1;
for(int i=1;i<=k;++i)
{
tmp[0]=1;for(int j=1;j<=k;++j)tmp[j]=1ll*tmp[j-1]*pw[i-1]%mod;
for(int j=i;j<=k;++j)
{
g[i][j]=g[i-1][j];
for(int o=0;o<=j;++o)
rec(g[i][j],1ll*c[j][o]*tmp[j-o]%mod*g[i-1][o]%mod);
}
}
for(int i=cnt-1;~i;--i)sum[i]=add(sum[i+1],pw[p[i]]);
int ans=0;
for(int i=0;i<k;++i)
{
int res=0;
for(int j=0;j<cnt&&p[j]<=k;++j)
{
int s=sum[j+1],ret=0;
tmp[0]=1;for(int o=1;o<=i;++o)tmp[o]=1ll*tmp[o-1]*s%mod;
for(int o=p[j];o<=i;++o)
inc(ret,1ll*c[i][o]*tmp[i-o]%mod*g[p[j]][o]%mod);
((cnt-1-j)&1)?rec(res,ret):inc(res,ret);
}
rec(ans,1ll*a[i]*res%mod);
}
for(int i=0;i<k;++i)inc(ans,1ll*a[i]*s[i]%mod);
printf("%d\n",1ll*qpow(2,mod-2)*ans%mod);
return 0;
}
NOI Online 2021 提高组 积木小赛
description
给出长度为\(n\) 的字符串\(s,t\) 求\(t\) 中本质不同的子串个数满足它是\(s\) 的一个子序列。
data range
\(n\le 3000\)
solution
直接按照题意模拟即可。注意哈希表不要写错呜呜
code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=3005,mod=1e5+7,base=101;
int n;char s[N],t[N];
struct ht
{
int tot;vector<ull>v[mod+5];
inline void ins(ull p)
{
int tm=p%mod,las;
for(int i=0;i<v[tm].size();++i)
if(v[tm][i]==p)return;
v[tm].push_back(p);
}
inline bool query(ull p)
{
int tm=p%mod;
for(int i=0;i<v[tm].size();++i)
if(v[tm][i]==p)return true;
return false;
}
}A;
int main()
{
scanf("%d",&n);
scanf("%s%s",s+1,t+1);
int ans=0;
for(int i=1;i<=n;++i)
{
ull now=0;int pos=1;
for(int j=i;j<=n;++j)
{
now=now*base+t[j]-'a'+1;
while(pos<=n&&s[pos]!=t[j])++pos;
if(pos>n)break;++pos;
if(A.query(now))continue;
++ans;A.ins(now);
}
}
printf("%d\n",ans);
return 0;
}
NOI Online 2021 提高组 岛屿探险
description
给出序列,每个位置\(i\) 有两个数\(a_i,b_i\) 。每次询问有四个参数\(l_j,r_j,c_j,d_j\) ,求\(\sum_{i=l_j}^{r_j}[a_i\oplus c_j\le \min(c_j,b_i)]\)
data range
\(n\le 10^5,a_i,b_i,c_j,d_j\le 2^{24}-1\)
solution
先假设对于所有的询问都有\(l_j=1,r_j=n\) ,在此情况下考虑部分分做法。
Part 1 \(\max\{d_j\}\le \min\{b_i\}\)
此时\(b\) 可以直接忽略,对于所有的\(a_i\) 建立起一棵\(01\) 字典树,每次询问相当于给定\(c,d\) ,有多少\(a_i\) 可以满足上式。这个十分trivial ,直接分类讨论一下即可。
Part 2 \(\min\{d_j\}\ge \max\{b_i\}\)
此时\(d\) 可以直接忽略,但是仍然十分不好做。
正难则反,考虑每一\(a_i,b_i\) 对于所有询问的贡献。
于是可以先将所有询问的\(c_j\) 插入\(01\) 字典树中,每次给定\(a,b\) ,然后在相应节点上累加\(tag\) 代表在当前节点的子树中增加此次\(a_i,b_i\) 的贡献。做完后遍历\(Trie\) 树,下放标记就可以得到每个询问的答案了。
现在回到原问题。考虑将每个询问区间差分,即\([l,r]\rightarrow [1,r]-[1,l-1]\)
容易发现如果\(i\) 位置对询问\([1,x_j]\) 可能有贡献当且仅当\(i\le x_j\) ,在此情况下只有当\(b_i,d_j\) 大小关系确定时我们才可以高效地计算答案。
换句话说,只有满足一种类似二维偏序的关系时快速计算贡献才是可行的。
于是我们可以将询问离线下来,然后将所有元素先一起按照\(b_i\ or\ d_j\) 从小到大排序。而后考虑计算贡献,采用\(cdq\) 分治的思想,在当前区间中,左右两边都按照原先岛屿的顺序进行排序,而后使用经典的\(two\ pointer\)的做法扫一遍即可。此时确定了\(i,x_j\) 和\(b_i,d_j\) 的大小关系,因此可以快速地计算贡献。
time complexity
\(\mathcal O(n\log_2^2n)\)
code
#include<bits/stdc++.h>
namespace iobuff{
const int LEN=1000000;
char in[LEN+5], out[LEN+5];
char *pin=in, *pout=out, *ed=in, *eout=out+LEN;
inline char gc(void)
{
return pin==ed&&(ed=(pin=in)+fread(in, 1, LEN, stdin), ed==in)?EOF:*pin++;
}
inline void pc(char c)
{
pout==eout&&(fwrite(out, 1, LEN, stdout), pout=out);
(*pout++)=c;
}
inline void flush()
{ fwrite(out, 1, pout-out, stdout), pout=out; }
template<typename T> inline void scan(T &x)
{
static int f;
static char c;
c=gc(), f=1, x=0;
while(c<'0'||c>'9') f=(c=='-'?-1:1), c=gc();
while(c>='0'&&c<='9') x=10*x+c-'0', c=gc();
x*=f;
}
template<typename T> inline void putint(T x, char div)
{
static char s[15];
static int top;
top=0;
x<0?pc('-'), x=-x:0;
while(x) s[top++]=x%10, x/=10;
!top?pc('0'), 0:0;
while(top--) pc(s[top]+'0');
pc(div);
}
}
using namespace iobuff;
using namespace std;
const int N=2e5+5,LOG=24;
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
int n,q,cnt,ans[N];
struct quy{bool tp1;int p,c,d,tp2,id;}a[N<<1],tmp[N<<1];
inline bool cmp(const quy&x,const quy&y){return x.d<y.d;}
struct Trie
{
int rt,tot,ch[N<<5][2],sz[N<<5],tag[N<<5];
inline void pre(){rt=tot=0;}
inline int nd()
{
int p=++tot;ch[p][0]=ch[p][1]=0;
sz[p]=tag[p]=0;return p;
}
inline void ins(int num)
{
if(!rt)rt=nd();
int now=rt;
for(int i=LOG;~i;--i)
{
++sz[now];int bit=(num>>i)&1;
if(!ch[now][bit])ch[now][bit]=nd();
now=ch[now][bit];
}
++sz[now];
}
inline int del(int num)
{
int now=rt;
for(int i=LOG;~i;--i)
{
--sz[now];int bit=(num>>i)&1;
tag[ch[now][0]]+=tag[now],tag[ch[now][1]]+=tag[now];tag[now]=0;
now=ch[now][bit];
}
--sz[now];return tag[now];
}
inline int query(int c,int d)
{
int now=rt,ans=0;
for(int i=LOG;~i&&now;--i)
{
int bit=(c>>i)&1;
if((d>>i)&1)
ans+=sz[ch[now][bit]],now=ch[now][bit^1];
else now=ch[now][bit];
}
if(now)ans+=sz[now];
return ans;
}
inline void update(int c,int d)
{
int now=rt;
for(int i=LOG;~i&&now;--i)
{
int bit=(c>>i)&1;
if((d>>i)&1)
++tag[ch[now][bit]],now=ch[now][bit^1];
else now=ch[now][bit];
}
if(now)++tag[now];
}
}A,B;
inline void upd1(int i)
{
if(a[i].tp1)ans[a[i].id]+=a[i].tp2*A.query(a[i].c,a[i].d);
else B.update(a[i].c,a[i].d);
}
inline void upd2(int j)
{
if(a[j].tp1)ans[a[j].id]+=a[j].tp2*B.del(a[j].c);
else A.ins(a[j].c);
}
void cdq(int l,int r)
{
if(l>=r)return;
int mid=(l+r)>>1;
cdq(l,mid),cdq(mid+1,r);
int i=l,j=mid+1,pos=l-1;
A.pre(),B.pre();
for(;j<=r;++j)if(a[j].tp1)B.ins(a[j].c);
j=mid+1;
while(i<=mid&&j<=r)
{
if(a[i].p<a[j].p||(a[i].p==a[j].p&&!a[i].tp1&&a[j].tp1))
upd1(i),tmp[++pos]=a[i++];
else upd2(j),tmp[++pos]=a[j++];
}
while(i<=mid)upd1(i),tmp[++pos]=a[i++];
while(j<=r)upd2(j),tmp[++pos]=a[j++];
for(i=l;i<=r;++i)a[i]=tmp[i];
}
int main()
{
scan(n),scan(q);
for(int i=1,c,d;i<=n;++i)
{
scan(c),scan(d);
a[++cnt]={0,i,c,d,0,0};
}
for(int i=1,l,r,c,d;i<=q;++i)
{
scan(l),scan(r),scan(c),scan(d);
if(l>1)a[++cnt]={1,l-1,c,d,-1,i};
a[++cnt]={1,r,c,d,1,i};
}
sort(a+1,a+cnt+1,cmp);
cdq(1,cnt);
for(int i=1;i<=q;++i)putint(ans[i],'\n');flush();
return 0;
}