1. 程式人生 > 其它 >Codeforces Round #755 部分題解

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\),要麼 \(\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\),支援如下操作:

  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);
	}
}