9月22模拟赛

A. 【雅礼2019.10.25 pm T1】极好的问题

题目

描述

\(Yazid\) 有一个序列 \(A_i(1≤i≤n)\) 和一个质数 \(P\)
对于一个三元组 \((x,y,z)\),如果满足 \(x≤y≤z\)\(x×y×z\) \(mod\) \(P=1\)
且存在三个互不相同的下标 \(i,j,k\) 使得 \(A_i=x,A_j=y,A_k=z\) 那么我们就说这个三元组是极好的。

现在,请你求出本质不同的极好的三元组的数目。两个三元组 \((x1,y1,z1),(x2,y2,z2)\) 被认为是本质不同的,当且仅当 \(x1≠x2\)\(y1≠y2\)\(z1≠z2\)

如果你能自如地运用你学习的算法解决这个极好的问题,那么你就会获得“Wow”的赞美,这将是你独享的时刻。

输入格式

第一行 \(2\) 个用空格隔开的整数 \(n,P\)

第二行 \(n\) 个用空格隔开的整数 \(A_1,...,A_n\)

输出格式

输出一行一个整数,表示极好的三元组的数目。

思路

首先根据题目要求,对给出的 \(A\) 序列进行去重计数操作。并算出每种数的乘法逆元。

\(\mathfrak{talk\; is\; easy\; show\; me\; the\; code}\)

inline void pre(){
	sort(a+1,a+1+n);b[1]=a[1];cnt=1;s[1]=1;
	for(int i=2;i<=n;i++){
		if(a[i]!=a[i-1]) b[++cnt]=a[i],s[cnt]=1;
		else s[cnt]++;
	}
	for(int i=1;i<=cnt;i++) inv[i]=qpow(b[i],P-2);	
}

极好的三元组有以下三种情况:

  • \(a\times a\times a\)

  • \(a\times a\times b\)

  • \(a\times b\times c\)

对于第一种情况,可以统计每个出现次数大于等于 \(3\) 的数的三次方 (在 \(mod\) \(P\) 意义下) 是否等于 \(1\)

对于第二种情况,统计每个出现次数大于等于 \(2\) 的数的二次方乘去重序列中的其它数 (在 \(mod\) \(P\) 意义下) 是否等于 \(1\)

\(\mathfrak{talk\; is\; easy\; show\; me\; the\; code}\)

for(int i=1;i<=cnt;i++){
	if(s[i]>=3)
		if(b[i]*b[i]%P*b[i]%P==1) ans++;		
	if(s[i]>=2)
		for(int j=1;j<=cnt;j++){
			if(j==i) continue;
			if(b[i]*b[i]%P*b[j]%P==1)ans++;				 
		}
}

对于第三种情况,预处理出所有两个不同的数的乘积,并在预处理中特判 \(a\times b=a'\)\(a\times b=b'\) 的情况。因为此时等价于情况二,不特判会导致重复计算。

\(\mathfrak{talk\; is\; easy\; show\; me\; the\; code}\)

for(int i=1;i<=cnt;i++)
	for(int j=i+1;j<=cnt;j++) {
		cj[++tot]=b[i]*b[j]%P;		
		if(cj[tot]==inv[i]) q++;
		if(cj[tot]==inv[j]) q++;
	}
sort(cj+1,cj+1+tot);	

接下来利用upper_boundlower_bound 作差得出每个数的乘法逆元在这些乘积中出现的次数。这些次数之和会反复出现三次如:

\[\left\{\begin{matrix} a\times b=c' & & \\ b\times c=a' & & \\ a\times c=b' & & \end{matrix}\right. \]

所以最后需要除 \(3\)

最后的最后:记得开\(long\) \(long\) ,记得随时 mod P

code

完整代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=6e6+105;
int a[N],b[N],s[N],P,inv[N],n,cnt,cj[N],ans,tot;
inline int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%P;
		a=a*a%P;b>>=1;
	}
	return res%P;
}
inline void pre(){
	sort(a+1,a+1+n);b[1]=a[1];cnt=1;s[1]=1;
	for(int i=2;i<=n;i++){
		if(a[i]!=a[i-1]) b[++cnt]=a[i],s[cnt]=1;
		else s[cnt]++;
	}
	for(int i=1;i<=cnt;i++) inv[i]=qpow(b[i],P-2);	
}
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
inline int count(int k){
	int x=upper_bound(cj+1,cj+1+tot,k)-cj;
	int y=lower_bound(cj+1,cj+1+tot,k)-cj;
	return x-y;
}
signed main(){
	n=read();P=read();
	for(int i=1;i<=n;i++) a[i]=read();
	pre();
	for(int i=1;i<=cnt;i++){
		if(s[i]>=3)
			if(b[i]*b[i]%P*b[i]%P==1) ans++;			
		if(s[i]>=2)
			for(int j=1;j<=cnt;j++){
				if(j==i) continue;
				if(b[i]*b[i]%P*b[j]%P==1)ans++;				
			}
	}
	int q=0;
	for(int i=1;i<=cnt;i++)
		for(int j=i+1;j<=cnt;j++) {
			cj[++tot]=b[i]*b[j]%P;
			if(cj[tot]==inv[i]) q++;
			if(cj[tot]==inv[j]) q++;
		}	
	sort(cj+1,cj+1+tot);int res=0;
	for(int i=1;i<=cnt;i++) res+=count(inv[i]);
	res-=q;
	printf("%d",res/3+ans);
	return 0;
}



D. 【长郡-NOIP2017模拟Day2-T2】座位安排

题目

描述

费了一番口舌,\(wfj_2048\) 终于成功的说服了 \(n∗m\) 个朋友来陪自己看电影。为了这次声势浩大的行动, \(wfi_2048\) 包下了一座有 \(n∗m\) 个座位的电影院。(\(wfj_2048\) 坐哪?我也不知道。。。。。。)

电影院前门的坐标为 \((0,0)\) ,后门的坐标为 \((0,m+1)\) 。有 \(k\) 个朋友站在前门外,剩下的 \(n∗m−k\) 个站在后门外。

但是,问题来了:每个朋友都有一个不一定相同的忍耐值 \(s\),若从她到达的门到座位的曼哈顿距离超过 \(s\) ,她会感到不爽并离开,可 \(wfi2048\) 不想错过任何一个朋友。于是,他想让你告诉他:是否存在一种方案,使得所有的朋友都能安排到合适的座位,且没有朋友会离开?

输入格式

第一行为 \(n\)\(m\) ,意义如描述之所示

第二行一个整数 \(k\) ,接着 \(k\) 个整数描述前门外的 \(k\) 个朋友的 \(s\)

第三行一个整数 \(n∗m−k\) 个数描述后门外的朋友的 \(s\)

输出格式

若存在合法的座位安排方案则输出YES,否则输出NO

思路

凭运气做法:

#include<bits/stdc++.h>
using namespace std;
int myrand() {
    std::mt19937 rng;
    rng.seed(std::random_device()());
    std::uniform_int_distribution<std::mt19937::result_type> dist2(1,10);
    return dist2(rng);
}
int main(){
    cout<<(myrand()<=6?"YES":"NO");
}

证据:

卡评测机,像这样:

和std的数据结构法不同,其实只需要一个很基础的贪心即可解决问题。

考虑一个事情:忍耐值越小越容易找不到座位。所以我们首先给忍耐值小的人安排座位。而且让他们尽量坐得远

对于从前入口和后入口进入的人,他们呈这样分布:

所以为了给后入口腾地方,前入口进的人的座位的列数应该尽量大。同理,后入口也是。

不同于std,这里将所有人的忍耐度都按从小到大排序,并记录一个\(qh\) 表示是从前进还是从后进。

用数组 \(vs[N][2]\) 来记录某一忍耐值的斜线是否坐满。

\(vs[3][1]=1\) 表示:从前入口进,\((2,1),(1,2)\) 都已经坐满。

最后,记得处理初始数据。

因为题目保证: \(n*m≤100000\) , 但是没有保证忍耐值 \(s\) 的大小。

所以:

int x=read();if(x>n+m) x=n+m;

不这么做会 \(RE\)

有关其他玄学优化

由于按照 \(s\) 排序后,越往后出现 NO 答案的可能性就越小,所有就有类似于模拟退火的这类优化:

\(\mathfrak{talk\; is\; easy\; show\; me\; the\; code}\)

while(t>1e-12 && i<=sum){
	flag=1;		
	check(p[i].s,p[i].qh);
	if(flag==1){printf("NO");return 0;}											
	i++;t*=d;
}
printf("YES");

code

#include<bits/stdc++.h>
using namespace std;
const int N=1e7+105;
bool mp[6105][6105];
int n,m,k1,k2,cnt;//qm[N],hm[N];
struct qwq{
	bool qh;//qh->0,q;qh->1,h
	int s;
}p[N];
bool cmp(qwq a,qwq b){return a.s<b.s;}
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
bool vs[N][2],flag;
inline void check(int s,int kind){
	if(kind==0){
		for(int i=s;i>=2;i--){
			if(vs[i][0]==1) continue;
			for(int j=min(i-1,m);j>=1;j--)
				if(mp[i-j][j]==0)
					{flag=0;mp[i-j][j]=1;break;}					
			if(flag==1) vs[i][0]=1;
			else if(flag==0) return ;
		}
	}
	else{
		for(int i=s;i>=2;i--){
			if(vs[i][1]==1) continue;
			for(int j=min(i-1,m);j>=1;j--)
				if(mp[m-(i-j)+1][j]==0)
					{flag=0;mp[m-(i-j)+1][j]=1;break;}
			if(flag==1) vs[i][1]=1;
			else if(flag==0) return ;
		}		
	}
}
int main(){
	n=read();m=read();k1=read();
	int sum=n*m;	
	for(int i=1;i<=k1;i++){
		int x=read();if(x>n+m) x=n+m;
		p[++cnt].s=x;
		p[cnt].qh=0;
	}
	k2=read();
	for(int i=1;i<=k2;i++){
		int x=read();if(x>n+m) x=n+m;
		p[++cnt].s=x;
		p[cnt].qh=1;
	}
	sort(p+1,p+1+sum,cmp);
	int i=1;
	while(i<=sum){
		flag=1;		
		check(p[i].s,p[i].qh);
		if(flag==1){printf("NO");return 0;}											
		i++;if(i>sum) break;
	}
	printf("YES");
	return 0;
}


E. 【雅礼2019.11.03pm-T3】红心大战

题目

描述

Resee在欢乐地和朋友们打红心大战,想请你帮她统计一下分数~

具体地游戏规则如下:

共有 \(4\) 个玩家,\(52\) 种牌

牌有"2,3,4,5,6,7,8,9,10,J,Q,K,A"自小到大种点数; 黑桃( \(S\) ), 红桃( \(H\) ), 梅花( \(C\) ), 方块( \(D\) )

一张牌由一种花色和一种点数组成

在每一回合中,每个玩家各出一张牌,点数最大的牌中最先出牌的人为该轮的赢家,四张牌被置入赢家的弃牌堆中。所有回合结束后,对于每个玩家弃牌堆中的牌独立进行结算,分两个步骤:

第一步,玩家拿出所有种类红桃牌各一张和一张黑桃 \(Q\),丢弃它们并且给其他玩家各加 \(26\) 分,重复以上过程直到不能作出这个操作

第二步,一张一张丢弃剩下所有的牌,如果这张牌是红桃则给自己加 \(1\) 分,如果是黑桃 \(Q\) 则给自己加 \(13\) 分,其他牌分数不变

输入格式

第一行一个整数 \(n\) 表示回合数

接下来四行四个字符串,依次表示四个人的名字

接下来 \(4∗n\) 行表示出的牌,三个字符串依次表示出牌的人,牌的花色,牌的点数

输出格式

四行按照输入顺序依次输出四个人的得分

思路

小模拟题,根据题意写代码。

坑点:

  • 注意10的读入

  • 注意测试点中有红桃1这张不存在的牌。

  • 注意减掉一套红桃牌的同时记得减掉一张黑桃 \(Q\)

code

#include<bits/stdc++.h>
using namespace std;
int n,j;
struct wanjia{
	string name;
	int qp[5][21],fen;
}p[5];
struct zhuopai{int cnt,mx,belong;}zp;
struct pai{int num,hua;}zpai[11];
inline void pre(){
	zp.mx=-1;zp.cnt=0;zp.belong=-1;j=0;
	memset(zpai,0,sizeof(zpai));
}
inline int ZH(char c){
	if(c=='S') return 1;
	else if(c=='H') return 2;
	else if(c=='C') return 3;
	else if(c=='D') return 4; 
}
inline int ZN(char c,char c2){
	int x=0;
	if(c=='1' && c2=='0') x=10;
	else if(isdigit(c)) x=c-'0';
	else{
		if(c=='J') x=11;
		else if(c=='Q') x=12;
		else if(c=='K') x=13;
		else if(c=='A') x=14;
	}
	return x;
}
inline void chupai(int k){
	char Hua,Num[2];int zh,zn;char c2;
	cin>>Hua;cin>>Num;
	zh=ZH(Hua);zn=ZN(Num[0],Num[1]);
	if(zp.mx==-1) {zp.mx=zn,zp.belong=k;}
	else if(zn>zp.mx){zp.mx=zn;zp.belong=k;}	
	zpai[++zp.cnt].num=zn;zpai[zp.cnt].hua=zh;
}
inline void mljs(int k){	
	for(int i=1;i<=zp.cnt;i++)
		p[k].qp[zpai[i].hua][zpai[i].num]++;	
}
inline void zjs(int k){
	bool flag=1;
	while(1){
		for(int i=2;i<=14;i++)
			if(p[k].qp[2][i]<=0){flag=0;break;}
		if(p[k].qp[1][12]<=0) flag=0;	
		if(flag==0) break;
		else{
			for(int i=2;i<=14;i++)p[k].qp[2][i]--;
			p[k].qp[1][12]--;			
			for(int i=1;i<=4;i++)if(i!=k) p[i].fen+=26;			
		}
	}
	for(int i=2;i<=14;i++)
		if(p[k].qp[2][i]!=0) 
			p[k].fen+=p[k].qp[2][i],p[k].qp[2][i]=0;
	if(p[k].qp[1][12]!=0) 
		p[k].fen+=p[k].qp[1][12]*13,p[k].qp[1][12]=0;
}
int main(){	
	cin>>n;
	for(int i=1;i<=4;i++) cin>>p[i].name;
	while(n--){
		pre();
		for(int i=1;i<=4;i++){
			string s;cin>>s;
			for(j=1;j<=4;j++) if(p[j].name==s) break;
			chupai(j);
		}
		mljs(zp.belong);
	}	
	for(int i=1;i<=4;i++) zjs(i);
	for(int i=1;i<=4;i++) printf("%d\n",p[i].fen);
	return 0;
}



posted @ 2021-09-23 09:27  Nickle-NI  阅读(67)  评论(0编辑  收藏  举报