zoukankan      html  css  js  c++  java
  • 并不对劲的复健训练-CF1329B&C:Heap Sequences

    CF1329B Dreamoon Likes Sequences

    题目描述

    一个数列是符合条件的,当且仅当该数列非空且单增且前缀异或和单增。
    给定(d,m)(d,mleq 10^9)),问所有每个数都是不超过(d)的正整数的数列中,符合条件的数列个数模(m)是多少。

    题解

    假设某个合法数列长度为(n),数列为(a_1,a_2,...,a_n),前缀和为(b_1,b_2,...,b_n)
    要想使({b_n})单增,就得有(forall i,b_i<b_{i+1})
    (b_i<b_{i+1})有两种情况:1.(b_{i+1})的最高的二进制位比(b_i)高;2.二者最高的二进制位相同,且二者都减去最高位后(b_{i+1})更大。
    考虑第2种情况,发现如果二者最高二进制位相同,由(a_{i+1}=b_ispace xorspace b_{i+1})可得(a_{i+1})的最高位比(b_i)低,而(a_1,...,a_i)中至少有一个的最高位和(b_i)相同,这与({a_n})单增的条件不符。
    所以不会出现第二种情况。也就是说,({b_n})中每个数的最高二进制位都比前一个数高。
    可以得出({a_n})中每个数的最高二进制位都比前一个数高。
    (1,2,...,d)中的数按最高二进制位分类。每一类中可以随便选一个或者不选。

    代码

    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<ctime>
    #include<iomanip>
    #include<iostream>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #define LL long long
    #define rep(i,x,y) for(int i=(x);i<=(y);++i)
    #define dwn(i,x,y) for(int i=(x);i>=(y);--i)
    #define view(u,k) for(int k=fir[u];~k;k=nxt[k])
    using namespace std;
    int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)&&ch!='-')ch=getchar();
    	if(ch=='-')f=-1,ch=getchar();
    	while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    	return x*f;
    }
    void write(int x)
    {
    	char ch[20];int f=0;
    	if(!x){putchar('0'),putchar('
    ');return;}
    	if(x<0)putchar('-'),x=-x;
    	while(x)ch[++f]=x%10+'0',x/=10;
    	while(f)putchar(ch[f--]);
    	putchar('
    ');
    }
    int t,d,m,ans;
    int mo(int x){if(x>=m)return x-m;if(x<0)return x+m;return x;}
    int main()
    {
    	t=read();
    	while(t--)
    	{
    		d=read(),m=read();ans=1;
    		LL now=1,nxt=1;
    		while(1)
    		{
    			if(nxt>=d){ans=(LL)ans*(d-now+2)%m;break;}
    			ans=(LL)ans*(nxt-now+2)%m,now=nxt+1;nxt=(now<<1)-1;
    		}
    		ans=mo(ans-1);
    		write(ans);
    	}
    	return (~(0-0)+1);
    }
    

    CF1329C Drazil Likes Heap

    题目描述

    有一个高度为(h)的有点权的满二叉树,满足大根堆的性质。
    删掉一个不是叶子的点,这个点的权值较大的儿子会移动到这个点的位置,然后在这个儿子原来的位置重复该过程。
    要删掉一些点,使它变成高度为(g)的满二叉树,且剩下的点的点权和尽可能小。问最小点权和、删掉哪些点。
    所有点的点权不同,(gleq hleq 20)

    题解

    首先发现剩下的部分的点权与删点顺序无关,因为每个点的点权在删完后都会变成原子树的最大值。
    考虑一种贪心的构造方法:从下往上构造,让每个点的新点权是原子树中 大于该点左右儿子的新点权 的 尽可能小的点权。
    该点选尽可能小的点权,不会使该点祖先的下界更大。
    可以在每个点处维护按大小排好序的子树点权数组,一个点的数组可以由左右儿子的数组用类似归并排序的方法合并。时间复杂度(Theta(2^h imes h))
    如果用每到一个点都遍历一遍这个点的所有儿子来找它的点权的话,时间复杂度也是(Theta(2^h imes h))的,但递归常数略大。

    这题还有完全不同的极其直观的另一种贪心做法:删当前根处的值,直到离根最近的叶子到根的距离是(g),然后递归处理左右子树。
    因为最大值都在堆顶,而且当一个点和它所有祖先都不能被删时,它的左右子树之间不会影响。时间复杂度也是(Theta(2^h imes h))

    代码

    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<ctime>
    #include<iomanip>
    #include<iostream>
    #include<map>
    #include<queue>
    #include<stack>
    #include<vector>
    #define LL long long
    #define rep(i,x,y) for(int i=(x);i<=(y);++i)
    #define dwn(i,x,y) for(int i=(x);i>=(y);--i)
    #define view(u,k) for(int k=fir[u];~k;k=nxt[k])
    using namespace std;
    #define maxn ((1<<20)+7)
    #define pii pair<int,int>
    #define fi first
    #define se second
    #define mp make_pair
    #define ls (u<<1)
    #define rs (u<<1|1)
    int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)&&ch!='-')ch=getchar();
    	if(ch=='-')f=-1,ch=getchar();
    	while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    	return x*f;
    }
    void write(LL x)
    {
    	char ch[20];int f=0;
    	if(!x){putchar('0'),putchar('
    ');return;}
    	if(x<0)putchar('-'),x=-x;
    	while(x)ch[++f]=x%10+'0',x/=10;
    	while(f)putchar(ch[f--]);
    	putchar('
    ');
    }
    int t,n,h,g,a[maxn],yes[maxn],len,dfn[maxn],siz[maxn],val[maxn];
    LL ans;
    pii ar[maxn],tmp[maxn];
    void merge(int u)
    {
    	int nl=dfn[ls],nr=dfn[rs],c=0;
    	rep(i,1,siz[u])
    	{
    		int va=(nl>=dfn[ls]+siz[ls]?1e9:ar[nl].fi),
    		vb=(nr>=dfn[rs]+siz[rs]?1e9:ar[nr].fi),
    		vc=c?1e9:a[u];
    		if(va<min(vb,vc)){tmp[i].fi=va,tmp[i].se=ar[nl].se,nl++;}
    		else if(vb<min(va,vc)){tmp[i].fi=vb,tmp[i].se=ar[nr].se,nr++;}
    		else tmp[i].fi=a[u],tmp[i].se=u,c++;
    	}
    	rep(i,1,siz[u])ar[dfn[u]+i-1]=tmp[i];
    }
    int getv(int u,int li)
    {
    	int L=dfn[u],R=dfn[u]+siz[u]-1;pii res;res.fi=1e9;
    	while(L<=R)
    	{
    		int mid=(L+R>>1);
    		if(ar[mid].fi>li)
    		{
    			if(res.fi>ar[mid].fi)res=ar[mid];
    			R=mid-1;	
    		}
    		else L=mid+1;
    	}
    	yes[res.se]=1;
    	return res.fi;
    }
    void work(int u,int d)
    {
    	if(d==g)
    	{
    		sort(ar+dfn[u],ar+dfn[u]+siz[u]);
    		val[u]=ar[dfn[u]].fi,yes[ar[dfn[u]].se]=1,ans+=val[u];
    		return;
    	}
    	work(ls,d+1),work(rs,d+1),merge(u);
    	val[u]=getv(u,max(val[ls],val[rs])),ans+=val[u];
    	return;
    }
    void build(int u,int d)
    {
    	dfn[u]=++len,ar[len].fi=a[u],ar[len].se=u,siz[u]=1;
    	if(d==h){return;}
    	build(ls,d+1),build(rs,d+1),siz[u]+=siz[ls]+siz[rs];
    	return;
    }
    int main()
    {
    	t=read();
    	while(t--)
    	{
    		h=read(),g=read(),n=(1<<h)-1;ans=len=0;
    		rep(i,1,n)a[i]=read(),yes[i]=0;
    		build(1,1);
    		work(1,1);
    		write(ans);
    		dwn(i,n,1)if(!yes[i])printf("%d ",i);
    		puts("");
    	}
    	return (~(0-0)+1);
    }
    

    一些感想

    打穿越火线不要开黑:跟穿越火线强的人组队无法提高水平,跟不那么强的人组队无法提高分数。
    但是跟英语水平强的人开黑既能提高水平(指睡着前有更多时间想题而不是翻译)又能提高分数(指有更多时间想题就更容易上分)。:)

  • 相关阅读:
    MySql cmd下的学习笔记 —— 引擎和事务(engine,transaction)
    MySql cmd下的学习笔记 —— 有关视图的操作(algorithm)
    MySql cmd下的学习笔记 —— 有关视图的操作(建立表)
    MySql cmd下的学习笔记 —— 有关常用函数的介绍(数学函数,聚合函数等等)
    MySql cmd下的学习笔记 —— 有关多表查询的操作(多表查询练习题及union操作)
    MySql 在cmd下的学习笔记 —— 有关多表查询的操作(内连接,外连接,交叉连接)
    MySql cmd下的学习笔记 —— 有关子查询的操作(where型,from型,exists型子查询)
    MySql cmd下的学习笔记 —— 有关select的操作(order by,limit)
    剑指Offer--第21题 调整数组顺序使奇数位于偶数前面;
    剑指Offer--和为s的连续正数序列
  • 原文地址:https://www.cnblogs.com/xzyf/p/12664515.html
Copyright © 2011-2022 走看看