【模拟赛】2019.9.23—2019.9.24

背景:

模拟赛定下来了,,周一周二周五,周三讨论,这晚自习占的,一看课表发现周三周四是语文和英语晚自习,这还是不停课吗???白天的课,能()上的就上(主要上理科和数学,,语文英语基本没上过,,,结果就被KK怼回去上文化课了,结果发现好多不好补回来,但是没有大片的空白,就是那些老师拓展的东西没有学到,有点亏了,还有作业,基本没写,就是偶尔自己闲了就写写题当做休息,,这样怎么可能提高文化课,,,)这文化课啊,还是要好好学的,毕竟竞赛不是上学的唯一途径,但,高考是。

不瞎扯了来好好看题,,,

题目:


Day 1 题目:

T1: 硬币求和(scoins)

简化题目:

∑ i = 1 n 1 2 i \sum_{i=1}^{n} \frac{1}{2\sqrt{i}} i=1n2i 1。(答案误差不超过 ± 1 \pm1 ±1

(就是这么直接,,)

题解:

(这个题,总用时15分钟,然后就A掉了,,,神奇吧,,前10分钟在看题,结果发现,并没有什么有用的文字描述,都是些水话,一点都么没有用,看到样例后面的样例解释我才差不多能看懂,,然后就试了试大样例,可以确定是个结论题,然后就直接随便写了个结论,结果就A了,,,又一次的小凯的疑惑啊,神奇!)

还是要好好看正解:
∑ i = 1 n 1 2 i = 1 2 ∗ ( 1 i ) \sum_{i=1}^{n} \frac{1}{2\sqrt{i}}=\frac{1}{2}*(\frac{1}{\sqrt{i}}) i=1n2i 1=21(i 1)
要求出来这个答案,可以从 1 i \frac{1}{\sqrt{i}} i 1 这里下手。

从高中的放缩得到常见不等式:
2 ∗ ( n + 1 − n ) = 2 n + 1 + n < 1 n < 2 n + n − 1 = 2 ∗ ( n − n − 1 ) 2*(\sqrt{n+1}-\sqrt{n})=\frac{2}{\sqrt{n+1}+\sqrt{n}}<\frac{1}{\sqrt{n}}<\frac{2}{\sqrt{n}+\sqrt{n-1}}=2*(\sqrt{n}-\sqrt{n-1}) 2(n+1 n )=n+1 +n 2<n 1<n +n1 2=2(n n1 )
可以知道:
∑ i = 1 n 2 i + i + 1 < ∑ i = 1 n 1 i < ∑ i = n 1 2 i + i − 1 \sum^{n}_{i=1}\frac{2}{\sqrt{i}+\sqrt{i+1}}<\sum^{n}_{i=1}\frac{1}{\sqrt{i}}<\sum^{1}_{i=n}\frac{2}{\sqrt{i}+\sqrt{i-1}} i=1ni +i+1 2<i=1ni 1<i=n1i +i1 2
从左边的最小值推出:
∑ i = 1 n 2 i + 1 + i = 2 ∗ ∑ i = 1 n ( i + 1 − i ) = 2 ∗ ( n + 1 − 1 ) \sum_{i=1}^{n}\frac{2}{\sqrt{i+1}+\sqrt{i}}=2*\sum_{i=1}^{n}(\sqrt{i+1}-\sqrt{i})=2*(\sqrt{n+1}-1) i=1ni+1 +i 2=2i=1n(i+1 i )=2(n+1 1)
从左边的最大值推出:
∑ i = n 1 2 i − 1 + i = 2 ∗ ∑ i = n 1 ( i − i − 1 ) = 2 ∗ n \sum_{i=n}^{1}\frac{2}{\sqrt{i-1}+\sqrt{i}}=2*\sum_{i=n}^{1}(\sqrt{i}-\sqrt{i-1})=2*\sqrt{n} i=n1i1 +i 2=2i=n1(i i1 )=2n
再乘上刚开始的 1 2 \frac{1}{2} 21,范围就是:
( n + 1 − 1 , n ) (\sqrt{n+1}-1,\sqrt{n}) (n+1 1,n )
QED.

但是我考场上吧,直接写了个 n − 1 \sqrt{n}-1 n 1 ,然后就A掉了,这个其实是因为 n − 1 \sqrt{n}-1 n 1 虽然不在 ( n + 1 − 1 , n ) (\sqrt{n+1}-1,\sqrt{n}) (n+1 1,n )的范围内,但是可以卡在 ± 1 \pm1 ±1里面的,这个自行证明吧。(然后就用掉了我之前攒的所有RP,,)

```
#include<bits/stdc++.h>
#define int long long
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
signed main()
{
//	freopen("scoins.in","r",stdin);
//	freopen("scoins.out","w",stdout); 
	int n=read(), c=sqrt(n+1.9); 
	int ans=0; if(c*c>n+1) ans=1; 
	printf("%lld\n",c-ans-1);
    //自己的一行AC代码:(从此RP-ocean;)
    //int n=read();printf("%lld",sqrt(n)-1); 
	return 0;
}

T2:汉诺塔(hanoi)

简化题目:

n n n个盘子 m m m个柱子的汉诺塔问题。( n , m < = 1500 n,m<=1500 nm<=1500

题解:

(这个题,一上去我就知道这个爆搜我是绝对写不出来的,然后就只写了 m = 3 m=3 m=3的情况,并没有推出来最终的递推式,,,结果想着是能拿20分的,结果,,, 2 1500 2^{1500} 21500,,所以,,蒙了,,高精!!!,不是说好了不考高精的嘛!!!我就直接没有学~~555555555哭死在角落,,,)

正解:
f [ n ] [ m ] = min ⁡ 0 ≤ k < n { 2 f [ n ] [ m ] + f [ n − k ] [ m − 1 ] } f[n][m] = \min_{0 \le k \lt n} \lbrace 2f[n][m] + f[n-k][m-1] \rbrace f[n][m]=0k<nmin{2f[n][m]+f[nk][m1]}
这个是表示你在取 k k k个盘子的时候是会把它们放在 m m m个柱子上的,然后就在把剩下的盘子放在 m − 1 m-1 m1个柱子上,然后再把之前的盘子放到最后一个柱子上,由于是取 k k k个盘子,所以方案数最后要取 m i n min min

当你找到正确的递推式之后就会发现这仿佛是一个 O ( n 3 ) O(n^3) O(n3)的写法,所以呢,要优化啊!当你在取k个盘子的时候你会发现,如果n越大,k就会越大(这一点也可以打表看出来,打出来好像是个斜放的杨辉三角,是LDY大佬发现的,,,tql)然后 k k k就符合这个单调性所以就可以用单调指针进行优化。

代码:

(由于ex的高精,我就写了主要的那部分代码,,,)

#include <bits/stdc++.h>
using namespace std;
#define MAXN 1500
#define ll unsigned long long
#define INFLL 0x3f3f3f3f3f3f3f3fllu
#define rint register int
inline int rf(){int r;int s=0,c;for(;!isdigit(c=getchar());s=c);for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);return s^45?r:-r;}
int n, m, T; ll f[MAXN+5][MAXN+5]; 
vector<int> A[MAXN+5];
inline void Mul2(vector<int> &A)
{
	A.push_back(0); A[0]*=2; 
	for(int i=1;i<(int)A.size();i++) A[i]=(A[i]<<1)+A[i-1]/10,A[i-1]%=10; 
	if(!A.back()) A.pop_back();
}
int main()
{
//	freopen("hanoi.in","r",stdin); 
//	freopen("hanoi.out","w",stdout);
	n=MAXN; m=MAXN; A[1].assign(1,2); 
	for (int i=2; i<=n; ++i)
	{
		A[i]=A[i-1],--A[i-1][0]; 
		Mul2(A[i]);
	}
	--A[n][0];
	
	memset(f,0x3f,sizeof f); memset(f[1]+1,0,n<<3);
	f[2][1]=1;  ll s=2;
	for(rint i=1;i<=n;i++)
	{
		f[3][i]=min(f[3][i],s-1);
		s = min(s<<1,INFLL+1);
	}
    //我码的在这里!!(卑微的掺杂在众多高精处理之中,,,,)
	for(int i=4;i<=m;i++)
	{
		f[i][1]=1;
		for(int j=2;j<=n;j++)
		{
			int k=1;
			f[i][j]=2*f[i][k]+f[i-1][j-k];
			while (k+1<j&&2*f[i][k]+f[i-1][j-k]>2*f[i][k+1]+f[i-1][j-k-1]) k++;
            //这里用单调指针进行优化,如果k的答案>k+1的答案而且k和k+1存在就k++往下查。
		}
	}
	
	
	T=rf();
	while(T--)
	{
		n = rf(); m = rf(); 
		if(m==1){puts("0"); continue;} 
		else if(m==2){puts(n>1?"No Solution":"1"); continue;}
		else if(m==3)
		{	
			for(rint i=A[n].size()-1;~i;i--) putchar(A[n][i]+'0');
			putchar('\n'); 
			continue;
		} 
		else printf("%llu\n",f[m][n]);
	}	
	return 0;
}

T3:随机子树(tree)

简化题目:

求在 [ L , R ] [L,R] [L,R]区间的子树联通块的个数。

题解:

(这个题,我由于没好好看题,没注重子树这个东西,然后就直接找的每两个点的树上距离,然后就死也想不到怎么过样例中的 [ 3 , 5 ] [3,5] [3,5]然后就挂掉了,好像调了快两个小时,,这就告诉我,要好好看题啊~~555555哭死在角落)

正解:

(表示这个题我只会到 O ( n 3 ) O(n^3) O(n3)的做法,还有 O ( n 2 ) O(n^2) O(n2)的方程式优化部分,,就会这么多,后面要用到长链剖分+线段树+树形DP的大工程我就不参与了吧,,,这太难了,,等打完CSP再说,,)

由于求直径大于一个值不太好做,但是直径小于一个值还是比较好做的,先只考虑直径在 [ 1 , R ] [1,R] [1,R]区间的联通块个数。这就类似于直径的DP式了, f [ p ] [ d ] f[p][d] f[p][d]表示以点p为最浅的点,最深点距离点p的深度为d的联通块个数。进行树形DP,将一个新儿子v加入的时候就是:
f ′ [ p ] [ m a x ( d 1 , d 2 + 1 ) ] = ∑ d 1 + d 2 + 1 < = R f [ p ] [ d 1 ] × f [ v ] [ d 2 ] f'[p][max(d_1,d_2+1)]=\sum_{d_1+d_2+1<=R}f[p][d_1]\times f[v][d_2] f[p][max(d1,d2+1)]=d1+d2+1<=Rf[p][d1]×f[v][d2]
然后可以想办把max优化掉,就可以分类讨论,枚举 d 2 d_2 d2:
f ′ [ p ] [ d 2 + 1 ] + = ( ∑ d 1 + d 2 + 1 ≤ R , d 1 ≤ d 2 f [ p ] [ d 1 ] ) × f [ v ] [ d 2 ] f ′ [ p ] [ d 1 ] + = ∑ d 1 + d 2 + 1 ≤ R , d 1 > d 2 f [ p ] [ d 1 ] × f [ v ] [ d 2 ] f'[p][d_2+1] += \left( \sum_{d_1+d_2+1 \le R, d_1 \le d_2} f[p][d_1] \right) \times f[v][d_2] \\f'[p][d_1] += \sum_{d_1+d_2+1 \le R,d_1 \gt d_2} f[p][d_1] \times f[v][d_2] f[p][d2+1]+=d1+d2+1R,d1d2f[p][d1]×f[v][d2]f[p][d1]+=d1+d2+1R,d1>d2f[p][d1]×f[v][d2]
这样就可以用一个前缀和优化就好吧第一个式子搞掉了,但第二个,,,要用线段树,,菜鸡不想写了,,,,太菜了。。

(说明一下:由于本菜鸡太菜,不会自己写dp时及其解释,就直接搬题解了,但是在本博客中出现的菜鸡是读懂了的,剩下的就不会了。)

代码:

(由于不会写,所以就没打算订正代码,,,,(卑微))

Day1总结:

第一要说的就是看懂题,好好读懂题,你才会发现其实你之前的一大堆思想就是个假算法,,就是在瞎扯,,,还是要好好读题,多推几遍样例是不会浪费太多时间的,所以,还是那句话,读懂题你才知道自己是否在通往得分的路上,否则你一走偏,就从此和OI无缘了,,,还有就是,,要吐槽一下这个高精,,,真的,刚学OI的时候就学到了高精,但老师那个时候就说高精不考不用学了,知道就好了,,所以,我就很听话的过了一年,,然后到现在就一点也不会高精,也没有想到会用到高精,这是真的有意思,之前的所有模拟赛都没有过高精,我还特地去翻了前几年的真题结果也是没有高精啊!!!但是打完这场之后老师就又说可能考了,,,~~5555555555哭死o(╥﹏╥)o,,还是抽时间学一下吧。还有就是今天的 R P RP RP由于过度使用,估计近期的考试都死掉了吧,,,,(ㄒoㄒ)


Day2题目:

T1:括号序列(bracket)

简化题目:

给你一个括号串,求出合法的括号子串的个数。

题解:

(这个题啊,刚看懂题就大惊了,这不就是2017年noip的原题《时间复杂度》的极简版吗,而且是没有REE的情况,就是开个栈就好了,,,但是,,(ㄒoㄒ)一个莫名其妙的原因,直接挂掉了,不知道为什么,结果是少考虑了一种情况,然后就完了,,100分啊,,没有了,,这要是正式考试,我就只能死回去学文化课了,,,o(╥﹏╥)o)

正解:

神奇的是这个题,,,竟然有 O ( n n ) O(n\sqrt{n}) O(nn )的优化型爆搜, O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))的lower_bound的神奇做法,真的是强啊~~

这个题确实就是一个栈就跑过了,开个栈,如果是"("就进栈,不是就判断,如果这个 " ( ) " "()" "()"前面什么都没的话就加上 1 1 1(自身的贡献),不是的话就加上之前的贡献,再加上自身的贡献 1 1 1,对于括号只有两种可能,一种是嵌套的,一种就是就地并列的,所以可以对每个单括号写第二个结构体,每次可以记录一下嵌套的个数,这样的话方便统计答案。

代码:
#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int sea=1e6+7;
char ch[sea],s[sea];
int n,top=0,ans,b[sea];
struct hit{int x,sum;}a;
stack<hit>st;
int main()
{
//	freopen("bracket.in","r",stdin);
//	freopen("bracket.out","w",stdout);
	cin>>ch; n=strlen(ch); 
	over(i,1,n) s[i]=ch[i-1];  int num=ans=0;
	over(i,1,n)
	{
		if(s[i]=='(') a.x=1;else a.x=2;
		if(st.size())
		{
			if(st.top().x==1&&a.x==2)
			{
				st.pop();
				if(st.empty()) ans+=++num;//记录并列的 
				else ans+=++st.top().sum;//记录嵌套的 
			}
			else st.push(a);
		}
		else st.push(a);
	}
	printf("%d\n",ans);
	return 0;
}

T2:和数检测(check)

简化题目:

给定 T T T个询问, n n n个数,常数 d d d,求是否存在 a i + a j = m a_i+a_j=m ai+aj=m ( m < = 1 e 9 , n < = 1 e 6 ) (m<=1e9,n<=1e6) m<=1e9,n<=1e6

题解:

(这个题,上去一看, n 2 n^2 n2暴力跑啊,只有 20 20 20分,,那就开个桶跑,,只有 40 40 40,,,那就 m a p map map,只有 50 — 55 50—55 5055分,那就二分,,也是 50 50 50,那就,,就分段单调指针跑二分,也就 70 70 70分,,,然而这个题我跑的 m a p map map,就拿了 50 50 50,,,但是还有的PHarr大佬竟然用 p b d s pbds pbds直接就 S T L STL STL O ( n ) O(n) O(n)的,,,tql)

正解:

分块!!!

这题解也是真的妙,这是真的神奇,值域分块,我真的是,,,服了,,还是序列分块写多了,值域分块都忘的这么干净了,这个题暴力来说就是开个桶啊,但是对于 1 e 9 1e9 1e9进行分开真的是有意思,(这里说明一下,其实真实的m不是 1 e 9 1e9 1e9,在机房有位Tyouchie大佬写的树状数组,写的是 1 e 7 1e7 1e7的,证明了所有数据在 1 e 7 1e7 1e7之前就能找到答案,所以这个真实的数据并不是 1 e 9 1e9 1e9)。

这就来解释一下 s t d std std的标算,(由于 s o l u t i o n solution solution真的是很不能看懂,觉得出题人是用文言写的吧,所以就自己看代码理解了一下,由于自己的分块还是比较熟悉的就比较好看懂)

首先 M = s q r t ( m ) ≈ 40000 M=sqrt(m) \approx40000 M=sqrt(m)40000 这就是块长,然后这就是一个已经分过块的桶,这其实就是叫值域分块。

在每个块里面进行对这个数的存桶,然后怎么优化 1 e 9 1e9 1e9呢,就是把每个块进行取模这样的话,每个块就是 1 — 40000 1—40000 140000,然后对于每个数再进行取模等一下代码细节,具体看代码吧,应该很好理解。

代码:
#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int pool=1e6+7;
const int ocean=1e9;
const int sea=4e4;
vector<int>v[sea];
typedef vector<int>::iterator iter;
int T,n,m,flag,a[pool],block,f[sea*4];
int main()
{
//	freopen("check.in","r",stdin);
//	freopen("check.out","w",stdout);
	T=read();
	over(t,1,T)
	{
		n=read(); m=read();block=m/sea;
		over(i,0,block) v[i].clear(); int ans=0;
		over(i,1,n) 
		{
			int x=read(); int y=x%sea; x=x/sea;
			v[x].push_back(y);
		}
		over(i,0,block)
		{
            //先放进去a[i]
			int l=(m-(i+1)*sea+1)/sea,r=(m-i*sea)/sea,base=l*sea;
			over(j,l,r) for(iter it=v[j].begin();it!=v[j].end();it++) 
			f[*it+j*sea-base]=1;
			
            //对比
			for(iter it=v[i].begin();it!=v[i].end();it++) 
			if(f[m-*it-i*sea-base]) {ans=1;break;}
			
			//清空
            over(j,l,r) for(iter it=v[j].begin();it!=v[j].end();it++) 
			f[*it+j*sea-base]=0;
			if(ans) break; 
		}
		printf("%d\n",ans);
	}
	return 0;
}

T3:与(and)

简化题目:

给你n个数,求你有多少种方法可以将它分成两部分,使这两部分至少有一个数,并且两部分进行按位&操作后的结果是相同的。

题解:

(这个题我连指数级的暴力都打不出来,,,~~555555,还是之后又找到的大佬学了指数级的爆搜,由于指数级的爆搜不会,就直接想的正解,但是,正解又没有完好的思路体系,所以就直接放弃了,,,)

这个题Lcentury以及他的同学,用 D P DP DP成功 A A A掉,,Lec是写了个 80 行 + 80行+ 80+ D P DP DP,然而他同学就更厉害了,,直接用 S T L STL STL写的 D P DP DP,,就50行+,,, D P DP DP,,而我。。。还在想 D P DP DP是怎么 A A A掉的。。

正解:

容斥+并查集!!!

这个题啊,题解是真的妙!!! ( a g a i n ) (again) again

首先这个题由于要求方案数,仔细看一下题,如果正这去做的话,就是一个DP的东西,去找到相同的,这样子不会太好做,所以就用到了容斥,有所有的减去不相同的,由于你是要求有集合的一个和,也可以理解成并集,但是你可以求出每个集合,用容斥来做,这点可以参考一下下面的解释,这样的话答案就可以表示为:
A n s = ∑ d = 0 131071 ( − 1 ) d × f ( d ) Ans=\sum_{d=0}^{131071}(-1)^d\times f(d) Ans=d=0131071(1)d×f(d)

f ( d ) = 2 k − 2 ( k 是 并 查 集 的 个 数 ) f(d)=2^k-2(k是并查集的个数) f(d)=2k2(k)

这个其中的 d d d就是对于这个数限制,关于这个限制还是要好好说一下的,就是呢,你对于这个数的二进制位从最右边开始进行遍历,如果他是 1 1 1或者他在这个限制内的数很符合这个限制的话,就记录他的答案,这就需要我们先把这个限制的满位遍历出来,然后在对于每一个限制进行对于每一个数的遍历,找到在这个限制内的所有的合法数,这一点可以用并查集进行合并,为什么用并查集进行合并呢,这里呢,我可以举个例子进行说明,当你已经限制了两位的时候就是 d = 2 d=2 d=2的时候,就可以有三种情况: 01 , 10 , 11 01,10,11 01,10,11,这样的情况中可以知道, 01 01 01& 10 10 10 10 10 10& 01 01 01 11 11 11& 01 01 01 11 11 11& 10 10 10 在这个限制中都是不相同的数,只有 11 11 11& 11 11 11 是对于这一限制有贡献的,而由于现在要找到所有没有贡献的数,那就可以开个并查集对于像 01 01 01 10 10 10这样的数进行一个合并,用并查集来做就很好。这所有都做完,是把 2 17 2^{17} 217都做一遍,这样的话最后就是查找一下并查集的个数就是 k k k,然后在减去你多加上的两种不合法情况就是 2 k − 2 2^{k}-2 2k2 了,至于为什么是 2 2 2 k k k次方,这个是组合数的基础知识,不知道的也可以打表找出来。最后对答案加上容斥的部分再进行合并即可。

细说一下这个容斥:

设S是有穷集, P 1 , P 2 , P 3 , … , P n P_1,P_2,P_3,…,Pn P1,P2,P3,,Pn 是n条性质。S中的任一元素x对于这n条性质可能符合其中的1种,2种,3种……n种,也可能都不符合。设 A i A_i Ai 表示S种具有 P i P_i Pi 性质的元素构成的子集。有限集合A中的元素个数记为 ∣ A ∣ \left| A \right| A。则:

S中具有性质 P 1 , P 2 , P 3 , … , P n P_1,P_2,P_3,…,Pn P1,P2,P3,,Pn 的元素个数为:
∣ A 1 ∣ + ∣ A 2 ∣ + … + ∣ A n ∣ \left|A_1\right|+\left|A_2\right|+…+\left|A_n\right| A1+A2++An

= ∑ i ∣ A i ∣ − ∑ i < j ∣ A i ∩ A j ∣ + ∑ i < j ∣ A i ∩ A j ∩ A k ∣ − … + ( − 1 ) n + 1 ∣ A 1 ∩ A 2 ∩ A 3 … ∩ A n ∣ =\sum_{i}\left|A_i\right|-\sum_{i<j}\left|A_i\cap A_j\right|+\sum_{i<j}\left|A_i\cap A_j\cap A_k \right|-…+(-1)^{n+1}\left|A_1\cap A_2\cap A_3…\cap A_n\right| =iAii<jAiAj+i<jAiAjAk+(1)n+1A1A2A3An
就是对于一大推要求和的集合,可以转化为求它们的交集,可以参考下下面的图:

在这里插入图片描述

这样的话就好理解多了,你需要求的就是所有蓝色面积的和,但是你只知道 A 1 , A 2 , A 3 … A n A_1,A_2,A_3…A_n A1,A2,A3An 这就可以通过一加一减的形式,减去重复的加上新增的,再加上之前的式子,这就很好理解了。

代码:

指数级爆搜:

#include<bits/stdc++.h>
using namespace std;
const int nn=100;
int n, ans=0, a[nn];
bool vis[nn];
inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}
void dfs(int now,int sum)//表示第now个数,选择了sum个数 
{
	if(now==n+1)//由于数now的初值的 1,则最大就是n+1, 
	{
		if(sum==0||sum==n) return ;//如果两个集合中存在没有选择的数 
		int x=(1<<18)-1, y=(1<<18)-1;//全赋为 1 
		for(int i=1;i<=n;++i) if(vis[i]) x=x&a[i];else y=y&a[i];//判断两个集合内的答案是否是一样的 
		if(x==y) ans++;
		return ;
	}
	dfs(now+1,sum); vis[now]=1;//不选择这个数 
	dfs(now+1,sum+1); vis[now]=0;//选择这个数
	return ;
}
int main()
{
//	freopen("and.in","r",stdin);
//	freopen("and.out","w",stdout);
	n=read(); for(int i=1;i<=n;++i) a[i]=read();
	dfs(1,0);
	printf("%d\n",ans);
	return 0;
}

正解:

#include<bits/stdc++.h>
#define int long long 
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int sea=61;
const int ocean=131072;
int n,ans=0,a[sea],b[sea],fa[sea];
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
signed main()
{
//	freopen("and.in","r",stdin);
//  	freopen("and.out","w",stdout);
	n=read(); over(i,1,n) a[i]=read();
	b[0]=1; over(i,1,sea) b[i]=b[i-1]*2;
	over(d,0,ocean-1)
	{
		int d1=d,cnt=1,s;//容斥
		over(i,1,n) fa[i]=i; 
		for(int i=0;i<17;i++,(d1>>=1),s=i)//枚举d的位数
		if(d1&1)
		{
			cnt=-cnt;//容斥 
			int j1=(int)b[i],j2=0,flag=0;//j1的初始值就是i满位的时候 
			over(j,1,n) //枚举所有的数 
			if(!(a[j]&j1))//如果这个数没有超过满位的j1
			{
				flag=1;//标记一下,如果是有的数大于现在的限制了,就直接扩大限制而不必是仅仅跳出次循环了 
		  		if(!j2) j2=get(j);//向上找到他的父亲节点,就是在比他位数少的数中有1的 
				else fa[get(j)]=j2;//要是之前都没有就把他直接赋成根节点,这样就会得到好多并查集 
			}
			if(!flag) break;
		}
		if(s!=17) continue; 
		int num=0; over(i,1,n) if(i==get(i)) num++;  //记录并查集个数 
		ans+=cnt*(b[num]-2);
		//答案就是所有位数上的(-1)^cnt*(2^k-2);具体的-2是因为你在枚举的时候并不能全部枚举完,要删除两种情况,就是枚举所有和枚举0种的情况 
    }
	printf("%lld\n",ans);
 	return 0;
}

Day2总结:

恕我直言,今天的题解,真的是巧妙啊,值域分块,并查集做序列操作,真的是强,而且特别考验对普通序列的尽可能优化,头脑风暴啊,tql,%%%%%%出题人。但是呢,我的T1是打挂了,,这就太惨了,尽管写过原题,可能是对原题还不是特别透彻吧,需要再研究研究多写几遍,T2是太ex了,std在我本机上跑的 5 s + 5s+ 5s+,在大佬电脑删跑的 1 s − 1s- 1s,不知为何,由于没有给是时限,是看自己评测机上的时间,老师开了两倍时限,我在可我还是死掉了,,,后来改了改才过,,但是这个想法是真的没想到,桶还能这这样开,,T3告诉我指数级(阶乘级)暴力很重要,不要忽略,之前都是说说,但是没写过,还没练过,然后就挂掉了。总之,今天是个头脑风暴但暴力主场的不知怎么说的比赛,所以还是那句话:一个问题要有两种想法,一种保底暴力,一种天马行空。

总结:

这两天的模拟赛打下来是150,要是D2T1不打挂就250了,然后就是在打指数级暴力就是270了,大概就接近省一了,,但是说实话,两天大概都能拿个150—180左右才可以稳稳的啊,这就很难了我还是菜,尽管就差不到60天了,我还是要好好努力的!!!

Continue……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值