noip 2018 模拟赛11

T 1 T_1 T1——binary(3093)

Description:

对于一个二叉树,我们知道若根的编号为 x x x,则它的左儿子编号为 2 ⋅ x 2\cdot x 2x,右儿子编号为 2 ⋅ x + 1 2\cdot x+1 2x+1
现在有一串字符,包含 ′ P ′ , ′ L ′ , ′ R ′ , ′ ∗ ′ 'P','L','R','*' P,L,R,,表示从编号 1 1 1开始走, ′ P ′ 'P' P表示原地不动, ′ L ′ 'L' L表示走到它的左儿子, ′ R ′ 'R' R表示走到它的右儿子, ′ ∗ ′ '*' 表示3种情况都有可能。
求最后所有的可能值的和。
∣ S ∣ ≤ 1 0 5 |S|\le 10^5 S105
Solution:

  • 转移比较明显,简单说一个对于 ′ ∗ ′ '*' 的情况,即是将3种情况再来转移一次罢了。
  • 但是这里并没有模数,对于 ∣ S ∣ ≤ 1 0 5 |S|\le 10^5 S105,可能值的个数 3 ∣ S ∣ 3^{|S|} 3S显然会炸,所以还要一个高精即可。

Code:

#include
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define DREP(i,f,t) for(int i=(f),i##_end_=(t);i>=i##_end_;--i)
#define ll long long
templateinline void Rd(T &x){
	x=0;char c;
	while((c=getchar())<48);
	do x=(x<<1)+(x<<3)+(c^48);
	while((c=getchar())>47);
}

const int N=10002;

int n;
char s[N];

const int Q=1000000;
struct Big{
	int num[1002],len;
	Big(){
		memset(num,0,sizeof(num));
		len=1;
	}
	void Pr(){
		if(!num[len-1]){puts("0");return;}
		printf("%d",num[len-1]);
		DREP(i,len-2,0)printf("%06d",num[i]);		
	}
	Big operator +(const Big &a)const{
		Big b;
		b.len=max(len,a.len);
		REP(i,0,b.len-1){
			int &B=b.num[i];
			B+=num[i]+a.num[i];
			if(B>=Q)B-=Q,b.num[i+1]++;
		} 
		if(b.num[b.len])b.len++;
		return b;
	}
	Big operator+(int a){
		Big b;
		b.len=len;
		REP(i,0,len-1){
			int &B=b.num[i];
			B+=num[i]+a%Q;
			if(B>=Q)b.num[i+1]++,B-=Q;
			a/=Q;
		}
		if(b.num[b.len])b.len++;
		return b;
	}
	Big operator *(const int &a)const{
		Big b;
		b.len=len;
		REP(i,0,len-1){
			int &B=b.num[i];
			B+=num[i]*a;
			if(B>=Q)b.num[i+1]+=B/Q,B%=Q;
		}
		if(b.num[b.len])b.len++;
		return b;
	}
	Big operator *(const Big &a)const{
		Big b;
		b.len=a.len+len-1;
		REP(i,0,len-1){
			REP(j,0,a.len-1){
				int &B=b.num[i+j];
				B+=num[i]*a.num[j];
				if(B>=Q)b.num[i+j+1]+=B/Q,B%=Q;
			} 
		} 
		while(b.num[b.len])b.len++;
		return b;
	}
	
	void clear(){
		memset(num,0,sizeof(num));
		len=1;
	}
	
}L,R,P,sum,ans,tmp;
int main(){
//	freopen("binary.in","r",stdin);
//	freopen("binary.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1);

	sum=sum+1;
	if(s[1]=='P')P=P+1;
	if(s[1]=='L')L=L+2;
	if(s[1]=='R')R=R+3;
	if(s[1]=='*'){
		P=P+1;
		L=L+2;
		R=R+3;
		sum=sum*3;
	}
	REP(i,2,n){
		tmp=P+L+R;
		if(s[i]=='P') {
			P=tmp;
			L.clear();
			R.clear();
		}
		if(s[i]=='L') {
			L=tmp*2;
			P.clear();
			R.clear(); 
		}
		if(s[i]=='R') {
			R=tmp*2;
			R=R+sum;
			P.clear();
			L.clear();
		}
		if(s[i]=='*') {
			P=tmp;
			L=tmp*2;
			R=tmp*2;
			R=R+sum;
			sum=sum*3;
		}
	}
	
	ans=L+R+P;
	ans.Pr();	
	return 0;
}

T 2 T_2 T2——string(3058)

Description:

有两个字符串 s , w s,w s,w,现在有一些操作。
1.从 s s s中删去任意一个字符,花费为 a a a
2.在 s s s中任意位置插入一个任意字符,花费为 a a a
3.将 s s s中的某个字符替换成其它字符,花费为 b b b
求是否在费用不超过 k k k的情况下,使 s s s变成 w w w
注意 ∣ s ∣ 可 能 不 等 于 ∣ w ∣ |s|可能不等于|w| sw
∣ s ∣ , ∣ w ∣ ≤ 1 0 5 , a , b , k ≤ 100 |s|,|w|\le 10^5,a,b,k\le100 s,w105,a,b,k100

Solution:

  • 首先,有一个朴素的 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 s s s的前 i i i位和 w w w的前 j j j位匹配的最小代价。
  • 那么转移就有:
  • 如果删去一个字符,可以从 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]转移;
  • 如果加入一个字符,可以从 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]转移;
  • 如果换一个字符(或者本来就可以匹配),可以从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]转移。
  • 这样的复杂度为 O ( ∣ s ∣ × ∣ w ∣ ) O(|s|\times |w|) O(s×w)
  • 注意到 k k k很小,但是如果把 k k k记录到状态里,又会漏情况(比如,记录 d p [ i ] [ j ] dp[i][j] dp[i][j] s s s i i i个字符,花费 j j j代价,在 w w w里最多匹配多少。这样的状态是不对的,如aba到aa,应该删掉b,可是DP中会让b换成a,从而多匹配一个)
  • 还有先把 a a a b b b等于0的情况判掉,然后会发现,由于只能花费 k k k代价,那么每次匹配的位置 j j j必然在 [ i − k , i + k ] [i-k,i+k] [ik,i+k]里。于是第二维只用循环 2 k 2k 2k次即可。
  • 可以记 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个和前 i + j − 100 i+j-100 i+j100个匹配的代价。
  • O ( ∣ s ∣ × k ) O(|s|\times k) O(s×k)

Code:

#include
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)

const int N=1e5+2;

int n,m;
char S[N],W[N],tmp[N];
int A,B,K;

int dp[N][2];

int main(){
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	scanf("%s%s%d%d%d",S,W,&A,&B,&K);
	n=strlen(S),m=strlen(W);
	
	if(!A){
		puts("0");
		return 0;
	}
	else if(!B){
		printf("%d\n",A*abs(n-m));
		return 0;
	}
	else if(abs(n-m)>K){
		puts("-1");
		return 0;
	}
	if(n>m){
		strcpy(tmp,S);
		strcpy(S,W);
		strcpy(W,tmp);
		swap(n,m);
	}
	if(2*Am2)++L;
		REP(i,L,R){
			if(S[i-1]==W[j-1]) dp[i][1]=dp[i-1][0];
			else {
				dp[i][1]=dp[i-1][0]+B;
				if((j==1 or R==n or i!=R) and dp[i][1]>dp[i][0]+A) dp[i][1]=dp[i][0]+A;
				if((j<=m2 or i!=L) and dp[i][1]>dp[i-1][1]+A) dp[i][1]=dp[i-1][1]+A;
			}
		}
		REP(i,L,R) dp[i][0]=dp[i][1];
		if(RK?-1:dp[n][1]);	
	return 0;
}

T 3 T_3 T3——bus(3054)

Description:

一棵树,有 m m m条公交路线和 q q q个回家路线。
对于每个回家路线,求最少要转乘几次公交才能到家。
n , m , q ≤ 1 0 5 n,m,q\le 10^5 n,m,q105

Solution:

  • 比较裸的数据结构题(码农题)
  • 首先,对于一条链的情况,我们是可以贪心地,就是说对于一条公交路线,我们能不转乘就不转。
  • 那么在树上,对于每个回家路线 x , y x,y x,y,先考虑折路径的情况,我们可以求出 x x x l c a lca lca的最远转乘到哪以及乘坐次数,同理也可以求出 y y y l c a lca lca的。
  • 这就是经典的二维偏序问题了,也就是二维数点。
  • 查询是否有线路从一个端点的子树 d f s dfs dfs序区间里,到另一个端点的 d f s dfs dfs序区间里。
  • 如果有,那么就可以直接走这条线路,否则要在 l c a lca lca处换一条线路。
  • 排序+ B I T BIT BIT维护一下即可。

Code:

#include
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define SREP(i,f,t) for(int i=(f),i##_end_=(t);i=i##_end_;--i)
templateinline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
templateinline bool chkmax(T &x,T y){return xinline void Rd(T &x){
	x=0;char c;
	while((c=getchar())<48);
	do x=(x<<1)+(x<<3)+(c^48);
	while((c=getchar())>47);
}
const int INF=0x3f3f3f3f;
const int N=100005;

int n,m,q;

int qwq,head[N];
struct edge{
	int to,nxt;
}E[N<<1];
void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++;}
#define EREP(x) for(int i=head[x];~i;i=E[i].nxt)

struct p40{
	#define NN 1002 
	int dp[NN][NN],fa[NN],dep[NN],lstk[NN],rstk[NN],Line[NN];
	bool mark[NN][NN];
	int len;

	void dfs(int x,int f){
		EREP(x){
			int y=E[i].to;
			if(y==f) continue;
			dep[y]=dep[x]+1;
			fa[y]=x;
			dfs(y,x);
		}
	}

	void Get(int s,int t){
		int x=0,y=0;
		if(dep[s]dep[Fa[y]])Fa[x]=Fa[y];
		}
		G[x][0]=Fa[x];
	}
	
	int Jump(int &x,int y){
		int res=0;
		DREP(i,S-1,0) if(dep[G[x][i]]>dep[y]) x=G[x][i],res|=(1<dep[y])return -0x3f3f3f3f;
		else return res;
	}
	
	void solve(){
		dfs1(1,0),dfs2(1,1);
		SREP(j,1,S) REP(i,1,n) F[i][j]=F[F[i-1][j-1]][j-1];
		
		REP(i,1,n) Fa[i]=i;
		while(m--){
			int s,t;
			Rd(s),Rd(t);
			int lca=Lca(s,t);
			if(dep[Fa[s]]>dep[lca]) Fa[s]=lca;
			if(dep[Fa[t]]>dep[lca]) Fa[t]=lca;
			Q[++tot]=(node){Lt[s],Lt[t],-1,0};
			Q[++tot]=(node){Lt[t],Lt[s],-1,0};
		}
		
		dfs3(1,0);
		SREP(j,1,S) REP(i,1,n) G[i][j]=G[G[i][j-1]][j-1];
		
		REP(i,1,q){
			int x,y;
			Rd(x),Rd(y);
			int lca=Lca(x,y);
			ans[i]=Jump(x,lca)+Jump(y,lca);
			if(ans[i]<0) ans[i]=-1;
			else if(x!=lca && y!=lca){//Sum(Lt[x],Lt[y],Rt[x],Rt[y])=sum(Rt[x],Rt[y])-sum(Lt[x]-1,Rt[y])-sum(Rt[x],Lt[y]-1)+sum(Lt[x]-1,Lt[y]-1)
				ans[i]++;
				Q[++tot]=(node){Rt[x],Rt[y],1,i};
				Q[++tot]=(node){Lt[x]-1,Rt[y],-1,i};
				Q[++tot]=(node){Rt[x],Lt[y]-1,-1,i};
				Q[++tot]=(node){Lt[x]-1,Lt[y]-1,1,i};
			}
		}
		sort(Q+1,Q+1+tot);
		
		REP(i,1,tot){
			if(!Q[i].id) T.add(Q[i].x);
			else mark[Q[i].id]+=Q[i].d*T.query(Q[i].x);
		}
		REP(i,1,q){
			if(~ans[i] and mark[i]>0) ans[i]--;
			printf("%d\n",ans[i]);
		}
	}
	
}p3;


int main(){
//	freopen("bus.in","r",stdin);
//	freopen("bus.out","w",stdout);
	Rd(n),Rd(m),Rd(q);
	memset(head,-1,sizeof(head));
	SREP(i,1,n){
		int a,b;
		Rd(a),Rd(b); 
		addedge(a,b);
		addedge(b,a);
	}
	
//	if(n<=1000 and m<=1000) p1.solve();
//	else 
	p3.solve();
	
	return 0;
}

你可能感兴趣的:(离线赛-总结)