Codeforces Round #755 部分题解

Announcement 链接

某些题过水没有加入。

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\),要么 \(\angle P_jP_iP_k \le \arcsin(\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\),支持如下操作:

  1. 给定 \(l,r\),求 \(\displaystyle \sum^r_{i=l}a_i\)
  2. 给定 \(x,y\),将 \(x\) 所在置换环的全部元素加上 \(y\)
  3. 给定 \(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);
	}
}
posted @ 2021-11-17 20:19  cnyz  阅读(199)  评论(0编辑  收藏  举报