2019-11-2 CSP-S模拟测

T1(exgcd/ksm求乘法逆元)

在这里插入图片描述
思路:
看到 x y z   m o d   p   = 1 x*y*z\ mod\ p\ =1 ,长得就像 a x 1   ( m o d   p ) ax \equiv 1\ (mod\ p) ,于是联想到乘法逆元
一看数据范围:

对于所有测试点,保证 3 n 2 ; 333 3\le n\le 2;333 0 A i , P 2 30 0\le A_i,P<2^{30} ,P为质数

暴力枚举肯定是不现实,于是考虑只计算前两项,用前两项推出第三项
代码:
分情况讨论:

  1. c n t [ 1 ]   >   x ! = y ! = z cnt[1]\ ->\ x!=y!=z
  2. c n t [ 2 ]   >   x = = y ! = z cnt[2]\ ->\ x==y!=z
  3. c n t [ 3   >   x = = y = = z cnt[3\ ->\ x==y==z

将当前的 a a 数组手动离散化存在 n u m s nums 这个结构体内,在离散化处理的同时计算出逆元
然后利用简单组合数学(手玩几组数据)求出当前状态的所有方案数,累加即可

#include <bits/stdc++.h>
using namespace std;
const int N=3000;
int P;
int n,m;
int a[N];
struct node{
	int val,inv;
	int cnt[4]={0,0,0,0};
}nums[N];
int find(int key){
	int l=-1,r=m;
	int mid;
	while(l+1<r){
		mid=(l+r)>>1;
		if(nums[mid].val<key){
			l=mid;
		}
		else	r=mid;
	}
	if(r<m&&nums[r].val==key)	return r;
	return -1;
}
int qpow(int a,int b){
	int ans=1%P;
	for(;b;b>>=1){
		if(b&1)ans=(long long)ans*a%P;
		a=(long long)a*a%P;
	}
	return ans;
}
bool cmp_val(const node &a,const node &b){
	return a.val<b.val;
}
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
} 
int main(){
	n=read(),P=read();
	for(int i=0;i<n;i++){a[i]=read();}sort(a,a+n);
	int stamp=0,tmp_cnt=1;
	for(int i=1;i<=n;i++){
		if(i==n||(i>0&&a[i]!=a[i-1])){
			int x=a[i-1]%P,j=0;
			if(x>0){
				while(j<stamp&&nums[j].val!=x){++j;}
				if(j==stamp){stamp++;nums[j].val=x;nums[j].inv=qpow(x,P-2);}
				++nums[j].cnt[min(3,tmp_cnt)];
			}
			tmp_cnt=1;
		}
		else ++tmp_cnt;
	}
	m=stamp;
	for(int i=0;i<m;i++){
		nums[i].cnt[2]+=nums[i].cnt[3];
		nums[i].cnt[1]+=nums[i].cnt[2];
	}
	sort(nums,nums+m,cmp_val);
	int ans=0;
	for(int i=0;i<m;i++){
		for(int j=i;j<m;j++){
			int z=1LL*nums[i].inv*nums[j].inv%P;
			if(i!=j&&(z<=nums[i].val||z<=nums[j].val))	continue;
			int k=find(z);
			if(k==-1)	continue;
			int tmp=0;
			if(nums[i].val!=nums[j].val){
				tmp+=nums[i].cnt[1]*nums[j].cnt[1]*nums[k].cnt[1];
			}
			else{
				if(nums[i].val!=nums[k].val){
					tmp+=nums[i].cnt[1]*(nums[i].cnt[1]-1)/2*nums[k].cnt[1];
					tmp+=nums[i].cnt[2]*nums[k].cnt[1];
				}
				else{
					tmp+=nums[i].cnt[3];
					tmp+=nums[i].cnt[2]*(nums[i].cnt[1]-1);
					tmp+=1LL*nums[i].cnt[1]*(nums[i].cnt[1]-1)*(nums[i].cnt[1]-2)/6;
				}
			}
			ans+=tmp;
		}
	}
	printf("%d",ans);
	return 0;
}

T2(有限制的二维偏序问题)

在这里插入图片描述
此处的树状数组可以理解为权值线段树,考虑最短链长度的选取即可
内网题解

本题与传统的二维偏序问题类似,只需要仔细考虑维度、顺序问题。
对于一条长度为 l l 的链,贪心地,我们发现用容量最大的 l l 个背包一定是最优的背包安排。
因此将所有点按 x x 降序排序,并依次枚举所有点,用权值树状数组维护每个 y y 坐标结尾的最长链长度,注意需要维护能够存的下当前点背包数量,并在更新树状数组时将更新的答案与这个值取 min。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
int T,b[N],t[N],n,m,siz=0;
struct BIT{
	int c[N];
	int lowbit(int x){return x&(-x);}
	void clear(){memset(c,0,sizeof(c));}
	void add(int x,int v){for(;x;x-=lowbit(x))	c[x]=max(c[x],v);}
	int query(int x){int ans=0;for(;x<=siz;x+=lowbit(x))	ans=max(ans,c[x]);return ans;}
}bit;
inline int read(){
	int cnt=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
	while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
	return cnt*f;
}
struct node{int w,v;}a[N];
bool cmp(const node &a,const node &b){if(a.w!=b.w)return a.w<b.w;return a.v<b.v;}
void solve(){
	bit.clear(),siz=0;
	n=read();
	for(int i=1;i<=n;i++){a[i].w=read(),a[i].v=read();b[++siz]=a[i].v;}
	sort(a+1,a+1+n,cmp);reverse(a+1,a+1+n);
	sort(b+1,b+1+siz);siz=unique(b+1,b+1+siz)-(b+1);
	m=read();
	for(int i=1;i<=m;i++){t[i]=read();}sort(t+1,t+1+m);reverse(t+1,t+1+m);
	int ans=0,p=0;
	for(int i=1;i<=n;i++){
		while(p+1<=m&&t[p+1]>=a[i].w)++p;
		int x=lower_bound(b+1,b+siz+1,a[i].v)-b;
		int f=min(bit.query(x)+1,p);
		bit.add(x,f),ans=max(ans,f);
	}
	printf("%d\n",ans);
}
int main(){
	T=read();
	while(T--)	solve();
	return 0;
}

T3(动态规划)

在这里插入图片描述
在这里插入图片描述
状态转移方程得出:
在这里插入图片描述
即:原子树内部的组合方式 * 分出去次大编号子树的组合方式 * 剩余子树的组合方式
代码不是我的,我只是个蒟蒻罢了

#include <bits/stdc++.h>

using namespace std;

const int P=998244353;
const int N=500+10;
int c[N][N];

int n;

int dp[N][N];
bool ban[N];

int main(){
    int k;
    scanf("%d%d\n",&n,&k);
    memset(c,0,sizeof(c));
    for (int i=0;i<=n;++i){
        c[i][0]=1;
        for (int j=1;j<=i;++j)
            c[i][j]=(c[i-1][j-1]+c[i-1][j])%P;
    }
    memset(ban,0,sizeof(ban));
    for (int i=1;i<=k;++i){
        int x;
        scanf("%d",&x);
        ban[x]=1;
    }
    
    memset(dp,0,sizeof(dp));
    dp[1][1]=ban[1]?0:1;
    for (int d=2;d<=n;++d){
        for (int i=1;i<=n;++i){
            if (i==1){
                dp[i][d]=1;
                continue;
            }
            for (int j=1;j<i;++j){
                dp[i][d]=(dp[i][d]+1LL*dp[i-j][d]*dp[j][d-1]%P*c[i-2][j-1])%P;
            }
        }
        for (int i=1;i<=n;++i)
            if (ban[i])
                dp[i][d]=0;
    }
    int L,R;
    scanf("%d%d",&L,&R);
    for (int i=L;i<=R;++i){
        printf("%d",(dp[n][i]-dp[n][i-1]+P)%P);
        if (i!=R) putchar(' ');
    }
    puts("");
    return 0;
}
发布了37 篇原创文章 · 获赞 11 · 访问量 1932

猜你喜欢

转载自blog.csdn.net/weixin_42750325/article/details/102943448
今日推荐