背景:
模拟赛定下来了,,周一周二周五,周三讨论,这晚自习占的,一看课表发现周三周四是语文和英语晚自习,这还是不停课吗???白天的课,能(想)上的就上(主要上理科和数学,,语文英语基本没上过,,,结果就被KK怼回去上文化课了,结果发现好多不好补回来,但是没有大片的空白,就是那些老师拓展的东西没有学到,有点亏了,还有作业,基本没写,就是偶尔自己闲了就写写题当做休息,,这样怎么可能提高文化课,,,)这文化课啊,还是要好好学的,毕竟竞赛不是上学的唯一途径,但,高考是。
不瞎扯了来好好看题,,,
题目:
Day 1 题目:
T1: 硬币求和(scoins)
简化题目:
求 ∑ i = 1 n 1 2 i \sum_{i=1}^{n} \frac{1}{2\sqrt{i}} ∑i=1n2i1。(答案误差不超过 ± 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=1∑n2i1=21∗(i1)
要求出来这个答案,可以从
1
i
\frac{1}{\sqrt{i}}
i1 这里下手。
从高中的放缩得到常见不等式:
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+n2<n1<n+n−12=2∗(n−n−1)
可以知道:
∑
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=1∑ni+i+12<i=1∑ni1<i=n∑1i+i−12
从左边的最小值推出:
∑
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=1∑ni+1+i2=2∗i=1∑n(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=n∑1i−1+i2=2∗i=n∑1(i−i−1)=2∗n
再乘上刚开始的
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 n,m<=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]=0≤k<nmin{2f[n][m]+f[n−k][m−1]}
这个是表示你在取
k
k
k个盘子的时候是会把它们放在
m
m
m个柱子上的,然后就在把剩下的盘子放在
m
−
1
m-1
m−1个柱子上,然后再把之前的盘子放到最后一个柱子上,由于是取
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<=R∑f[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+1≤R,d1≤d2∑f[p][d1]⎠⎞×f[v][d2]f′[p][d1]+=d1+d2+1≤R,d1>d2∑f[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 50—55分,那就二分,,也是 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 1—40000,然后对于每个数再进行取模等一下代码细节,具体看代码吧,应该很好理解。
代码:
#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=0∑131071(−1)d×f(d)
f ( d ) = 2 k − 2 ( k 是 并 查 集 的 个 数 ) f(d)=2^k-2(k是并查集的个数) f(d)=2k−2(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 2k−2 了,至于为什么是 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|
=i∑∣Ai∣−i<j∑∣Ai∩Aj∣+i<j∑∣Ai∩Aj∩Ak∣−…+(−1)n+1∣A1∩A2∩A3…∩An∣
就是对于一大推要求和的集合,可以转化为求它们的交集,可以参考下下面的图:
这样的话就好理解多了,你需要求的就是所有蓝色面积的和,但是你只知道 A 1 , A 2 , A 3 … A n A_1,A_2,A_3…A_n A1,A2,A3…An 这就可以通过一加一减的形式,减去重复的加上新增的,再加上之前的式子,这就很好理解了。
代码:
指数级爆搜:
#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天了,我还是要好好努力的!!!