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;
}
相關文章
相關標籤/搜索