8.7模拟赛小结
T1 转圈圈
题目大意:给你一个只含有一个
对于每个位置
思考:原题要求我们每次枚举一段区间翻转 我们可以判断56pts
继续探究:
为什么会set 就行了 时间复杂度降至100pts
注意:set并不能动态删点 所以要开一个栈或者队列存下要删的点统一删
56pts Code:
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,k,m,s;
int a[N],w[N],vis[N];
int q[N],l,r;
void bfs()
{
q[++r]=s;
vis[s]=1;
w[s]=0;
while(l<r)
{
int x=q[++l];
if(k&1)
{
for(int i=max(1,x-k+1);i<=x;i++)
{
int mid=(i+i+k-1)/2;
int pos=mid-(x-mid);
if(a[pos]) continue;
if(i+k-1>n) break;
if(vis[pos]) continue;
q[++r]=pos;
vis[pos]=1;
w[pos]=w[x]+1;
}
}
else
{
for(int i=max(1,x-k);i<=x;i++)
{
int mid=(i+i+k-1)/2;
int pos=mid-(x-mid)+1;
if(i+k-1>n) break;
if(a[pos]) continue;
if(vis[pos]) continue;
q[++r]=pos;
vis[pos]=1;
w[pos]=w[x]+1;
}
}
}
}
int main()
{
scanf("%d%d%d%d",&n,&k,&m,&s);
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
a[x]=1;
}
bfs();
for(int i=1;i<=n;i++)
{
if(vis[i])printf("%d ",w[i]);
else printf("-1 ");
}
return 0;
}
AC code
#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,k,m,st;
int a[N],w[N],vis[N];
int q[N],l,r;
int del[N],len;
set<int> s[2];
void bfs()
{
fill(w+1,w+1+n,-1);
q[++r]=st;
w[st]=0;
while(l<r)
{
int x=q[++l];
if(k&1)
{
int mid=(max(1,x-k+1)*2+k-1)/2;
int pos=mid*2-x-2;
int times=min(x,n+1-k)-max(1,x-k+1)+1;
int lpos=pos+2,rpos=pos+times*2;
set<int>::iterator ls=s[x&1].lower_bound(lpos),rs=s[x&1].upper_bound(rpos);
len=0;
for(set<int>::iterator it=ls;it!=rs;it++)
{
pos=*it;
del[++len]=pos;
q[++r]=pos;
w[pos]=w[x]+1;
}
for(int i=1;i<=len;i++)
s[x&1].erase(del[i]);
}
else
{
int mid=(max(1,x-k)*2+k-1)/2;
int pos=mid*2-x+1-2;
int times=min(x,n+1-k)-max(1,x-k)+1;
int lpos=pos+2,rpos=pos+times*2;
set<int>::iterator ls=s[pos&1].lower_bound(lpos),rs=s[pos&1].upper_bound(rpos);
len=0;
for(set<int>::iterator it=ls;it!=rs;it++)
{
pos=*it;
del[++len]=pos;
q[++r]=pos;
w[pos]=w[x]+1;
}
for(int i=1;i<=len;i++)
s[pos&1].erase(del[i]);
}
}
}
int main()
{
scanf("%d%d%d%d",&n,&k,&m,&st);
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
a[x]=1;
}
for(int i=1;i<=n;i++)
if(i!=st&&!a[i]) s[i&1].insert(i);
bfs();
for(int i=1;i<=n;i++)
printf("%d ",w[i]);
return 0;
}
T2 扌舌 口丂 匚儿 酉己
疑云顶针 鉴定为纯纯迷惑
思考:
部分分1
先想全为?的10pts部分分 发现只要区间长度为偶数即合法
那么容易发现公式为:
期望得分10pts
部分分2
考虑
怎么才能匹配一段括号呢?
- 1.区间长度为偶数
- 2.如果将所有
?替换成(将(看为1)看为-1 那么在l\to r 这段区间中 所有前缀和\ge0 - 2.如果将所有
?替换成)将(看为-1)看为1 那么在l\to r 这段区间中 所有后缀和\ge0
如果同时满足三个区间 明显为匹配区间
为什么?
根据以上推导 我们可以推导出
通过
期望得分20pts
Code
#include<bits/stdc++.h>
#define ll long long
#define N 1000005
using namespace std;
string s;
ll ans,s1[N],s2[N];
int main()
{
cin>>s;
for(int i=1;i<=s.size();i++)
s1[i]=s1[i-1]+(s[i-1]==')' ? -1:1);
for(int i=s.size();i>=1;i--)
s2[i]=s2[i+1]+(s[i-1]=='('?-1:1);
for(int i=1;i<=s.size();i++)
{
for(int j=1;j<i;j++)//枚举 j~i
{
int p=1;
if((j-i+1)&1) continue;
for(int k=j;k<=i;k++)
if(s1[k]<s1[j-1]||s2[k]<2[i+1]) p=0;
ans+=p;
}
}
cout<<ans;
return 0;
}
部分分3
考虑优化
这样子 可以分类讨论去掉一句话
然后看下一句
if(a[j]>i) ans++;
这不就是求一段区间内有多少个数比当前的数大吗?考虑主席树
可能会想到主席树 可以维护
但是仔细想想会发现 实际上
那么我们可以考虑把
单点修改,区间查询 鉴定为树状数组
时间复杂度100pts
Code
#include<bits/stdc++.h>
#define ll long long
#define N 1000005
using namespace std;
string s;
ll ans;
int s1[N],s2[N];
int a[N],b[N],q[N],l;
ll tr[2][N];
struct node{
int x,v;
};
bool operator < (node a,node b)
{
return a.v>b.v;
}
priority_queue<node> p[2];
void add(int p,int x,int v)
{
x+=3;
while(x<=s.size()+3)
{
tr[p][x]+=v;
x+=x&-x;
}
}
ll ask(int p,int x)
{
x+=3;
ll sum=0;
while(x)
{
sum+=tr[p][x];
x-=x&-x;
}
return sum;
}
int main()
{
cin>>s;
for(int i=1;i<=s.size();i++)
s1[i]=s1[i-1]+(s[i-1]==')' ? -1:1);
for(int i=s.size();i>=1;i--)
s2[i]=s2[i+1]+(s[i-1]=='(' ? -1:1);
for(int i=0;i<=s.size();i++)
{
a[i]=s.size()+1;
while(l&&s1[i]<s1[q[l]])
a[q[l--]]=i;
q[++l]=i;
}
l=0;
for(int i=s.size()+1;i>=1;i--)
{
b[i]=0;
while(l&&s2[i]<s2[q[l]])
b[q[l--]]=i;
q[++l]=i;
}
for(int i=0;i<=s.size();i++)
{
while(!p[i&1].empty()&&p[i&1].top().v<=i)
{
add(i&1,p[i&1].top().x,-1);
p[i&1].pop();
}
ans+=ask(i&1,i-2)-ask(i&1,b[i+1]-1);
if(a[i]>i) add(i&1,i,1),p[i&1].push((node){i,a[i]});
}
cout<<ans;
return 0;
}
T3 米哈游内战 USACO
我现在要点名一款????二字游戏
简要题意:给你一个字符串 只能交换相邻两个字符 求出让每个子串变成回文串的最小步数的和 变不了贡献就为
思考:
怎样才是最小步数呢?
把原串抽象成
因为你只能交换相邻的字符 如果相邻字符相同后交换等于没有交换 一定不优
举个例子:
交换第
因此 如果
那么一定是
现在分析怎么才能两两对称
定义中点为
如果当前两点中点在
最终点对一定是
需要移动的步数为
等价于
所以答案就是
前面的东西直接预处理了 后面的把
这样
枚举答案的方法就是枚举中间点 然后向两边扩展即可
时间复杂度
code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#define N 7505
#define ll long long
using namespace std;
int n;
ll ans;
int cnt,c[N];
string s;
struct tree{
int tr[N*2];
void clear(int n)
{
for(int i=1;i<=n*2;i++)
tr[i]=0;
return ;
}
void add(int x,int v)
{
while(x<=n*2)
{
tr[x]+=v;
x+=x&-x;
}
return;
}
int ask(int x)
{
int sum=0;
while(x)
{
sum+=tr[x];
x-=x&-x;
}
return sum;
}
}tr[2];
int main()
{
cin>>s;
n=s.size();
s=" "+s;
for(int l=1;l<=n;l++)
{
for(int r=l;r<=n;r++)
{
if(s[r]=='G') c[++cnt]=r;
if(cnt&1 &&(r-l)&1) ans--;
else if(cnt&1) ans+=abs((l+r)/2-c[cnt/2+1]);
}
cnt=0;
}
for(int i=1;i<=n;i++)
if(s[i]=='G') c[++cnt]=i;
c[cnt+1]=n+1;
for(int i=1;i<=cnt;i++)//以c[i]为中点
{
tr[0].clear(n),tr[1].clear(n);
int sum=0;
for(int j=1;i-j>=1&&i+j<=cnt;j++)
{
int p=c[i-j]+c[i+j];
tr[0].add(p,1);
tr[1].add(p,p);
sum+=p;
for(int l=c[i-j-1]+1;l<=c[i-j];l++)
for(int r=c[i+j];r<=c[i+j+1]-1;r++)
{
if((r-l)&1) continue;
int s1=tr[0].ask(l+r),s2=tr[1].ask(l+r);
ans+=1ll*s1*(l+r)-s2;
ans+=(sum-s2)-1ll*(j-s1)*(l+r);
}
}
}
for(int i=1;i<=cnt;i++)
{
tr[0].clear(n);
tr[1].clear(n);
int sum=0;
for(int j=0;i-j>=1&&i+j+1<=n;j++)
{
int p=c[i-j]+c[i+j+1];
tr[0].add(p,1);
tr[1].add(p,p);
sum+=p;
for(int l=c[i-j-1]+1;l<=c[i-j];l++)
for(int r=c[i+j+1];r<=c[i+j+1+1]-1;r++)
{
int s1=tr[0].ask(l+r),s2=tr[1].ask(l+r);
ans+=1ll*(l+r)*s1-s2;
ans+=1ll*(sum-s2)-1ll*(l+r)*(j+1-s1);
}
}
}
cout<<ans;
return 0;
}
T4 抽卡 原题
摆(