某些题过水没有加入。
CF1584D Guess the Permutation
原本有一个长度为 \(n\) 的序列 \(a\) 满足 \(a_i=i\)。现在我们将 \([i,j-1]\) 与 \([j,k]\) 进行翻转,我们不会告诉你 \(i,j,k\),你每次可以向交互库询问一个区间的逆序对数,试在 \(40\) 次询问内求出 \(i,j,k\)。
\(4\le n\le 10^9\)。
Solution
\(\log 10^9\) 约为 \(30\),先利用二分询问前缀,来得到 \(i\) 的取值。
如果我们询问 \([i,n]\) 与 \([i+1,n]\) 我们就可以得知 \([i,j-1]\) 的长度,进而求出 \(j\),那么 \(k\) 也解决了。
时间复杂度 \(O(\log n)\),询问次数 \(log n+4\)。
Code
ll query(int L,int R) {
printf("? %d %d\n",L,R);fflush(stdout);
ll ret;scanf("%lld",&ret);
return ret;
}
void solve() {
scanf("%d",&n);
int L=1,R=n,i;
while(L<=R) {
int mid=(L+R)>>1;
if(query(1,mid)==0) L=mid+1;
else R=mid-1;
}
// printf("%d %d\n",L,R);
i=L-1;
int j=i+query(i,n)-query(i+1,n)+1;
int k=j+query(j,n)-query(j+1,n);
printf("! %d %d %d\n",i,j,k);fflush(stdout);
return;
}
CF1584E Game with Stones
给定一个长度为 \(n\) 的序列 \(a_i\),现在问其中有多少个子段,满足存在一种方案,每次使相邻的两个数减一,可以使得所有数变为 \(0\)。
\(1\le n\le 3\times 10^5\),\(0\le a_i\le 10^9\)。
Solution
从后往前考虑每一个元素,并维护,当当前元素是 \(a_i\),每个两个元素应该要被取多少次。
比如说四个元素 \(a_1,a_2,a_3,a_4\),建出的序列就是 \(a_1,a_2-a_1,a_3-a_2+a_1,a_4-a_3+a_2-a_1\)。
考虑在前面加上一个 \(a_0\) 会发生什么,会变成 \(a_0,a_1-a_0,a_2-a_1+a_0,a_3-a_2+a_1-a-0,a_4-a_3+a_2-a_1+a_0\)。
不难发现是偶数位添加 \(a_0\),奇数位添加 \(-a_0\),接下来考虑统计答案。
如果对于某一个位置 \(p\),发现其变为负数了,那么说明取不出了,那么我只需要查出 \([i,p]\) 这一段内的子段数,回到序列上,也就是说序列对应元素为 \(0\),只要查出 \(0\) 的个数就行了。
直接使用线段树维护,时间复杂度 \(O(n\log n)\)。
Code
struct SGT {
ll add[N*4+10],mi[N*4+10];
int mic[N*4+10];
void pushup(int p) {
mi[p]=min(mi[p*2],mi[p*2+1]);
mic[p]=0;
if(mi[p]==mi[p*2]) mic[p]+=mic[p*2];
if(mi[p]==mi[p*2+1]) mic[p]+=mic[p*2+1];
}
void pushadd(int p,int v) {
mi[p]+=v,add[p]+=v;
}
void build(int p,int l,int r) {
// printf("%d,%d,%d\n",p,l,r);
add[p]=0,mi[p]=0,mic[p]=r-l+1;
if(l==r) return;
int mid=(l+r)>>1;
build(p*2,l,mid),build(p*2+1,mid+1,r);
}
void pushdown(int p) {
if(add[p]!=0) pushadd(p*2,add[p]),pushadd(p*2+1,add[p]);
add[p]=0;
}
void update(int p,int l,int r,int x,int y,int v) {
if(x>y) return;
// printf("upd %d,%d,%d,%d,%d,%d\n",p,l,r,x,y,v);
if(x<=l&&r<=y) {
pushadd(p,v);
return;
}
pushdown(p);
int mid=(l+r)>>1;
if(x<=mid) update(p*2,l,mid,x,y,v);
if(y>mid) update(p*2+1,mid+1,r,x,y,v);
pushup(p);
}
int query(int p,int l,int r,int x,int y) {
if(x>y) return 0;
if(x<=l&&r<=y) return mi[p]==0?mic[p]:0;
pushdown(p);int mid=(l+r)>>1,ret=0;
if(x<=mid) ret+=query(p*2,l,mid,x,y);
if(y>mid) ret+=query(p*2+1,mid+1,r,x,y);
return ret;
}
int Min(int p,int l,int r) {
if(l==r) return l;
int mid=(l+r)>>1;pushdown(p);
if(mi[p*2]<0) return Min(p*2,l,mid);
else return Min(p*2+1,mid+1,r);
}
} ES,OS;
ll ans;
int n,a[N+10];
void solve() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int O=n,E=n;ans=0;
if(n&1) E--; else O--;
O=(O-1)/2+1,E/=2;
ES.build(1,1,E+1),OS.build(1,1,O+1);
for(int i=1;i<=10;i++);
for(int i=n;i>=1;i--) {
int f=(i&1?1:-1),Ol,El,Or,Er;
if(i&1) Ol=i/2+1,El=(i+1)/2;
else Ol=(i+1)/2+1,El=i/2;
OS.update(1,1,O+1,Ol,O,a[i]*f);
ES.update(1,1,E+1,El,E,-a[i]*f);
int mi=min(2*OS.Min(1,1,O+1)-1,2*ES.Min(1,1,E+1));
if(mi%2) Er=(mi-1)/2,Or=mi/2;
else Er=mi/2-1,Or=(mi-1)/2+1;
ans+=OS.query(1,1,O+1,Ol,Or);
ans+=ES.query(1,1,E+1,El,Er);
}
printf("%lld\n",ans);
}
CF1584F Strange LCS
给定 \(n\) 个字符串 \(S_i\),保证 \(S_i\) 中每一个字母至多出现两次,\(S_i\) 的字符集是所有英语字母,求出这些字符串的最长公共子串。
\(2\le n\le 10\)。
Solution
设 \(f_{c,S}\) 表示以 \(c\) 结尾的最长公共子串,且这个字符在 \(1\sim n\) 是第几次出现。
转移可以采用刷表法,非常好转移,不妨来考虑转移顺序。
我们将状态记录下来,按照这个字符所在的位置的字典序进行转移,可以得到正确的转移顺序。
时间复杂度 \(O(\Sigma^22^n)\)。
Code
int f[60][(1<<10)+10],L[11][60][2],LC[60][(1<<10)+10],LS[60][(1<<10)+10];
int Len[12];
int n,tot;
char S[12][1000];
int get(char c) {
if('a'<=c&&c<='z') return c-'a';
return c-'A'+26;
}
struct state {
int c,S;
bool operator <(const state &x)const {
for(int i=0;i<n;i++)
if(L[i+1][c][S>>i&1]!=L[i+1][x.c][x.S>>i&1])
return L[i+1][c][S>>i&1]<L[i+1][x.c][x.S>>i&1];
return 0;
}
} p[(1<<10)*60];
char turn(int x) {
if(x<26) return x+'a';
return x-26+'A';
}
void print(int x,int S) {
// printf("print(%d,%d)\n",x,S);
if(x<0||S<0) return;
print(LC[x][S],LS[x][S]);
putchar(turn(x));
}
void solve() {
memset(f,0xc0,sizeof f);memset(L,0,sizeof L);
memset(LC,-1,sizeof LC),memset(LS,-1,sizeof LS);
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%s",S[i]+1);Len[i]=strlen(S[i]+1);
for(int j=1;j<=Len[i];j++) {
int x=get(S[i][j]);L[i][x][1]=j;
if(!L[i][x][0]) L[i][x][0]=j;
}
for(int j=0;j<52;j++) {
if(!L[i][j][0]) L[i][j][0]=Len[i]+1;
if(!L[i][j][1]) L[i][j][1]=Len[i]+1;
// printf("%d %d\n",L[i][j][0],L[i][j][1]);
}
}
tot=0;
for(int i=0;i<52;i++)
for(int S=0;S<(1<<n);S++)
p[++tot]={i,S};
sort(p+1,p+1+tot);
int ans=0,C=-1,SS=-1;
for(int i=1;i<=tot;i++) {
int c=p[i].c,S=p[i].S,fl=1;
// printf("%d %d\n",c,S);
for(int j=0;j<n;j++)
if(L[j+1][c][S>>j&1]>Len[j+1]) fl=0;
if(!fl) continue;
if(f[c][S]<0) f[c][S]=1;
if(ans<f[c][S]) ans=f[C=c][SS=S];
for(int j=0;j<52;j++) {
int SS=0,FL=1;
for(int k=0;k<n&&FL;k++)
if(L[k+1][j][0]<=L[k+1][c][S>>k&1]) {
if(L[k+1][j][1]<=L[k+1][c][S>>k&1]) FL=0;
else SS^=1<<k;
}
if(!FL) continue;
// printf("turn to %d,%d\n",j,SS);
if(f[j][SS]<f[c][S]+1) f[j][SS]=f[c][S]+1,LC[j][SS]=c,LS[j][SS]=S;
}
}
printf("%d\n",ans);print(C,SS);puts("");
}
CF1584G Eligible Segments
给定在平面上的 \(n\) 个点,求出有多少个点对 \((i,j)\),使得对于任意 \(P_k\),满足 \(P_k\) 到线段 \(P_iP_j\) 的距离 \(\le R\)。
\(1\le n\le 3000\)。
Solution
枚举 \(P_i\),\(P_j\),如果要满足 \(P_k\) 到 \(P_iP_j\) 距离不超过 \(d\),要么 \(P_kP_i\le R\),要么 \(\asin(\frac{R}{|P_iP_k|})\),那么我们可以暴力枚举 \(P_i\),对于每个 \(P_k\) 求出其应该在的角度区间。
那么只需要判断角度是不是在这个区间就可以了。时间复杂度 \(O(n^2)\)。
Code
const db pi=acos(-1.0),eps=1e-8;
const int N=3000;
int n;db d,xL,yL,xR,yR;
struct Point {
db x,y;
} P[N+10];
bool f[N+10][N+10];
int ans;
db slope(Point a,Point b) {
return atan2(b.y-a.y,b.x-a.x);
}
db dist(Point a,Point b) {
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
pair<db,db> calc(Point a,Point b) {
db x=slope(a,b);
db y=asin(d/dist(a,b));
return mp(x-y,x+y);
}
int main() {
scanf("%d%lf",&n,&d);
for(int i=1;i<=n;i++) {
scanf("%lf%lf",&P[i].x,&P[i].y);
for(int j=i+1;j<=n;j++) f[i][j]=1;
}
for(int i=1;i<=n;i++) {
xL=yL=-pi-eps,xR=yR=pi+eps;
for(int j=1;j<=n;j++) if(i!=j)
if(dist(P[i],P[j])>d) {
auto now=calc(P[i],P[j]);
if(now.fi>=-pi-eps&&now.se<=pi+eps) {
xL=max(xL,now.fi),yL=max(yL,now.fi);
xR=min(xR,now.se),yR=min(yR,now.se);
}
else if(now.fi<-pi-eps)
xR=min(xR,now.se),yL=max(yL,now.fi+2*pi);
else xR=min(xR,now.se-2*pi),yL=max(yL,now.fi);
}
for(int j=1;j<=n;j++) if(i!=j) {
db tmp=slope(P[i],P[j]);
if((tmp<xL||tmp>xR)&&(tmp<yL||tmp>yR)) f[min(i,j)][max(i,j)]=0;
}
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
ans+=f[i][j];
printf("%d\n",ans);
}
CF1588F Jumping Through the Array
给定一个长度为 \(n\) 序列 \(a\) 与一个大小为 \(n\) 的 \(1\sim n\) 的排列 \(p\),支持如下操作:
- 给定 \(l,r\),求 \(\displaystyle \sum^r_{i=l}a_i\)。
- 给定 \(x,y\),将 \(x\) 所在置换环的全部元素加上 \(y\)。
- 给定 \(x,y\),交换 \(p_x,p_y\)。
\(1\le n\le 2\times 10^5\)。
Solution
将询问分块处理,分成 \(B\) 段后,将 \(2\) 操作涉及到的 \(x\) 与 \(3\) 操作涉及到的 \(x,y\) 标记上黑点。
从一个白点终止到黑点的链必然是整体出现的,而且链少,我们可以把它们总体处理,维护这条链的 \(\Delta\)。
对于 \(1\) 操作,答案也就是原本的 \(\sum^r_{i=l}a_i\) 以及原本的每一条链在 \([l,r]\) 内的节点个数乘上 \(\Delta\),这个可以二分,时间复杂度 \(O(B\log n)\)。
对于 \(2\) 操作,我们可以暴力为每条链打标记,时间复杂度 \(O(B)\)。
对于 \(3\) 操作,直接交换就可以了,时间复杂度 \(O(1)\)。
然后时间复杂度是 \(O(\frac q B\times (B^2\log n+n))\),选择合适的 \(B\),比如 \(500\),即可跑过。
Code
const int B=500,N=2e5;
int n,p[N+10],in[N+10],id[N+10];
ll a[N+10],sum[N+10],dval[N+10];
struct node {
int op,x,y;
} Q[B+10];
vector<int> chain[N+10];
#define ub(a,b) upper_bound(chain[a].begin(),chain[a].end(),b)
void solve(int L,int R) {
for(int i=1;i<=n;i++) {
sum[i]=sum[i-1]+a[i];
chain[i].clear(),chain[i].shrink_to_fit();
}
int tot=0;
memset(dval,0,sizeof dval),memset(in,0,sizeof in);
for(int i=L;i<=R;i++) {++tot;
scanf("%d %d %d",&Q[tot].op,&Q[tot].x,&Q[tot].y);
if(Q[tot].op!=1) in[Q[tot].x]=Q[tot].x;
if(Q[tot].op==3) in[Q[tot].y]=Q[tot].y;
}
int top=0;
for(int i=1;i<=n;i++) {
if(in[i]==i) id[++top]=i;
if(in[i]) continue;
int now=p[i],c;
while(now!=i&&!in[now]) now=p[now];
c=in[now];if(now==i) c=-1;
now=p[i];in[i]=c;
while(now!=i&&!in[now]) in[now]=c,now=p[now];
}
// for(int i=1;i<=n;i++) printf("%d ",in[i]);
// puts("");
for(int i=1;i<=n;i++)
if(in[i]>0) chain[in[i]].pb(i);
for(int i=1;i<=tot;i++) {
if(Q[i].op==1) {
ll ans=sum[Q[i].y]-sum[Q[i].x-1];
for(int j=1;j<=top;j++)
ans+=dval[id[j]]*(ub(id[j],Q[i].y)-ub(id[j],Q[i].x-1));
printf("%lld\n",ans);
}
else if(Q[i].op==2) {
int now=in[Q[i].x];
do {
dval[now]+=Q[i].y;
now=in[p[now]];
} while(now!=in[Q[i].x]);
}
else swap(p[Q[i].x],p[Q[i].y]);
// for(int i=1;i<=top;i++) printf("%lld ",dval[i]);
// puts("");
}
for(int i=1;i<=n;i++)
if(in[i]>0) a[i]+=dval[in[i]];
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
int B=500;
int q;scanf("%d",&q);
for(int L=1;L<=q;L+=B) {
int R=min(q,L+B-1);
solve(L,R);
}
}