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)\) 符合条件的数有

\[1,2,4,7,8,11,13,14 \]

容易发现恰好有\(8\) 个数(\(16\) 的一半)

根据题目所求,稍微往高次推推?

\[1+2+4+7+8+11+13+14=\frac 12\sum_{i=0}^{15}i\\ 1^2+2^2+4^2+7^2+8^2+11^2+13^2+14^2=\frac 12\sum_{i=0}^{15}i^2\\ 1^3+2^3+4^3+7^3+8^3+11^3+13^3+14^3=\frac 12\sum_{i=0}^{15}i^3\\ \]

妙哉!然而到了\(4\) 次方似乎就不对劲了。。。

没关系。注意到\(16=2^4\) ,也就是说严格低于\(2\) 的幂次的次方应该都有这个结论。用数学语言表达下

\[Let\ g_{b,c}=\sum_{i=0}^{2^b-1}(-1)^{pop(i)}i^c\\ So\ when\ b>c,g_{b,c}=0 \]

考虑证明这个结论。首先要有一个递推式,不难发现:

\[g_{b,c}=\sum_{i=0}^{2^{b-1}-1}(-1)^{pop(i)}i^c-\sum_{i=0}^{2^{b-1}-1}(-1)^{pop(i)}(i+2^{b-1})^c\\ =g_{b-1,c}-\sum_{i=0}^{2^{b-1}-1}(-1)^{pop(i)}\sum_{c'=0}^c\binom c{c'}i^{c'}(2^{b-1})^{c-c'}\\ =g_{b-1,c}-\sum_{c'=0}^c\binom c{c'}(2^{b-1})^{c-c'}g_{b-1,c'} \]

采用数学归纳法证明,归纳基础\(b=0\) 时结论显然成立,假设\(b<h\) 时结论均成立,下面对\(b=h\) 进行归纳:

\[For\ c<b-1,g_{b,c}=g_{b-1,c}-\sum_{c'=0}^c\binom c{c'}(2^{b-1})^{c-c'}g_{b-1,c'}=0-0=0\\ For\ c=b-1,g_{b,c}=g_{b-1,b-1}-\sum_{c'=0}^{b-1}\binom c{c'}(2^{b-1})^{c-c'}g_{b-1,c'}=g_{b-1,b-1}-g_{b-1,b-1}=0 \]

证毕。现在我们已经得到了一个很好的性质,让我们从头开始推,看能不能用上。

\[ans=\frac 12\sum_{i=0}^{n-1}(1-(-1)^{pop(i)})f(i)\\ =\frac 12\sum_{i=0}^{n-1}f(i)-\frac 12\sum_{i=0}^{n-1}(-1)^{pop(i)}f(i) \]

考虑减号前的式子,将\(f(i)\) 展开之后交换和式就是一个自然数幂和,这个东西怎么做都行,这里不再赘述。

主要考虑后面的式子。我们想要让它尽量往\(g_{b,c}\) 上靠,但\(n\) 不一定是\(2\) 的正整数次幂。于是我们可以考虑\(b_0<b_1<\cdots<b_{t-1}\) 满足

\[n=\sum_{i=0}^{t-1}2^{b_i} \]

然后类似数位\(dp\) 考虑第一位小于\(n\) 的位置,那么

\[=\sum_{i=0}^{k-1}a_i\sum_{j=0}^{n-1}(-1)^{pop(j)}j^i\\ =\sum_{i=0}^{k-1}a_i\sum_{c=0}^{t-1}(-1)^{t-1-c}\sum_{x=0}^{2^c-1}(-1)^{pop(x)}(x+\sum_{j=c+1}^{t-1}2^{b_j})^i\\ Let\ sum_c=\sum_{j=c}^{t-1}2^{b_j},So\\ =\sum_{i=0}^{k-1}a_i\sum_{c=0}^{t-1}(-1)^{t-1-c}\sum_{x=0}^{2^{b_c}-1}(-1)^{pop(x)}(x+sum_{c+1})^i\\ =\sum_{i=0}^{k-1}a_i\sum_{c=0}^{t-1}(-1)^{t-1-c}\sum_{x=0}^{2^{b_c}-1}(-1)^{pop(x)}\sum_{j=0}^i\binom ijx^j(sum_{c+1})^{i-j}\\ =\sum_{i=0}^{k-1}a_i\sum_{c=0}^{t-1}(-1)^{t-1-c}\sum_{j=0}^i\binom ij(sum_{c+1})^{i-j}\sum_{x=0}^{2^{b_c}-1}(-1)^{pop(x)}x^j\\ =\sum_{i=0}^{k-1}a_i\sum_{c=0}^{t-1}(-1)^{t-1-c}\sum_{j=0}^i\binom ij(sum_{c+1})^{i-j}g_{b_c,j}\\ \]

然后就可做了。根据性质,只有\(\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;
}
posted @ 2021-03-29 11:19  BILL666  阅读(282)  评论(0编辑  收藏  举报