#动态规划,组合计数,树状数组,前缀和#F 简单计数题&K 最简单的题

先膜两位出题人


F 简单计数题

题目

\(n\)个活动,预约期有\(k\)天,第\(j\)天YC可以获得\(a_j(1\leq a_j\leq n)\)张预约券,
他会在\(n\)个活动中等概率选择\(a_k\)个活动消耗预约券,
\(k\)天后,YC同时参加\(n\)个活动的信心就提高了\(\prod_{i=1}^nw_i\)\(w_i\)表示第\(i\)个活动消耗的预约券个数
问他能同时参加\(n\)个活动的信心的期望是多少,为了方便统计,答案乘上\(\prod_{i=1}^nC(n,a_i)\)并对\(10^9+7\)取模


分析

首先乘上最后那一坨就是将期望转换成了贡献和,因为那一坨就是方案数。
接着,贡献就是从\(w_{1\sim n}\)中各抽取1个的方案(乘法原理)
考虑拎出\(n\)个出来,如果有一种方案第\(j\)天的预约券中选择了\(p_j\)张,且\(n=\sum_{i=1}^kp_i\)
那么这种方案的贡献就是\(\prod_{i=1}^kC(n-p_i,a_i-p_i)\)
因为已经保证抽出来了,那么剩下的预约券可以随便分配
那么就可以列\(dp\)方程,设\(dp[i][j]\)表示前\(i\)天预约券共拎出\(j\)张的贡献,那么

\[dp[i][j+k]=\sum dp[i-1][j]*C(j+k,k)*C(n-k,a_i-k) \]

其中\(dp[0][0]=1\)


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int mod=1000000007,N=1011;
int dp[21][N],c[N][N],n,k,a[N];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
inline signed mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
signed main(){
    n=iut(),k=iut(),c[0][0]=dp[0][0]=1;
    for (rr int i=1;i<=n;++i){
    	c[i][0]=c[i][i]=1;
    	for (rr int j=1;j<i;++j)
    	     c[i][j]=mo(c[i-1][j-1],c[i-1][j]);
	}
	for (rr int i=1;i<=k;++i) a[i]=iut();
	for (rr int i=1;i<=k;++i)
	for (rr int j=0;j<=n;++j) if (dp[i-1][j])
	for (rr int p=0;p<=n;++p){
		if (j+p>n||p>a[i]) break;
		rr int t=1ll*c[j+p][p]*c[n-p][a[i]-p]%mod;
		dp[i][j+p]=mo(dp[i][j+p],1ll*t*dp[i-1][j]%mod);
	}
	return !printf("%d",dp[k][n]);
}

K 最简单的题

题目

给出两个长度为\(n\)的数列\(a,b\)
\(f(i,j)=LIS(a_i,b_i,a_j,b_j)\)
\(f(i,j)=1\sim 4\)的个数
\(1\leq n\leq 10^5,1\leq a,b\leq m\leq 10^3\)


分析

枚举\(j\),然后分类讨论,标绿为所在位置





首先一共5种情况,考虑它们的共同规律,当\(a_j<b_j\)时,右边罕见的出现平的分界线
其它情况都是要将纵轴顺时针旋转45度,实际上是逆时针。
被BPM大爷嘲讽后恶补高中数学,学完后却发现我在想桃子
还不如直接说原\((x,y)\)对应\((x-y,y)\),当然要平移横坐标,加上\(m\)
如下图\(m=15\)的情况

讲到这里好像还真是一道简单题
一开始我想的是用树状数组维护,但是BPM连\(STL::sort\)也没放过,
多组数据直接TLE(幸好O2卡过去了)
然后BPM就让我用前缀和做,其实思路差不多,
这里也不细讲了,反正代码很清晰


树状数组代码

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define rr register
using namespace std;
const int N=100011;
int n,m,Cnt,c[N]; long long dp[5];
struct rec{int x,y;}a[N],b[N<<1];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
bool cmp1(rec a,rec b){return a.y>b.y;}
inline signed query(int x){rr int ans=0; for (;x;x-=-x&x) ans+=c[x]; return ans;}
inline void update(int x,int y){for (;x<=m;x+=-x&x) c[x]+=y;}
signed main(){
	while (scanf("%d%d",&n,&m)==2){
		dp[1]=dp[2]=dp[3]=dp[4]=0;
		for (rr int i=1;i<=n;++i) a[i]=(rec){iut(),iut()};
		for (rr int i=1;i<=n;++i) if (a[i].x<a[i].y)
		    b[++Cnt]=(rec){a[i].x,a[i].y-(a[i].x+1==a[i].y)};
		sort(b+1,b+1+Cnt,cmp1),sort(a+1,a+1+n,cmp1);
		for (rr int i=1,j=1;i<=Cnt;++i){
			while (j<=n&&a[j].y>=b[i].y) update(a[j].x,1),++j;
			rr int t=query(b[i].x-1); dp[3]+=t,dp[2]+=j-t-1;
		}
		Cnt=0,memset(c,0,sizeof(c));
		for (rr int i=1;i<=n;++i) b[i]=(rec){a[i].x>=a[i].y,a[i].x}; Cnt=n;
		for (rr int i=1;i<=n;++i) if (a[i].x<a[i].y) b[++Cnt]=(rec){2,a[i].y-(a[i].x+1==a[i].y)};
		sort(b+1,b+1+Cnt,cmp1); for (rr int i=1;i<=n;++i) a[i].x-=a[i].y-m;
		for (rr int i=1,j=1,t=0;i<=Cnt;++i){
			while (j<=n&&a[j].y>=b[i].y) t+=a[j].x<m,++j;
			rr int t2=(b[i].x==2)?-1:1,t1=b[i].x!=1;
			dp[2+t1]+=t*t2,dp[1+t1]+=(j-t-1)*t2; 
		}
		reverse(a+1,a+1+n),Cnt=0,memset(c,0,sizeof(c));
		for (rr int i=1;i<=n;++i) b[i].y=a[i].x-m+a[i].y;
		for (rr int i=1;i<=n;++i) b[i].x=a[i].x<m;
		sort(b+1,b+1+n,cmp1),reverse(b+1,b+1+n);
		for (rr int i=1,j=1,t=0;i<=n;++i){
			while (j<=n&&a[j].y<b[i].y) t+=a[j].x<m,++j;
			dp[3+b[i].x]+=t,dp[2+b[i].x]+=j-t-1;
		}
		printf("%lld %lld %lld %lld\n",dp[1],dp[2],dp[3],dp[4]);
		memset(c,0,sizeof(c));
	}
	return 0;
} 

前缀和代码

#include <cstdio>
#include <cctype>
#include <cstring>
#define rr register
using namespace std;
const int N=100011,M=1011;
int n,m,Cnt,K,s[M<<1][M],x[N],y[N];
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
signed main(){
	while (scanf("%d%d",&n,&m)==2){
		rr long long dp[5]={0,0,0,0,0};
		memset(s,0,sizeof(s)),K=m<<1;
		for (rr int i=1;i<=n;++i)
		    x[i]=iut(),y[i]=iut(),++s[x[i]][y[i]];
		for (rr int i=1;i<=m;++i)
		    for (rr int j=1;j<=m;++j)
		        s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		for (rr int i=1;i<=n;++i)
		if (x[i]<y[i]){
			rr int px=x[i],py=y[i]-(x[i]+1==y[i]);
			dp[3]+=s[px-1][m]-s[px-1][py-1];
			dp[2]+=s[m][m]-s[px-1][m]-s[m][py-1]+s[px-1][py-1];
		}
		for (rr int i=1;i<=m;++i)
		    for (rr int j=1;j<=m;++j) s[i][j]=0;
		for (rr int i=1;i<=n;++i) x[i]-=y[i]-m,++s[x[i]][y[i]];
		for (rr int i=1;i<=K;++i)
		    for (rr int j=1;j<=m;++j)
		        s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
		for (rr int i=1;i<=n;++i){
			rr int T=x[i]<m,F=x[i]+y[i]-m,G=y[i]-(F+1==y[i]);
			rr int TEMP=s[K][m]-s[m-1][m]-s[K][F-1]+s[m-1][F-1];
			dp[3+T]+=s[m-1][F-1],dp[1+T]+=TEMP;
			dp[2+T]+=s[K][m]-TEMP-s[m-1][F-1];
			if (!T) continue;
			dp[2]-=s[K][m]-s[m-1][m]-s[K][G-1]+s[m-1][G-1];
			dp[3]-=s[m-1][m]-s[m-1][G-1];
		}
		printf("%lld %lld %lld %lld\n",dp[1],dp[2],dp[3],dp[4]);
	}
	return 0;
} 

吐槽和膜拜时间

两位出题人出的题都是码题难度不高,思维难度不低的题目
其中K题所花的改题时间更多了那么一两个月233
吐槽树状数组开了O2比前缀和不开O2还要慢
主要聊一聊K题,历程大概是这样的
两个月,什么也不会,干聊纯属浪费时间;
一个月,学习高中数学;一个月,改完K题
其中大概只有一个月时间是没有被浪费掉的
其实我一开始觉得题目超级难,主要是想起了不堪回首的记忆
但其实说难不难,BPM没有一味地去帮助我,而是让我自己去思考,
虽然有点不情愿,但是我觉得很有道理,
想起考完\(CSP-S\)之后\(WYC\)也给我提出了一些建议
好吧,不管怎么说一模加油,不要丢脸
upd:20200410

posted @ 2020-03-18 21:42  lemondinosaur  阅读(323)  评论(0编辑  收藏  举报