ARC 104

A - Plus Minus

题目大意:

  给你\(X+Y,X-Y\),求解\(X\)\(Y\).c++

思路:过水.

B - DNA Sequence

题目大意:

  给你一个长度为\(n\)的串\(S\),询问有多少个子串\(T\)知足其中\(A,T\)数量相等,\(C,G\)数量相等.数组

思路:

  用桶记录\(A-T\),\(C-G\)的数量,而后就没了.dom

核心代码:
n=rd();scanf("%s",S+1);
	int a=0,b=0;Mp[M][M]++;
	LL ans=0;
	for(int i=1; i<=n; ++i) {
		if(S[i]=='A')a++;
		else if(S[i]=='T')a--;
		if(S[i]=='C')b++;
		else if(S[i]=='G')b--;
		ans+=Mp[a+M][b+M];
		Mp[a+M][b+M]++;
	}cout<<ans;

C - Fair Elevator

题目大意:

  给你\(n\)条线段,每条线段左端点为\(A_i\),右端点为\(B_i\).若\(A_i\)或者\(B_i\)\(-1\),则这个点未知,每一个点都不相同,只能取\(1,2..2n\).如今有一个要求就是若是线段相交,那么它们的长度需相等.询问是否存在一种状况知足要求.学习

思路:

  考虑如何判断一个区间【L,R】合法.优化

  • 一:左端点都必须在右端点的左边.(若不知足,要么这个区间能够拆分为两个合法区间,要么就是不合法)
  • 二:相连的左右端点间距离为\((R-L+1)/2\).

  那么咱们就能够用\(dp\)合并两个区间.(竟然没想到)spa

代码:
#include<bits/stdc++.h>
using namespace std;
const int M=405;
int n;
int A[M],B[M];
bool dp[M];
bool vis[M],flag[M];
bool Check(int l,int r) {
	if((r-l+1)&1)return false;
	int L=(r-l+1)/2;
	memset(flag,0,sizeof flag);
	for(int i=1; i<=n; ++i) {
		if(~A[i]&&~B[i]) {
			if(B[i]<l||A[i]>r)continue;
			if(A[i]<l||A[i]>L+l-1)return false;
			if(B[i]<L+l||B[i]>r)return false;
			if(B[i]-A[i]!=L)return false;
		}else if(~A[i]) {
			if(A[i]>r||A[i]<l)continue;
			if(A[i]>L+l-1)return false;
			if(vis[A[i]+L])return false;
			flag[A[i]+L]=true;
		}else if(~B[i]) {
			if(B[i]>r||B[i]<l)continue;
			if(B[i]<L+l)return false;
			if(vis[B[i]-L])return false;
			flag[B[i]-L]=true;
		}
	}
	for(int i=l; i<=r; ++i)
		if(!vis[i]&&!flag[i]) {
			if(i>l+L-1||vis[i+L]||flag[i+L])return false;
			flag[i+L]=true;
		}
	return true;
}
bool f2;
int main(){
	n=rd();
	for(int i=1; i<=n; ++i) {
		A[i]=rd(),B[i]=rd();
		if(~A[i]) {
			if(vis[A[i]]){return puts("No"),0;}
			vis[A[i]]=true;
		}
		if(~B[i]) {
			if(vis[B[i]]){return puts("No"),0;}
			vis[B[i]]=true;
		}
	}dp[0]=true;
	for(int i=1; i<=n<<1; ++i)
		for(int j=1; j<i; ++j) {
			if(!dp[j-1])continue;
			if(Check(j,i)){dp[i]=true;break;}
		}
	if(dp[n+n])puts("Yes");
	else puts("No");
	return 0;
}

D - Multiset Mean

题目大意:

  给你三个数\(n,k,m\),你能够选择\(1...n\)之间的数组成一个可重集,每种数最多有\(k\)个.询问对于每一个\(1\leq x\leq n\),使的这个数集的平均数为\(x\)的方案数.code

思路一:

  首先有一个很直观的思路,就是枚举平均数\(x\),那么数\(i\)对数集的影响就是\(i-x\),咱们就能够作一个小\(dp\),最后的答案就是\(dp[0]\),加一个前缀和优化,这样的复杂度大约\(O(n^4k)\).显然咱们能够来一个最值优化,那么确定跑不满,而后就水过去了.get

核心代码:
n=rd(),k=rd(),P=rd();
	for(int i=1; i<=n; ++i) { 
		int Mxr=0,Mxl=0,R=0;
		bool cur=0;
		dp[0][0]=1;
		for(int j=1; j<i; ++j)Mxl+=(i-j)*k;
		for(int j=n; j; --j) {
			R=min(Mxl,Mxr);
			for(int f=0; f<=R; ++f)dp[cur^1][f]=0;
			if(j==i) {
				for(int f=R; f>=0; --f)dp[cur^1][f]=1LL*dp[cur][f]*(k+1)%P;
			}else if(j>i) {
				for(int f=0; f<=R+(j-i)*k; ++f) {
					sum[f]=dp[cur][f];
					if(f>=j-i)Add(sum[f],sum[f-(j-i)]);
				}
				for(int f=R+(j-i)*k; f>=0; --f)
					if(f>=(j-i)*(k+1))Add(dp[cur^1][f],sum[f]-sum[f-(j-i)*(k+1)]);
					else Add(dp[cur^1][f],sum[f]);
			}else {
				for(int f=R-(j-i)*k; f>=0; --f) {
					sum[f]=dp[cur][f];
					if(f-(j-i)<=R-(j-i)*k)Add(sum[f],sum[f-(j-i)]);
				}
				for(int f=R-(j-i)*k; f>=0; --f) {
					if(f-(j-i)*(k+1)<=R-(j-i)*k)Add(dp[cur^1][f],sum[f]-sum[f-(j-i)*(k+1)]);
					else Add(dp[cur^1][f],sum[f]);
				}
			}
			if(j>=i)Mxr+=(j-i)*k;
			if(j<i)Mxl-=(i-j)*k;
			cur^=1;
		}printf("%d\n",(dp[cur][0]-1+P)%P);
		memset(dp,0,sizeof dp);
	}
思路二:

  (以上代码又丑又慢,请勿学习)咱们采用上面的思路发现,若是计算平均数为\(x\)的答案,那么\(1..n\)的数的贡献变成\(+1,+2,+3,+4,..+n-x\)\(-1,-2,-3,-4,-(x-1)\),这时咱们惊奇地发现竟然是两个前缀.那么天然想到将正负贡献分开来,最后乘一下便可.it

核心代码:
n=rd(),k=rd(),P=rd();
	int R=0;
	int MxR=n*(n+1)/2*k;dp[0][0]=1;
	for(int i=1; i<=n; ++i) {
		R+=k*i;R=min(R,MxR);
		for(int j=0; j<=R; ++j) {
			dp[i][j]=dp[i-1][j];
			if(j>=i)Add(dp[i][j],dp[i][j-i]);	
		}
		for(int j=R; j>=i*(k+1); --j)
			Add(dp[i][j],-dp[i][j-i*(k+1)]);
	}R=0;
	for(int i=1; i<=n; ++i) {
		int res=0;
		for(int j=0; j<=R; ++j) {
			Add(res,1LL*dp[i-1][j]*dp[n-i][j]%P);
		}
		R+=k*i;R=min(R,MxR);
		printf("%d\n",(1LL*res*(k+1)-1)%P);	
	}

E - Random LIS

题目大意:

  给你\(n\)个数\(A_1..A_n\),如今有一个数列\(X\),每一个\(X_i\)\(1..A_i\)间随机生成.询问生成的数列\(X\)的指望\(LIS\)的长度.io

思路:

  看到\(n\leq 6\),直接惧怕,这确定是一个奇奇妙妙的复杂度.我先暴力枚举这\(n\)个数的大小关系,而后咱们就获得一个肯定\(LIS\)的数列,统计知足这个大小关系的数列个数.
  因而我一直在暴力用组合数推式子,结果啥也推不出.(题解:到这一步就是一道经典的分段值域\(dp\)问题[APIO2016]划艇).
  也就是咱们能够将这些范围划分若干段,而后对于在同一范围内的数直接组合数求解.(具体看划艇这道题)
  这道题还有个颇有意思的地方就是暴力枚举前面这些数的大小关系,毕竟有等于的状况比较难搞.看题解的时候看到一个叫贝尔数的东西.(具体看代码)

代码:
#include<bits/stdc++.h>
using namespace std;
const int P=1e9+7;
int n;
int A[10],bel[10];
int rk[10],tot;
int p[10],f[10];
LL fpow(LL a,int cnt) {
	LL res=1;
	for(int i=cnt; i; i>>=1,a=a*a%P)if(i&1)res=res*a%P;
	return res;
}
void Add(int &x,int y) {
	x+=y;(x>=P)&&(x-=P);
}
int Ans;
int s[10],b[10];
int dp[10][10];
LL C(int n,int m) {
	if(n<m)return 0;
	LL res=1;
	for(int i=n; i>n-m; --i)res=res*i%P;
	for(int i=2; i<=m; ++i)res=res*fpow(i,P-2)%P;
	return res;
}
void Solve() {
	for(int i=1; i<=tot; ++i)p[i]=i;	
	do {
		for(int i=1; i<=n; ++i)rk[i]=p[bel[i]];
		int ans=0;
		for(int i=1; i<=n; ++i) {
			f[i]=0;
			for(int j=1; j<i; ++j)
				if(rk[j]<rk[i])Max(f[i],f[j]);
			++f[i];Max(ans,f[i]);
		}memset(s,0,sizeof s);
		for(int i=1; i<=n; ++i) {
			if(!s[rk[i]])s[rk[i]]=A[i];	
			else Min(s[rk[i]],A[i]);
		}b[tot]=s[tot];
		for(int i=tot-1; i; --i)Min(s[i],s[i+1]),b[i]=s[i];
		sort(b+1,b+tot+1);int cnt=unique(b+1,b+tot+1)-b-1;
		for(int i=1; i<=tot; ++i)s[i]=lower_bound(b+1,b+cnt+1,s[i])-b;
		memset(dp,0,sizeof dp);
		for(int i=0; i<=cnt; ++i)dp[0][0]=1;
		for(int i=1; i<=tot; ++i) {
			for(int j=1; j<=s[i]; ++j) {
				int len=b[j]-b[j-1];
				for(int k=i; k; --k)
					if(s[k]>=j)Add(dp[i][j],dp[k-1][j-1]*C(len,i-k+1)%P);
				Add(dp[i][j],dp[i][j-1]);
			}
			for(int j=s[i]+1; j<=cnt; ++j)dp[i][j]=dp[i][j-1];
		}
		Add(Ans,1LL*dp[tot][cnt]*ans%P);
	}while(next_permutation(p+1,p+tot+1));
}
void dfs(int now) {
	if(now==n+1) {Solve();return;}
	for(int i=1; i<=tot; ++i) bel[now]=i,dfs(now+1);
	bel[now]=++tot;dfs(now+1);--tot;
}
int main(){
	n=rd();
	for(int i=1; i<=n; ++i)A[i]=rd();
	dfs(1);
	for(int i=1; i<=n; ++i)Ans=1LL*Ans*fpow(A[i],P-2)%P;
	printf("%d",Ans);
	return 0;
}

F - Visibility Sequence

题目大意:

  给你一个长度为\(n\)的数列\(X\),你能获得一个数列\(A\),知足\(1\leq A_i\leq X_i\).对于每一个\(A\)数列有一个\(P\)数列,\(P_i\)\(i\)左边第一个比\(A_i\)大的数的下标,若没有则为\(-1\).询问有多少种不一样的\(P\)数列.

思路:

  毫无思路,这至关于构建一棵笛卡尔树的过程,因此只要咱们能够统计出有多少种不一样形态的笛卡尔树就能够了.
  考虑区间\(dp\),每次将两个区间和一个点合并,至关于建树.因为咱们须要保证这是一个大根堆,咱们还须要一维记录当前这个区间最大值不超过\(x\),看一下数据发现\(x\)有点大,可是咱们其实能够将它离散掉,那么复杂度为\(O(n^4)\).

代码:
#include<bits/stdc++.h>
using namespace std;
const int P=1e9+7;
void Add(int &x,int y) {
	x+=y;(x>=P)&&(x-=P);
}
int n;
int A[105];
int f[105][105][105];
int main(){
	n=rd();
	for(int i=1; i<=n; ++i) {
		A[i]=rd(),Min(A[i],n+1);
		for(int j=1; j<=n+1; ++j)f[i][i][j]=1;
	}
	for(int l=n; l; --l)
		for(int r=l+1; r<=n; ++r)
			for(int mid=l; mid<=r; ++mid)
				for(int k=1; k<=n+1; ++k) {
					int Mx=A[mid]>=k?k:A[mid];
					int res=1;
					if(l^mid)res=1LL*res*f[l][mid-1][Mx]%P;
					if(r^mid)res=1LL*res*f[mid+1][r][Mx==n+1?Mx:Mx-1]%P;
					Add(f[l][r][k],res);
				}
	printf("%d\n",f[1][n][n+1]);
	return 0;
}
相关文章
相关标签/搜索