E. Arranging The Sheep
最终答案就是移动到中间那只羊,羊是偶数的话就靠近中间任取一只就行。
∗
.
∗
.
.
.
∗
.
∗
∗
*.*...*.**
∗.∗...∗.∗∗
羊都往第三只那里移动,即下标为7的那只羊,可以发现3会移动3步,1会移动4步,9移动1,10移动1.一共移动9步。且每只羊移动的步数就是与中间羊之间的.的数量
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
string s;
int main()
{
int t;
cin>>t;
while(t--)
{
int n,cur=0;
cin>>n;
cin>>s;
vector<int>a;
for(int i=0;i<n;i++)
{
if(s[i]=='.')++cur;
else a.push_back(cur);
}
if(a.empty())
{
puts("0");
continue;
}
int m=a[a.size()/2];
long long ans=0;
for(auto i:a)ans+=abs(m-i);
cout<<ans<<endl;
a.clear();
}
return 0;
}
F1 Guess the K-th Zero (Easy version)
询问给出的和实际上就是告诉你区间 l-r 之间1的个数
所以用区间长度-询问就是区间0的个数,然后就可以用二分了
当 1~mid的0的个数>=k 的时候 r=mid, l=mid+1
(l=mid+1 因为个数<k说明1~mid的0不够多,要把mid往右边挪动)
这样就可以让 l 不断逼近答案。
最终操作次数就是log的
代码中的g数组是F2分组用的,不用管
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int g[N],cnt=0,n,k;
int ask(int l,int r)
{
cout<<'?'<<' '<<l<<' '<<r<<endl;
int x;
cin>>x;
return x;
}
void print(int x)
{
cout<<'!'<<' '<<x<<endl;
int id=(x+10-1)/10;
for(int i=id;i<=cnt;i++)g[i]--;
}
bool check(int mid)
{
return mid-ask(1,mid)>=k;
}
int main()
{
int t,f=0;
cin>>n>>t;
while(t--)
{
scanf("%d",&k);
int l=1,r=n;
while(l<r)
{
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
print(l);
}
return 0;
}
F2. Guess the K-th Zero (Hard version)
1.先将n按长度10分组。即[1-10][11-20][21-30]…
然后右端点不能超过n。比如最后一组是[21-28]
把每组依次编号1,2,3,。。。。
分出2e4组
2.对每一组求前缀0的个数,用掉2e4 次query,
query(1,每组的右端点)
这样我们就可以扫一遍,快速确认要到哪个分组里面进行二分
此时还剩下4e4次query
题目一共会有1e4次询问
确认到哪个分组里进行二分要log(10)次,大约是4次
所以是4e4次query,刚刚好。
交互题一般思路:二分,分组,分块,二进制。
注意右端点不能超过n
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int g[N],cnt=0,n,k;
int ask(int l,int r)
{
cout<<'?'<<' '<<l<<' '<<r<<endl;
int x;
cin>>x;
return x;
}
void print(int x)
{
cout<<'!'<<' '<<x<<endl;
int id=(x+10-1)/10;
for(int i=id;i<=cnt;i++)g[i]--;
}
bool check(int mid)
{
return mid-ask(1,mid)>=k;
}
int main()
{
int t,f=0;
cin>>n>>t;
while(t--)
{
scanf("%d",&k);
if(!f)
{
for(int i=1;i<=n;i+=10)
{
int r=min(i+10-1,n);
g[++cnt]=r-ask(1,r);
}
f=1;
}
int l=0,r=1;
while(g[r]<k)r++;
r=10*r,l=r-9;
r=min(r,n);
while(l<r)
{
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
print(l);
}
return 0;
}
G. To Go Or Not To Go?
传送门最多使用一次就行。
设ms是从起点到传送门的最短花费(包括传送门费用和路程花费),从起点bfs一遍,到每个点的最短距离,同时如果该点是传送门的话,更新
m
s
=
m
i
n
(
m
s
,
d
i
s
[
x
]
[
y
]
+
a
[
x
]
[
y
]
)
ms=min(ms,dis[x][y]+a[x][y])
ms=min(ms,dis[x][y]+a[x][y])
然后从终点弄个me,从终点做同样的操作
如果ms和me都不是无穷大,则ans=ms+me,还有一种可能就是不走传送门,然后两种情况取个min就是最终答案。
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10;
typedef long long ll;
#define pii pair<int,int>
ll n,m,w,a[N][N],dis[N][N],dx[]= {0,0,1,-1},dy[]= {1,-1,0,0},ms,me;
queue<pii>q;
void bfs1()
{
memset(dis,-1,sizeof dis);
ms=1e18;
q.push({1,1});
dis[1][1]=0;
while(!q.empty())
{
auto t=q.front();
q.pop();
int x=t.first,y=t.second;
if(a[x][y]>0)ms=min(ms,dis[x][y]+a[x][y]);
for(int i=0; i<4; i++)
{
int nx=x+dx[i],ny=y+dy[i];
if(nx<1||nx>n||ny<1||ny>m||a[nx][ny]==-1||dis[nx][ny]!=-1)continue;
dis[nx][ny]=dis[x][y]+w;
q.push({nx,ny});
}
}
}
void bfs2()
{
memset(dis,-1,sizeof dis);
me=1e18;
while(!q.empty())q.pop();
q.push({n,m});
dis[n][m]=0;
while(!q.empty())
{
auto t=q.front();
q.pop();
int x=t.first,y=t.second;
if(a[x][y]>0)me=min(me,dis[x][y]+a[x][y]);
for(int i=0; i<4; i++)
{
int nx=x+dx[i],ny=y+dy[i];
if(nx<1||nx>n||ny<1||ny>m||a[nx][ny]==-1||dis[nx][ny]!=-1)continue;
dis[nx][ny]=dis[x][y]+w;
q.push({nx,ny});
}
}
}
int main()
{
cin>>n>>m>>w;
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)scanf("%lld",&a[i][j]);
bfs1();
bfs2();
ll ans=dis[1][1];
if(ms!=1e18&&me!=1e18)
{
if(ans!=-1)ans=min(ans,ms+me);
else ans=ms+me;
}
cout<<ans;
return 0;
}