• 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);
    	}
    }
    
  • 相关阅读:
    支持向量机(SVM)相关免费学习视频集锦
    《量化投资:以MATLAB为工具》连载(2)基础篇-N分钟学会MATLAB(中)
    《量化投资:以MATLAB为工具》连载(1)基础篇-N分钟学会MATLAB(上)
    OpenCV 轮廓基本特征
    !!破解灯塔线取点与画线的“难点”
    理工科应该的知道的C/C++数学计算库(转)
    521. 最长特殊序列 Ⅰ
    520. 检测大写字母
    459. 重复的子字符串
    443. 压缩字符串
  • 原文地址:https://www.cnblogs.com/cnyzz/p/15569313.html
Copyright © 2020-2023  润新知