• 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]);
    }
    
  • 相关阅读:
    关于SDK-manager中我们需要下载哪些?
    不只是撸代码搞鸡汤,也有故事!
    [Selenium]如何通过Selenium实现Ctrl+click,即按住Ctrl的同时进行单击操作
    【设计模式】单例模式
    【Java多线程】线程池学习
    【leetcode】147 Insertion Sort List
    【webssh】shellinabox搭建
    【SpringMVC】一次处理项目中文乱码的经历
    【Java多线程】JUC包下的工具类CountDownLatch、CyclicBarrier和Semaphore
    【leetcode】3 SUM
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14591813.html
Copyright © 2020-2023  润新知