NOI Online题解

愤怒的小N

题目描述

点此看题

解法

首先可以发现奖励关就是二进制 (1) 个数为奇数的数。

先讲一下 (60) 分的做法,因为并不是人人一来就能拿满分,但这是正解的一个引子。

看到这个限制就想到了用数位 (dp) 去做,我们从小数位往大数位考虑,那么我们尝试维护 (x^t) 的和,每当遇到一个 1 之后就统计答案,不难发现可以固定大数位一定,这一位设置为 0,然后小数位随便选,就可以统计出所有的数。

(dp[t][0/1]) 表示考虑了后 (n-i) 个小数位(我把这一维滚动掉了),(1) 的个数是偶数(/)奇数的 (x^t) 的和。因为这个状态的含义是乱填,转移就考虑这一位是填 (0) 还是 (1),原理就用二项式展开(其中 (pre) 表示之前的数):

[(2^{n-i}+pre)^t=sum_{l=0}^t pre^lcdot 2^{(n-i)l}cdot{tchoose l} ]

那么我们就根据这个来转移:

  • 这一位填 (1)(dp[t][0]leftarrow sum_{l=0}^t 2^{(n-i)l}cdot dp'[t-l][1]cdot{tchoose l})(dp[t][1]leftarrow sum_{l=0}^t2^{(n-i)l}cdot dp'[t-l][0]cdot{tchoose l})

  • 这一位填 (0)(dp[t][0]leftarrow dp'[t][0])(dp[t][1]leftarrow dp'[t][1])

初始令 (dp[0][0]=1),算答案的时候也用二项式定理把前后合并起来即可,时间复杂度 (O(k^2log_2n))(60) 分到手。

然鹅我在考试的时候写错了一个小地方,调了很久,在我输出第二组样例的中间变量是发现了下面的神奇东西。

为了版面我就不贴过来了,点此观察数据(附暴力代码)

发现每迭代一次就会多一个 (dp[t][0])(dp[t][1]) 相等,迭代 (k) 次之后都满足 (dp[t][0]=dp[t][1]),我还以为是我暴力 (dp) 打错了,但是手玩了一下发现没问题,年轻的我不知道这就是正解的引子。


现在给出本题至关重要的结论,当迭代次数((dp) 了多少次)大于 (k) 时,都有 (dp[t][0]=dp[t][1]),知道了这个结论可以归纳证明它:

当迭代次数为 (1) 时有 (dp[0][0]=dp[0][1]=1),当迭代次数为 (x) 的时候 (dp'[x-1][0]+dp'[x-1][1]) 会贡献到 (dp[x-1][0],dp[x-1][1]) 那里去,那么对于 (x-1) 这个次数的贡献是相等的,而比 (x-1) 更小的项由于数值相等的贡献是一样的,所以贡献相等,那么就多了 (x-1) 这一项的相等。

总结一句:秘密就藏在转移式里面。

那么也就是说迭代次数大于 (k) 之后答案就和 (1) 出现次数是奇是偶没关系了,直接算所有数除以 (2) 就得到奇数的答案了,那么可以改变一下算答案的方式,我们先统计出全部的数量:

[frac{1}{2}sum_{i=0}^{n-1}f(i)=frac{1}{2}sum_{t=0}^{k-1}a_tsum_{i=0}^{n-1}i^t ]

(sum_{i=0}^{n-1}i^t) 这个东西可以套路地算出来,就让 ( t yyb) 教你算吧:点此学,这部分时间复杂度 (O(log_2 n+k^2))

然后还要用迭代次数小于等于 (k) 的答案去修正他,设 (ji) 表示 (1) 出现次数为奇数的答案,(ou) 表示 (1) 出现次数为偶数的答案,所以最终的答案就是:

[frac{1}{2}(sum_{i=0}^{n-1}+ji-ou) ]

然后 (ji,ou) 这两个东西可以用上面讲的暴力的做法 (O(k^3)) 算出来,总时间复杂度 (O(log_2 n+k^3))

积木小赛

题目描述

点此看题

解法

唯一的教训是 ( t map) 常数太大了,改成排序去重就过了。

时间复杂度 (O(n^2log n)),考试的时候应该多关注一下常数问题吧。

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 3005;
#define ull unsigned long long
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,ans,dp[M][26];char s[M],t[M];
ull hs[M],pw[M],a[M*M];
ull get(int l,int r)
{
	return hs[r]-hs[l-1]*pw[r-l+1];
}
int main()
{
	n=read();
	scanf("%s",s+1);scanf("%s",t+1);
	//搞一点预处理
	pw[0]=1;
	for(int i=1;i<=n;i++)
	{
		pw[i]=pw[i-1]*391;
		hs[i]=hs[i-1]*391+t[i];
	}
	for(int j=0;j<26;j++) dp[n+1][j]=n+1;
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<26;j++)
			dp[i][j]=dp[i+1][j];
		dp[i][s[i]-'a']=i;
	}
	//枚举t的所有子串
	for(int l=1;l<=n;l++)
	{
		int p=1;
		for(int r=l;r<=n;r++)
		{
			//匹配不动了
			if(dp[p][t[r]-'a']==n+1) break;
			p=dp[p][t[r]-'a']+1;
			a[++ans]=get(l,r);
		}
	}
	sort(a+1,a+1+ans);
	ans=unique(a+1,a+1+ans)-a-1;
	printf("%d
",ans);
}

岛屿探险

题目描述

点此看题

解法

一开始写了一个树套树,虽然时间复杂度 (O(nlog^2n)) 但还是直接挂掉了,这东西又耗空间常数又大。

我们来分析一下要求的柿子:(a_ioplus c_jleq min(b_i,d_j)),看到 (min) 按照套路把它拆开。

如果要把他拆开就会产生一个关于 (b_i)(d_j) 的偏序关系,解决偏序关系可以用 ( t cdq) 分治,这个算法的套路是先想到分治再去想怎么套,所以我们把询问和修改塞到一起拿去 ( t cdq),外层按 (b_i,d_j) 排序即可。

对于 (b_ileq d_j) 的情况,问题转化成了 (a_ioplus c_jleq b_i),这就是左半边的修改对右半边询问的贡献,异或问题可以考虑 ( t tire) 来解决,那么我们考虑一颗关于 (c_j) 的树,用 (a_i,b_i) 再上面跑的时候直接打永久化标记就行了,统计的时候就统计链上被打上的标记之和。

对于 (b_i>d_j) 的情况,问题转化成了 (a_ioplus c_j>d_j),这就是右半边的修改对左半边询问的贡献,考虑一颗关于 (a_i) 的树,建出来之后把 (c_j,d_j)( t tire) 树上面跑就行了。

然后左半边和右半边都按位置排序即可,询问拆成 ([1,l),[1,r]) 这两个会方便一些。还有一个细节是 (b_i=d_j) 的情况不用关心,归类到哪一边算都可以,直接排序就行。时间复杂度 (O(nlog^2n))

再写树套树我吃屎

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 300005;
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,q,cnt,rt,ans[M],ls[50*M],rs[50*M],s[50*M];
struct node
{
	int x,c,d,f;
}a[M];
bool cmp1(node x,node y)
{
	return x.d<y.d;
}
bool cmp2(node x,node y)
{
	return x.x<y.x;
}
void init()//初始化 
{
	for(int i=1;i<=cnt;i++)
		ls[i]=rs[i]=s[i]=0;
	rt=cnt=0;
}
//subtask1
void ins1(int &x,int y,int p)
{
	if(!x) x=++cnt;
	s[x]++;
	if(p==-1) return ;
	if(y&(1<<p)) ins1(rs[x],y,p-1);
	else ins1(ls[x],y,p-1);
}
int ask1(int x,int c,int d,int p)
{
	if(!x || p==-1) return s[x];
	if(d&(1<<p))
	{
		if(c&(1<<p)) return ask1(ls[x],c,d,p-1)+s[rs[x]];
		return ask1(rs[x],c,d,p-1)+s[ls[x]];
	}
	if(c&(1<<p)) return ask1(rs[x],c,d,p-1);
	return ask1(ls[x],c,d,p-1);
}
//subtask2
void tag(int &x)
{
	if(!x) x=++cnt;
	s[x]++;
}
void ins2(int &x,int c,int d,int p)
{
	if(!x) x=++cnt;
	if(p==-1) {tag(x);return ;}
	if(d&(1<<p))
	{
		if(c&(1<<p)) {tag(rs[x]);ins2(ls[x],c,d,p-1);}
		else {tag(ls[x]);ins2(rs[x],c,d,p-1);}
		return ;
	}
	if(c&(1<<p)) ins2(rs[x],c,d,p-1);
	else ins2(ls[x],c,d,p-1);
}
int ask2(int x,int c,int p)
{
	int t=s[x];
	if(!x || p==-1) return t;
	if(c&(1<<p)) return t+ask2(rs[x],c,p-1);
	return t+ask2(ls[x],c,p-1);
}
void cdq(int l,int r)
{
	if(l==r) return ;
	int mid=(l+r)>>1;
	cdq(l,mid);cdq(mid+1,r);
	sort(a+l,a+mid+1,cmp2);
	sort(a+mid+1,a+r+1,cmp2);
	//subtask1:右边建tire左边查
	init();
	for(int i=l,j=mid+1;i<=mid;i++)
		if(a[i].f!=0)//是询问 
		{
			while(j<=r && a[j].x<=a[i].x)
			{
				if(a[j].f==0) ins1(rt,a[j].c,23);
				j++;
			}
			if(a[i].f>0) ans[a[i].f]+=ask1(rt,a[i].c,a[i].d,23);
			else ans[-a[i].f]-=ask1(rt,a[i].c,a[i].d,23); 
		}
	//subtask2:左边打标记右边查
	init();
	for(int i=mid+1,j=l;i<=r;i++)
		if(a[i].f!=0)
		{
			while(j<=mid && a[j].x<=a[i].x)
			{
				if(a[j].f==0) ins2(rt,a[j].c,a[j].d,23);
				j++;
			}
			if(a[i].f>0) ans[a[i].f]+=ask2(rt,a[i].c,23);
			else ans[-a[i].f]-=ask2(rt,a[i].c,23);
		}
}
int main()
{
	n=read();q=read();
	for(int i=1;i<=n;i++)
	{
		int c=read(),d=read();
		a[++m]=node{i,c,d,0};
	}
	for(int i=1;i<=q;i++)
	{
		int l=read(),r=read(),c=read(),d=read();
		a[++m]=node{r,c,d,i};
		a[++m]=node{l-1,c,d,-i};
	}
	sort(a+1,a+1+m,cmp1);
	cdq(1,m);
	for(int i=1;i<=q;i++)
		printf("%d
",ans[i]);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14591813.html