2018提高组模拟10

2018提高组模拟10

————————————————————————————————————————20181005

T1

阶乘

(WOJ4043)

素数线性筛    分解质因数   数学推理

描述

有n个正整数a[i],设它们乘积为p,你可以给p乘上一个正整数q,使p*q刚好为正整数m的阶乘,求m的最小值。

输入

共两行。

第一行一个正整数n。

第二行n个正整数a[i]。

输出

共一行

一个正整数m。

样例输入

1
6

样例输出

3

提示

样例解释:

当p=6,q=1时,p*q=3!

【数据范围与约定】

对于10%的数据,n<=10

对于30%的数据,n<=1000

对于100%的数据,n<=100000,a[i]<=100000

题解

先分解所有的质因数

题目要求一个最小的m使m!包含 p这个因子。 
可以把p分解质因数,假设 p=∏ai^bi(ai为质数),那么只要 m!包含了
所以对于每个ai^bi,分别求出满足条件的最小的 m,取最大值即可。 
怎么求m? 
先看一个简单的问题: 
27!里面有多少个3相乘? 
27!=1*2*...*27 
包含1个3的数有27/(3^1)=9个 
包含2个3的数有27/(3^2)=3个 
包含3个3的数有27/(3^3)=1个 
总共:9+3+1=13 个 
所以27!里面有13个3相乘。 
用这个方法就可以求得m!

我们可以各个质因数及它的个数算出一个最小的满足的m

再取个MAX

但,语言系统太弱了,无法完成口胡,就自行看代码吧

另外,还有二分的方法:*(^_^)*

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x;
}
#define ll long long
int n,a[100010],b[100010];
int pri[96]={0,2,3,5,7,11,13,17,19,23,
29,31,37,41,43,47,53,59,61,67,
71,73,79,83,89,97,101,103,107,109,
113,127,131,137,139,149,151,157,163,167,
173,179,181,191,193,197,199,211,223,227,
229,233,239,241,251,257,263,269,271,277,
281,283,293,307,311,313,317,331,337,347,
349,353,359,367,373,379,383,389,397,401,
409,419,421,431,433,439,443,449,457,461,
463,467,479,487,491,499};
queue<int>q;
void divide(int x){
	for(int i=1;pri[i]*pri[i]<=x&&i<=95;i++){
		if(x%pri[i]==0){
			q.push(pri[i]);
			do{
				x/=pri[i];
				b[pri[i]]++;
			}while(x%pri[i]==0);
		}
	}
	if(x>1){
		q.push(x);
		b[x]++;
	}
}
int r;
ll ans=0,c;
ll ques(int x){
	int an=0;
	while(b[x]>0){
		an+=(b[x]/(x+1)*(x-1)+b[x]%(x+1));
		b[x]/=(x+1);
	}
	return 1ll*an*x;
}
int main(){
	n=read();
	for(int i=1;i<=n;++i){
		a[i]=read();
		divide(a[i]);
	}
	while(!q.empty()){
		r=q.front();q.pop();
		c=ques(r);
		if(c>ans)ans=c;
	}
	printf("%lld",ans);
	return 0;
}

T2

上升序列

(WOJ4044)

状压DP    DFS部分分

描述

给出一个长度为 m 的上升序列 A(1 ≤ A[i]≤ n), 请你求出有多少种 1...n 的排列, 满足 A 是它的一个 LIS.

输入

第一行两个整数 n,m.

接下来一行 m 个整数, 表示 A.

输出

一行一个整数表示答案.

样例输入

【输入样例1】
5 3
1 3 4
【输出样例1】
11
【输入样例2】
4 2
3 4
【输出样例2】
5

样例输出

提示

【数据范围与约定】

对于前 30% 的数据, n ≤ 9;

对于前 60% 的数据, n ≤ 12;

对于 100% 的数据, 1 ≤ m ≤ n ≤ 15.

题解

DFS暴利出所有方案,都做一次最长上升子序列的DP,看那些合法。

如果中途已经发现不合法(给出的数组没有按规定排序,最长上升子序列大于了给出的),就不往下搜了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x;
}
#define ll long long
int n,m,c[20],a[20];
int b[20],f[20],d[20];
int dfs(int now){
	/*for(int i=1;i<=now;i++)
		cout<<i<<" "<<b[i]<<" "<<f[i]<<" "<<d[i]<<endl;
	cout<<endl;*/
	if(now>n){
		/*for(int i=1;i<now;i++)
			cout<<d[i]<<" ";
		cout<<endl;*/
		return 1;
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		if(b[i]||(a[i]&&!b[c[a[i]-1]]))continue;
		for(int j=0;j<now;j++){
			if(i>d[j])f[now]=max(f[now],f[j]+1);
		}
		if(f[now]>m){
			f[now]=0;
			continue;
		}
		b[i]=1;d[now]=i;
		ans+=dfs(now+1);
		f[now]=0;
		b[i]=0;d[now]=0;
	}
	return ans;
}
void work_1(){
	b[0]=1;
	printf("%d",dfs(1));
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		c[i]=read();
		a[c[i]]=i;
	}
	//if(n<=9&&m<=9)
		work_1();
	return 0;
}

正解:*(^…^)*

解释一下。

S2表示的是哪些数是D[i](最长不下降子序列的N*LOGn的算法的数组)。

在这道题中,因为没有重复,所以最长上升子序列就是最长不下降子序列。

而A[i]是1到n中的LIS(最长上升子序列),也就是最长不下降子序列。

所以用D[i]来存储,也就是一个取D[i]就等于取一个A[i]。

具体的我还有点懵,详细的看大佬题解。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
	int x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x;
}
int n,m,a[20],s2[20],s3[20];//二进制   三进制
ll f[14348990],ans=0;//f  3^15 
bool check(int sta){//state
	int flag=0;
	for(int i=1;i<=m;i++){
		if((sta&s2[a[i]-1])==0)flag=1;
		else if(flag)return 0;//这个位置有了,但它前面的a[i-1]却还没有 
	}
	return 1;
}
void dfs(int pos,int use,int sta){
	if(pos>n){
		if(!use)ans+=f[sta];//A数组用完 
		return;
	}
	sta+=s3[pos-1];dfs(pos+1,use,sta);
	sta+=s3[pos-1];dfs(pos+1,use-1,sta);
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++)a[i]=read();
	s2[0]=s3[0]=1;
	for(int i=1;i<=15;i++){
		s2[i]=(s2[i-1]<<1);
		s3[i]=(s3[i-1]<<1)+s3[i-1];
	}
	f[0]=1;
	for(int i=0;i<s2[n];++i){//枚举S的状态 
		if(!check(i))continue;//不合法:A数组没有按顺序
		
		
		for(int sub=i;1;sub=(sub-1)&i){//举S1,因为S1是S的子集
			//这个方法很有用,不会错
			int sta=0;
			for(int j=1;j<=n;j++){//计算sta   三进制 
				if(i&s2[j-1])sta+=s3[j-1];//S
				if(sub&s2[j-1])sta+=s3[j-1];//S1
			}
			if(f[sta]){//有值  可以向下传
				for(int j=1;j<=n;++j){
					int c=sta;
					if(c/s3[j-1]%3==0){//没用过
						c+=2*s3[j-1];
						for(int k=j;k<n;++k)
							if(c/s3[k]%3==2){//用过,且在D(最长不下降子序列)里
								c-=s3[k];//标记为1,用过,但不在D数组 
								break;
							}
						f[c]+=f[sta];
					}
				} 
			}
			if(!sub)break;
		}
	}
	dfs(1,m,0);//统计合法答案 
	printf("%lld",ans);
	return 0;
}

T3

(WOJ4045)

LCA    树上差分    树状数组

相遇

 

描述

豪哥生活在一个n个点的树形城市里面,每一天都要走来走去。虽然走的是比较的多,但是豪哥在这个城市里面的朋友并不是很多。

当某一天,猴哥给他展现了一下大佬风范之后,豪哥决定要获得一些交往机会来提升交往能力。豪哥现在已经物色上了一条友,打算和它(豪哥并不让吃瓜群众知道性别)交往。豪哥现在spy了一下这个人的所有行程起点和终点,豪哥打算从终点开始走到起点与其相遇。但是豪哥是想找话题的,他想知道以前有多少次行程和此次行程是有交集的,这样豪哥就可以搭上话了。这个路径与之前路径的有交集数量作为豪哥此次的交往机会。

但是豪哥急着要做交往准备,所以算什么交往机会的小事情就交给你了。

输入

第一行一个正整数n表示节点个数。接下来n-1行,每行两个正整数分别是u,v表示节点u和v之间有连边。接下来一行一个 正整数m表示路径个数。然后有m行,每行两个正整数分别是u,v分别表示u到v之间有一条路径

输出

输出共m行,每行一个整数,第i行表示豪哥在这条路径上获得的交往机会。

样例输入

5
1 2
1 3
3 4
3 5
4
4 5
4 2
1 3
1 2

样例输出

0
1
2
2
 

提示

【数据范围与约定】

对于20%的数据n,m≤2000

对于另外20%的数据n,m≤50000

对于另外10%的数据n,m≤200000保证树形结构是一条链

对于另外50%的数据n,m≤200000

题解

树上差分:*(^__^)*

已知路径求树上所有节点被路径覆盖次数:

1.对每条路径的 起点a和终点b 的权值 +1 , 对 lca(a, b) 的权值 -1 , 对 lca(a, b)的父节点 权值 -1

2.从根节点开始深搜,回溯时将其本身的权值加上所有子节点的权值

3.每个节点的权值既是其被路径覆盖的次数

于是可以转换为两个问题,用两个线段树维护:

1)当前LCA在之前路径上 ,树状数组区间修改单点查询

2)当前路径有之前LCA ,树状数组单点修改区间查询

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x;
}
struct edge{
	int u,v,nxt;
}e[800010];
int first[400010],cnt=0;
inline void add(int u,int v){
	e[++cnt].u=u;e[cnt].v=v;
	e[cnt].nxt=first[u];first[u]=cnt;
}
int n,m;
struct BIT{
	int c[800010]={0};
	inline void add(int x,int w){//x&-x   —>   x的二进制最右边的一个一的值 
		for(;x<=n;x+=x&-x)c[x]+=w;
	}
	inline int ask(int x){
		int an=0;
		for(;x;x-=x&-x)an+=c[x];
		return an;
	}
}T1,T2;
//T1区间修改单点查询      T2单点修改区间查询
//T1别的路径的LCA在当前路径上(包括两个LCA相重)
//T2当前路径的LCA在之前的路径上(不包括两个LCA相重) 
int fa[400010][20],dep[400010],st[400010],ed[400010],tim=0;
void dfs(int x,int f){
	st[x]=++tim;//dfs序 
	for(int i=1;(1<<i)<=dep[x];i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=first[x];i;i=e[i].nxt){
		int y=e[i].v;
		if(y==f)continue;
		fa[y][0]=x;
		dep[y]=dep[x]+1;
		dfs(y,x);
	}
	ed[x]=tim;
}
int lca(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	int t=dep[a]-dep[b];
	for(int i=0;(1<<i)<=t;i++)
		if(t&(1<<i))a=fa[a][i];//t的二进制在i这一位上为1
	if(a==b)return a;//
	for(int i=19;i>=0;--i)
		if(fa[a][i]!=fa[b][i])
			a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}
int val[400010];
int ans(int u,int v,int c){
	int e=T1.ask(st[u])+T1.ask(st[v])-2*T1.ask(st[c])+val[c];
	//cout<<c<<" "<<st[c]<<" "<<ed[c];
	int f=T2.ask(ed[c])-T2.ask(st[c]-1);
	return e+f;
}
int main(){
	int size=40<<20;//40M
    //__asm__ ("movl  %0, %%esp\n"::"r"((char*)malloc(size)+size));//调试用这个 
    __asm__ ("movq %0,%%rsp\n"::"r"((char*)malloc(size)+size));//提交用这个 

    //main函数代码 

	//freopen("1.in","r",stdin);
	n=read();
	for(int i=1;i<n;i++){
		int u,v;
		u=read();v=read();
		add(u,v);add(v,u);
	}
	dep[1]=1;
	dfs(1,0);
	m=read();
	//cout<<lca(1,3);
	while(m--){
		int u,v,c;
		u=read();v=read();
		c=lca(u,v);
		//cout<<c<<" ";
		printf("%d\n",ans(u,v,c));
		val[c]++;//特判LCA
		T2.add(st[u],1);
		T2.add(st[v],1);
		T2.add(st[c],-2);
		T1.add(st[c],1);
		T1.add(ed[c]+1,-1);
	}
	exit(0);//必须用exit 
}

【注】

这道题递归层数太多,会爆栈。

所以手动开栈。【网站开栈】

  int size=40<<20;//40MB
    //__asm__ ("movl  %0, %%esp\n"::"r"((char*)malloc(size)+size));//调试用这个 
    __asm__ ("movq %0,%%rsp\n"::"r"((char*)malloc(size)+size));//提交用这个 

    //main函数代码 



exit(0);//return 0;必须换exit(0);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值