2019.01.27【NOIP提高组】模拟 B 组 比赛总结

总结

今天的一切都挺正常的,只不过旁边的LSB的电脑坏了,于是腐王就离去了。
先看第一题。
怎么感觉这题有些难?好像我以前见过的一道神仙题今天你AK了吗,于是我头皮发麻,转到T2。
觉得这题像是图论耶,于是在纸上画出了矛盾关系,但似乎求不到答案啊!这题应该是数论DP,然后我就转到T3。
感觉T3是给每一个数划分“势力范围”,即记录下它为最大值的最大区间的 l 和 r ,它为最小值的最大区间的 l 和 r。于是我就认为T3最水,然后就死磕T3了……曾一度脑子“短路”认为自己想出了正解,然而最终仍然毫无头绪,只好回到T1。
这时,我才发现T1是如此的水,立刻想出了正解。
T2貌似是状压DP,我列出了状态转移方程,是 O ( k ⋅ 2 n ) O(k\cdot 2^n) O(k2n)
由于我看错题意,以为k最大是 n 2 n^2 n2的,所以我认为T2状压大概有60分,然后就开始敲T1和T2了。
最后1小时我在无助中挣扎:T2的状压DP怎么搞都不对啊!怎么样例一直输出6!!!
最后1分钟,我终于打算交T2代码了,结果OJ卡了整整1.5分钟!
在此,强烈谴责那些故意卡OJ的人!!!


题解

T1

前言1:这是我比赛时所用的方法。
前言2:这道题目求的应该是第k小,而不是第k大。
先把3的次方数写出来,得 1 , 3 , 9 , 27 , 81 , … 1,3,9,27,81,\ldots 1,3,9,27,81,,接着就可以发现一个显而易见的规律了——每一个数都大于前面所有数的和。
于是我们不妨从小到大列举出它们的排列:
3 0 3 1 3 1 + 3 0 3 2 3 2 + 3 0 3 2 + 3 1 3 2 + 3 1 + 3 0 3 3 3 3 + 3 0 3 3 + 3 1 3 3 + 3 1 + 3 0 3 3 + 3 2 3 3 + 3 2 + 3 0 3 3 + 3 2 + 3 1 3 3 + 3 2 + 3 1 + 3 0 3 4 ⋯ 3^0\\ 3^1\\ 3^1+3^0\\ 3^2\\ 3^2+3^0\\ 3^2+3^1\\ 3^2+3^1+3^0\\ 3^3\\ 3^3+3^0\\ 3^3+3^1\\ 3^3+3^1+3^0\\ 3^3+3^2\\ 3^3+3^2+3^0\\ 3^3+3^2+3^1\\ 3^3+3^2+3^1+3^0\\ 3^4\\ \cdots 303131+303232+3032+3132+31+303333+3033+3133+31+3033+3233+32+3033+32+3133+32+31+3034
然后就可以发现一个神奇的规律了:
3 x 3^x 3x为开头的排列的总数等于前面以 3 0 , 3 1 , 3 , … 3 x − 2 , 3 x − 1 3^0,3^1,3,\ldots 3^{x-2},3^{x-1} 30,31,3,3x2,3x1开头的所有排列的总数,为 2 x 2^x 2x,包括前面所有的即为 2 x + 1 − 1 2^{x+1}-1 2x+11。且去掉开头 3 x 3^x 3x后可以发现它们是完全相等的。
因此我们可以每次将k减去 2 i 2^i 2i,(其中 2 i ≤ k , 2 i + 1 > k 2^i\leq k,2^{i+1}>k 2ik,2i+1>k),再把答案加上 3 i − 1 3^{i-1} 3i1
下面讲一种DL们的神奇的优化:

把k转换成二进制,再把这一个01串当成3进制数转回十进制,接着输出。

T2

我想到用状压DP
f i , j f_{i,j} fi,j表示矛盾了 i 条关系,已选择的人状态表示为 j 的方案数。
我们就可以用 f i , j f_{i,j} fi,j更新 f i + ( j , t ) , j & 2 t − 1 f_{i+(j,t),j \And 2^{t-1}} fi+(j,t),j&2t1,其中 1 ≤ t ≤ n 1\leq t\leq n 1tn ( x , y ) (x,y) (x,y)表示应该排在第y个人后面的人有多少个在x中,也就是违背了y的多少条关系。
但是如果这样暴力做,时间复杂度是 O ( 2 n n 2 k ) O(2^nn^2k) O(2nn2k)的,你说这能不能过(〃‘▽’〃)
因此我们需要优化(废话)。不妨定义 b i t i bit_i biti表示 i 的二进制中有几个1,这个显然可以预处理出来。个人感觉最简单的方法就是:

for(i=1;i<1<<n;i++) bit[i]=bit[i^i&-i]+1;

接着再定义 b i b_i bi表示第 i 个人的矛盾关系压缩后的状态。比如有矛盾关系1 2和1 5,那么 b 1 = ( 10010 ) 2 b_1=(10010)_2 b1=(10010)2。用这两个数组优化DP就可以把时间复杂度优化到 O ( 2 n n k ) O(2^nnk) O(2nnk)了。
这题相当难调,我搞了半天才发现是细节错误:循环本是 1 → 2 n − 1 1\to2^n-1 12n1的,我用一个变量存 2 n − 1 2^n-1 2n1,循环时又把这个变量减一!就这个东西,把我调得死去活来。

T3

比赛时只想出了30分的做法。感觉要用线段树等数据结构维护,但是想不出正解。
可以用分治来做。定义solve(l,r)表示区间[l…r]的所有最大值*最小值的和。
由于是分治,我们要求出一个 m i d = ⌊ l + r 2 ⌋ mid=\left\lfloor\cfrac{l+r}{2}\right\rfloor mid=2l+r。考虑子区间[a…b]:

  1. b ≤ m i d b\leq mid bmid时,直接调用solve(a,b)就可以了;
  2. a &gt; m i d a&gt;mid a>mid时,和上面一样,也是直接调用solve(a,b)
  3. a ≤ m i d ⋀ b &gt; m i d a\leq mid\bigwedge b&gt;mid amidb>mid时,子区间[a…b]是跨越mid的,这种情况无法分治,需要直接处理。

我们不妨从 m i d → l mid\to l midl方向取一点 a a a,表示区间的左端点。在往左扫的过程中,记录区间[mid…a]中的最大值 m a x l maxl maxl和最小值 m i n l minl minl
另外在区间(mid…r]中寻找两个点:

  • 第一个小于 m a x l maxl maxl的位置,记为u;
  • 第一个大于 m i n l minl minl的位置,记为v。

这里只讨论 u &lt; v u&lt;v u<v的情况, u &gt; v u&gt;v u>v的情况同理(以下情况中,a是固定的子区间左端点):

  • 对于右端点b在区间(mid…u)中的情况,它的最大值是 m a x l maxl maxl,最小值是 m i n l minl minl,所以对答案的贡献就是 ( u − m i d ) × m a x l × m i n l (u-mid)\times maxl\times minl (umid)×maxl×minl
  • 对于右端点b在区间[u…v)中的情况,它的最大值还是 m a x l maxl maxl,但是最小值是不确定的。可以预处理数组 p n = min ⁡ m i d &lt; i ≤ n a i p_n=\min_{mid&lt;i\leq n}a_i pn=minmid<inai,对答案的贡献就为区间[u…v]的最小值之和乘上 m a x l maxl maxl。最小值之和可以用前缀和预处理出来。
  • 对于右端点b在区间[v…r]中的情况,最大值最小值都不确定,就类似地定义数组 q n = max ⁡ m i d &lt; i ≤ n a i q_n=\max_{mid&lt;i\leq n}a_i qn=maxmid<inai,同时维护前缀和记录区间内 p i × q i p_i\times q_i pi×qi的和。

这样子就可以 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)过这道题目了。


CODE

这次的题目细节比较多,可以对一下标程。

T1

#include<cstdio>
using namespace std;
long long three[40];
int main()
{
    
    
	long long n,k,i,j,ans;
	scanf("%lld",&n);
	for(i=1,j=0;j<=35;i*=3,j++) three[j]=i;
	while(n--)
	{
    
    
		scanf("%lld",&k),ans=0;
		for(i=1ll<<35,j=35;i>0;i>>=1,j--)
			if(k>=i)
				k-=i,ans+=three[j];
		printf("%lld\n",ans);
	}
	return 0;
}

T2

#include<cstdio>
using namespace std;
#define mod 1000000007
#define ll long long
#define M 2097152
#define N 21
int i,j,k,temp,max,n,m,q,bit[M],b[N],two[N];
ll f[N][M],ans;
int main()
{
    
    
	scanf("%d%d%d",&n,&m,&q);
	max=(1<<n)-1;
	for(i=1;i<=m;i++) scanf("%d%d",&j,&k),b[j-1]+=1<<k-1;
	for(i=1;i<=max;i++) bit[i]=bit[i^i&-i]+1;
	f[0][0]=1;
	for(i=0;i<=q;i++)
		for(j=0;j<=max;j++)
			if(f[i][j])
				for(k=0;k<n;k++)
					if(~j&1<<k)
					{
    
    
						temp=i+bit[j&b[k]];
						if(temp<=q)
							f[temp][j|1<<k]+=f[i][j];
					}
	for(i=ans=0;i<=q;i++) ans=(ans+f[i][max])%mod;
	printf("%lld\n",ans);
	return 0;
}

T3

#include<cstdio>
using namespace std;
#define mod 1000000007
#define ll long long
#define N 500005
ll min[N],max[N],p[N],q[N],tot[N],val[N],ans;
inline ll mymin(ll x,ll y){
    
    return x<y?x:y;}
inline ll mymax(ll x,ll y){
    
    return x>y?x:y;}
void solve(int l,int r)
{
    
    
	if(l==r)
	{
    
    
		ans=(ans+val[l]*val[l])%mod;
		return;
	}
	int mid=l+r>>1,a,u=mid,v=mid;
	ll minl=mod,maxl=0;
	min[u]=mod,max[u]=tot[u]=p[u]=q[u]=0;
	for(a=mid+1;a<=r;a++)
	{
    
    
		min[a]=mymin(min[a-1],val[a]);
		max[a]=mymax(max[a-1],val[a]);
		p[a]=(p[a-1]+min[a])%mod;
		q[a]=(q[a-1]+max[a])%mod;
		tot[a]=(tot[a-1]+min[a]*max[a])%mod;
	}
	for(a=mid;a>=l;a--)
	{
    
    
		minl=mymin(minl,val[a]);
		maxl=mymax(maxl,val[a]);
		while(min[u+1]>=minl&&u<r) u++;
		while(max[v+1]<=maxl&&v<r) v++;
		if(u<v)
		{
    
    
			ans=(ans+(u-mid)*maxl%mod*minl)%mod;
			ans=(ans+(p[v]-p[u]+mod)*maxl)%mod;
			ans=(ans+tot[r]-tot[v]+mod)%mod;
		}
		else
		{
    
    
			ans=(ans+(v-mid)*maxl%mod*minl)%mod;
			ans=(ans+(q[u]-q[v]+mod)*minl)%mod;
			ans=(ans+tot[r]-tot[u]+mod)%mod;
		}
	}
	solve(l,mid);
	solve(mid+1,r);
}
int main()
{
    
    
	int n,i,j,k;
	scanf("%d",&n);
	for(i=1;i<=n;i++) scanf("%d",&val[i]);
	solve(1,n);
	printf("%lld\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/huangzihaoal/article/details/86670710