0924 模拟赛
写在前面
\(cb\)什么都不会\(QAQ\)
T1 括号序列
Idea
题目要求为子串,即连续。
即,当前面出现一个合法的子串,后面又有一个合法的,\(Ans=1+1\);
同理,如果前面有\(n\)个合法的,后面又有一个合法的,\(Ans=1+n\)
于是每当遇到一个合法的括号对儿时,看它前面是否有连续的括号对儿
用\(stack\)记录,用\(stack.tmp\)来记录当前括号作为两个连续合法序列是的分隔时,当前括号后面有多少合法序列。
某机房\(dalao\)说他就写了个爆搜,然后....他\(A\)了....
还有个\(dalao\)写了个双端队列,然后....他\(MLE\)了....
Code
struct Node{
int x,tmp;
}b;
char a[maxn];
stack<Node>s;
signed main(){
// freopen(File".in","r",stdin);
// freopen(File".out","w",stdout);
cin>>a+1;
int len=strlen(a+1);
int ans=0,sum=0;
for(int i=1;i<=len;i++){
if(a[i]=='(') b.x=1;
else b.x=2;
if(s.size()){
if(s.top().x==1&&b.x==2){
s.pop();
if(s.empty()){
ans+=(sum+1);
sum++;
}
else{
ans+=(s.top().tmp+1);
s.top().tmp++;
}
}
else s.push(b);
}
else s.push(b);
}
printf("%lld",ans);
return 0;
}
T2 和数检测
Idea
这道题...,看完题我就用了\(map\)...
不想再多说些什么了... 代码一看就懂...
只有\(55pts\)....
\(dalao\)们采取了树状数组、双指针、二分等操作写了这道题。果然,我就是个\(cb\)
Code
int a[maxn];
signed main(){
// freopen(File".in","r",stdin);
// freopen(File".out","w",stdout);
int T=read();
while(T--){
map<int,int>p;
int flag=1;
int n=read(),m=read();
for(int i=0;i<n;i++){
a[i]=read();
if(flag){
if(p[m-a[i]]==1) flag=0;
if(p[m-a[i]]!=1){
p[a[i]]=1;
p[m-a[i]]=2;
}
if(p[a[i]]==2) flag=0;
}
}
if(!flag) puts("1");
else puts("0");
}
return 0;
}
以下正解
const int maxx=10000010;
int n,flag,m;
int a[maxn],f[maxx];
int main(){
// freopen(File".in","r",stdin);
// freopen(File".out","w",stdout);
int T=read();
for(int k=1;k<=T;k++){
if(k!=1)for(int i=1;i<=n;i++)if(a[i]<=maxx-10)f[a[i]]=0;
n=read();m=read();flag=0;
for(int i=1;i<=n;i++){
a[i]=read();
if(a[i]<=maxx)f[a[i]]=1;
}
if(m<=10000000){
for(int i=1;i<=n;i++){
if(a[i]>m)continue;
if(f[m-a[i]]){flag=1;break;}
}
if(flag)puts("1");
else puts("0");
}
else{
sort(a+1,a+1+n);
int j=n,w;
for(int i=1;i<=n;++i){
if(a[i]>m)break;
w=m-a[i];
while(a[j]>w&&j) j--;
if(!j)break;
if(a[j]==w){flag=1;break;}
}
if(flag)puts("1");
else puts("0");
}
}
return 0;
}
T3 与
Idea
考场上,我一直在琢磨样例一咋搞,结果还是没搞出来。我太菜了\(QAQ\)
到最后还是没搞出来,
看看题解的说法
考虑容斥,则\(Ans= \displaystyle \sum_{c=0}^{131071} (-1)^c \times f(c)\)
其中,变量\(c\)枚举的是子集,代表需要限制为不符合要求的二进制位(即这些位一定得一边是0一边是1)。\(f(c)\)指在\(c\)的限制下的方案数。
由于有一些位一定得一边是0一边是1,所以这些位为1的元素一定得归一边,可以考虑把它们合并为一个元素。如果有多个位有限制,那就用并查集来合并元素。合并后,剩余的这些元素可以被任意分到左侧或右侧,则\(f(c) = 2^k − 2\),其中\(k\)表示合并后的元素个数,减去2是为了避免有一侧没有分配到元素。具体的\(-2\)是因为你在枚举的时候并不能全部枚举完,要删除两种情况,就是枚举所有的枚举0种的情况
如果\(n\)个数的某个二进制位全部为0或为1,则可以直接把这一位去掉。这样可以避免在后续计算时的特殊讨论。
Code
int n,ans;
int a[maxn],b[maxn],fa[maxn];
inline int find(int x){
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
signed main(){
// freopen(File".in","r",stdin);
// freopen(File".out","w",stdout);
n=read();
for(int i=1;i<=n;i++) a[i]=read();
b[0]=1;
for(int i=1;i<=maxn;i++) b[i]=b[i-1]*2;
for(int d=0;d<=131071;d++){
int d1=d,cnt=1,s;//容斥
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=0;i<17;i++,(d1>>=1),s=i)//枚举d的位置
if(d1&1){
cnt=-cnt;//容斥
int j1=b[i],j2=0,flag=0;//j1的初始值就是i满位的时候
for(int j=1;j<=n;j++)//枚举所有的数
if(!(a[j]&j1)){//如果这个数没有超过满位的j1
flag=1;//标记一下,如果是有的数大于现在的限制了,就直接扩大限制而不必是仅仅跳出次循环了
if(!j2) j2=find(j);//向上找到他的父亲节点,就是在比他位数少的数中有1的
else fa[find(j)]=j2;//要是之前都没有就把他直接赋成根节点,这样就会得到好多集合
}
if(!flag) break;
}
if(s!=17) continue;
int num=0;
for(int i=1;i<=n;i++) if(i==find(i)) num++;//记录集合个数
ans+=cnt*(b[num]-2);
}
printf("%lld",ans);
return 0;
}