因为只有4种字母,且只能询问 n + 2 n+2 n+2次,那么我们需要平均一次就要确定一位。
而询问的串长,最长为 4 × n 4\times n 4×n,所以每次对于一个要确定的字母我们可以直接输出四种情况。但是这样需要2次确定一位,所以我们需要进一步优化。
首先,我们可以花两步确定第一位:
询问"AB",如果返回的值大于0,则就是"AB"中的一个,然后就输出一个"A",如果返回值为1,那么开头就是"A",否则为"B"。如果不是"AB",那么就是"XY",所以输出"X",和"AB"同样的判断方法,那么就可以知道第一位。
对于后面的每一位,我们要尽量做到一次询问确定一位。由于开头的那一位不会在后面再次出现,那么我们只剩下三种选择。
举个例子说明:
假如开头是"A",那么下一位可能为"B,X,Y",而如果每次只增加一位的话,就还要花费一个操作去确定,所以我们增加两位:“BB”,“BX”,“BY”,这样如果返回值没有增加,下一位就不是"B",但是如果增加了两个,那么下一位就是"B",如果不是"B"的话,还要判断"X,Y",由于"A"不能再出现,所以我们再加一个"XA",这样如果返回值只增加了一个,那么下一位就是"X",否则没增加就是"Y"。
所以直接模拟,而到了最后一位,直接按照开始的方法,在剩下三种情况中花费两个操作判断一下即可。
代码(交互):
#include "combo.h"
using namespace std;
string Nex(char a,int id){
if(a=='A'){
if(id==1) return "BB";
if(id==2) return "BX";
if(id==3) return "BY";
if(id==4) return "XA";
}else if(a=='B'){
if(id==1) return "AA";
if(id==2) return "AX";
if(id==3) return "AY";
if(id==4) return "XB";
}else if(a=='X'){
if(id==1) return "AA";
if(id==2) return "AB";
if(id==3) return "AY";
if(id==4) return "BX";
}else if(a=='Y'){
if(id==1) return "AA";
if(id==2) return "AB";
if(id==3) return "AX";
if(id==4) return "BY";
}
}
string Ok(char a,int con,int bef){
if(a=='A'){
if(con==bef){
return "Y";
}else if(con==bef+1){
return "X";
}else if(con==bef+2){
return "B";
}
}else if(a=='B'){
if(con==bef){
return "Y";
}else if(con==bef+1){
return "X";
}else if(con==bef+2){
return "A";
}
}else if(a=='X'){
if(con==bef){
return "Y";
}else if(con==bef+1){
return "B";
}else if(con==bef+2){
return "A";
}
}else if(a=='Y'){
if(con==bef){
return "X";
}else if(con==bef+1){
return "B";
}else if(con==bef+2){
return "A";
}
}
}
string End(char a,int id){
if(a=='A'){
if(id==1){
return "B";
}else if(id==2){
return "X";
}else if(id==3){
return "Y";
}
}else if(a=='B'){
if(id==1){
return "A";
}else if(id==2){
return "X";
}else if(id==3){
return "Y";
}
}else if(a=='X'){
if(id==1){
return "A";
}else if(id==2){
return "B";
}else if(id==3){
return "Y";
}
}else if(a=='Y'){
if(id==1){
return "A";
}else if(id==2){
return "B";
}else if(id==3){
return "X";
}
}
}
string guess_sequence(int N){
string st="";
string pre="AB";
int con=press(pre);
if(!con){
con=press("X");
if(con) st="X";
else st="Y";
}else{
con=press("A");
if(con) st="A";
else st="B";
}
pre=st;
if(N==1) return pre;
for(int i=2;i<=N-1;i++){
string ask="";
for(int j=1;j<=4;j++)ask+=pre+Nex(st[0],j);
con=press(ask);
pre+=Ok(st[0],con,i-1);
}
string last=pre+End(st[0],1)+pre+End(st[0],2);
con=press(last);
if(con==N-1){
last=pre+End(st[0],3);
}else{
last=pre+End(st[0],1);
con=press(last);
if(con==N-1){
last=pre+End(st[0],2);
}
}
return last;
}
开始我想的是,对于一个前缀 0 ∼ L 0\sim L 0∼L,我们维护里面最左下角的点和最右上角的点,然后每次询问我们就判断每个前缀数的个数是不是等于所在矩形区间的大小,为了支持快速查询修改,外面再套一个树,或者用主席树之类的,但是就是十分复杂,且复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的,是无法通过所有数据的。
所以我们转换思路,对于一个前缀 0 ∼ L 0\sim L 0∼L如果它形成了一个矩形,那么与其相交的所有 2 × 2 2\times 2 2×2的子矩形要么是只有两个相交,要么是全部在里面,要么是只有一个相交,而一个的只有矩形的四个顶点,而三个相交的又不存在,但是2,4个的个数不确定,所以我们就只维护对于一个前缀 0 ∼ L 0\sim L 0∼L的所在格子形成的图形与其相交的所有 2 × 2 2\times 2 2×2的子矩形中 1 , 3 1,3 1,3的个数,如果合法的话必须只有 4 , 0 4,0 4,0个。
这个可以先预处理出来,然后用权值线段树维护即可,每次修改就交换然后线段树上改这两个点周围包含它的 2 × 2 2\times 2 2×2的子矩形即可,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
#include "seats.h"
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=1e6+10;
vector <int> mp[M];
int rr[M],cc[M];
int B1[M],B3[M];
int n,m;
struct node{
int b1,b3;
node(){}
node(int a,int b):b1(a),b3(b){}
bool operator <(const node &a)const{
return b1<a.b1||(b1==a.b1&&b3<a.b3);
}
node operator +(const node &a)const{
return node(b1+a.b1,b3+a.b3);
}
bool operator ==(const node &a)const{
return b1==a.b1&&b3==a.b3;
}
node operator +=(const node &a){return *this=*this+a;}
bool isok(){return b1==4&&!b3;}
};
int tot,root;
node S[M<<2],lazy[M<<2];
int ls[M<<2],rs[M<<2],sum[M<<2];
void pushup(int o){
S[o]=min(S[ls[o]],S[rs[o]]);sum[o]=0;
if(S[o]==S[ls[o]])sum[o]+=sum[ls[o]];
if(S[o]==S[rs[o]])sum[o]+=sum[rs[o]];
}
void pushdown(int o){
node &now=lazy[o];
if(now==node(0,0)) return;
S[ls[o]]+=now;S[rs[o]]+=now;
lazy[ls[o]]+=now;lazy[rs[o]]+=now;
now=node(0,0);
}
void build(int &o,int l,int r){
o=++tot;lazy[o]=node(0,0);
if(l==r){
S[o]=node(B1[l],B3[l]);sum[o]=1;
return;
}
int mid=l+r>>1;
build(ls[o],l,mid);
build(rs[o],mid+1,r);
pushup(o);
}
void update(int o,int l,int r,int L,int R,node v){
if(L>R) return;
if(L<=l&&r<=R){
S[o]+=v;lazy[o]+=v;
return;
}
pushdown(o);
int mid=l+r>>1;
if(L<=mid) update(ls[o],l,mid,L,R,v);
if(R>mid) update(rs[o],mid+1,r,L,R,v);
pushup(o);
}
int query(){
if(S[root].isok()) return sum[root];
else return 0;
}
int stk[4];
const int dx[]={0,1,0,1},dy[]={0,0,1,1};
void init(int x,int y){
for(int i=0;i<=3;i++){
stk[i]=mp[x+dx[i]][y+dy[i]];
} sort(stk,stk+4);
}
void give_initial_chart(int H, int W, std::vector<int> R, std::vector<int> C) {
n=H,m=W;
for(int i=0;i<=n+1;i++){
mp[i].resize(m+2);
for(int j=0;j<=m+1;j++){
mp[i][j]=n*m+1;
}
}
for(int i=1,sz=n*m;i<=sz;i++){
rr[i]=R[i-1]+1;cc[i]=C[i-1]+1;
mp[rr[i]][cc[i]]=i;
}
for(int i=0;i<=n;i++)for(int j=0;j<=m;j++){
init(i,j);
++B1[stk[0]];--B1[stk[1]];
++B3[stk[2]];--B3[stk[3]];
}
for(int i=1,sz=n*m;i<=sz;i++){
B1[i]+=B1[i-1];
B3[i]+=B3[i-1];
}
build(root,1,n*m);
}
void New(int x,int y,int v){
init(x,y);
update(root,1,n*m,stk[0],stk[1]-1,node(v,0));
update(root,1,n*m,stk[2],stk[3]-1,node(0,v));
}
const int tx[]={0,-1,0,-1},ty[]={0,0,-1,-1};
void Up(int x,int y,int v){
for(int i=0;i<=3;i++)
New(x+tx[i],y+ty[i],-1);
mp[x][y]=v;
for(int i=0;i<=3;i++)
New(x+tx[i],y+ty[i],1);
}
int swap_seats(int a, int b) {
++a;++b;
int v1=mp[rr[a]][cc[a]],v2=mp[rr[b]][cc[b]];
swap(rr[a],rr[b]);swap(cc[a],cc[b]);
Up(rr[a],cc[a],v1);
Up(rr[b],cc[b],v2);
return query();
}
由于每次每种形态不能去和能去的都是一个编号的前缀或者后缀,所以我们考虑将图转换成两个重构树,一个是编号小的向大的,另一个是编号大的向小的。
然后一个前缀就对应了小的向大的树上的一个子树,后缀就对应了大的向小的上面的一个子树,每次用类似二维数点的方式判断这两个子树集合是否有交,有交就证明可以在这里变成狼人到达终点,否则就不行。
将询问离线,在两个树上的dfs序上,就可以用树状数组来求答案啦。
这里的重构树的方法类似于kruskal重构树,所以用个并查集来建就好了。
#include "werewolf.h"
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lowbit(a) ((a)&(-(a)))
using namespace std;
const int M=1e6+1;
const int Log=20;
struct DSU{
int f[M];
void init(int n){
for(int i=0;i<=n;i++)f[i]=i;
}
int find(int a){return f[a]==a?a:f[a]=find(f[a]);}
int & operator [](int a){return f[a];}
int operator ()(int a){return find(a);}
};
int n;
struct Rebuild_Tree{
vector <int> g[M],t[M];
int in[M],root;
void add(int a,int b){g[a].push_back(b);}
void link(int a,int b){t[a].push_back(b);++in[b];}
DSU F;
int dfn[M],out[M],tim;
int f[Log][M];
void dfs(int a){
dfn[a]=++tim;
for(int i=1;i<Log;i++)
f[i][a]=f[i-1][f[i-1][a]];
for(auto v: t[a]){
f[0][v]=a;
dfs(v);
}
out[a]=tim;
}
void build(bool flag){
F.init(n);
if(flag){
for(int i=1,u;i<=n;i++){
for(auto v: g[i]){
u=F(v);
if(i!=u){
F[u]=i;
link(i,u);
}
}
}
}else{
for(int i=n,u;i>=1;i--){
for(auto v: g[i]){
u=F(v);
if(i!=u){
F[u]=i;
link(i,u);
}
}
}
}
for(int i=1;i<=n;i++)if(!in[i]){root=i;break;}
dfs(root);
}
void Find(int a,int to,int tp,int &x,int &y){
for(int i=Log-1;i>=0;i--){
if(f[i][a]&&(tp?f[i][a]>=to:f[i][a]<=to)){
a=f[i][a];
}
}
x=dfn[a]-1;
y=out[a];
}
}T1,T2;
int bit[M];
void add(int a,int b){
for(;a<=n;a+=lowbit(a))bit[a]+=b;
}
int query(int a){
int res=0;
for(;a;a-=lowbit(a))res+=bit[a];
return res;
}
struct Ask{
int s,t;
Ask(){}
Ask(int a,int b):s(a),t(b){}
};
vector <Ask> Q[M];
vector <int> ans;
int ref[M];
int a,b;
vector<int> check_validity(int N,vector<int> X,vector<int> Y,vector<int> S,vector<int> E,vector<int> L,vector<int> R) {
n=N;
for(int i=0,sz=X.size();i<sz;i++){
a=X[i]+1;b=Y[i]+1;
if(a<b)swap(a,b);
T1.add(a,b);T2.add(b,a);
}
T1.build(1);
T2.build(0);
for(int i=0,sz=S.size(),lx,rx,ly,ry;i<sz;i++){
T2.Find(S[i]+1,L[i]+1,1,lx,rx);
T1.Find(E[i]+1,R[i]+1,0,ly,ry);
Q[lx].push_back(Ask(ly,i));Q[lx].push_back(Ask(-ry,i));
Q[rx].push_back(Ask(-ly,i));Q[rx].push_back(Ask(ry,i));
}
for(int i=1;i<=n;i++)
ref[T2.dfn[i]]=T1.dfn[i];
ans.resize(S.size());
for(int i=1;i<=n;i++){
add(ref[i],1);
for(int j=0,sz=Q[i].size();j<sz;j++){
Ask now=Q[i][j];
int fl=now.s<0?now.s=-now.s,-1:1;
ans[now.t]+=query(now.s)*fl;
}
}
for(int i=0,sz=ans.size();i<sz;i++)if(ans[i]>0)ans[i]=1;else ans[i]=0;
return ans;
}
本文深入解析了算法竞赛中的策略,包括组合动作问题的优化方法,排座位问题的高效算法,以及狼人游戏中的路径判断技巧。通过对具体问题的分析,提出了创新的解决方案,如重构树和树状数组的应用。
3932

被折叠的 条评论
为什么被折叠?



