2018 10 05 校内模拟 二分+状压+树剖

T1: 阶乘
fact.cpp
【问题描述】
有 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
二分的上界要稍微大点,1e8,不然会漏掉答案。
验证时对p分解质因子,然后求出m!里面有多少个p的各个质因子,如果都比p多,那么m就合法。

代码:

#include <cstdio>   
#include <iostream>   
#include <cstring>   
#define LL long long   
#define N 600007   
using namespace std;   
LL n, zs[N], T, a[N];   
LL tot = 1, ans;   
bool b[N];    
 void Pre_work(){   
     for (int i = 2; i <= N / 2; i++){   
         if (!b[i]){   
             zs[++zs[0]] = i;   
             for (int j = 1; j <= zs[0]; j++)   
                 if (i * zs[j] > N / 2)    break;   
                 else    b[zs[j] * i] = 1;   
         }   
         else{   
             for (int j = 1; j <= zs[0]; j++)   
                 if (i * zs[j] > N / 2)    break;   
                 else    b[zs[j] * i] = 1;   
         }   
     }   
 }   
    
 LL max(LL a, LL b){   
     return a > b ? a : b;   
 }   
    void Cl(LL x){   
    for (int i = 1, p = x; p > 1; i++)   
        for (; p % zs[i] == 0; p /= zs[i]){   
            if (!b[p]){   
                a[p]++;   
                T = max(T, p);   
                p = 1;   
                break;   
            }   
            a[zs[i]]++, T = max(T, zs[i]);   
        }   
}   
   
bool Check(LL ain){   
    for (int i = 1; i <= T; i++){   
        int j = zs[i];   
        LL Jl = 0;   
        for (LL k = j; (k <= ain) && (Jl < a[zs[i]]); k *= j) Jl += ain / k;   
        if (Jl < a[zs[i]])    return 0;   
    }   
    return 1;   
}   
   
void Find(){   
    LL l = 1, r = 100000000;   
    while (l < r) {   
        LL mid = (l + r) / 2;   
        if (Check(mid))    r = mid;   
        else l = mid + 1;   
    }   
    printf("%lld", l);   
}   
   
int main(){   
    freopen("fact.in", "r", stdin);   
    freopen("fact.out", "w", stdout);   
    scanf("%lld", &n);   
    LL x;   
    Pre_work();   
    for (int i = 1; i <= n; i++){   
        scanf("%lld", &x);   
        if (!b[x])  {   
            a[x]++, T = max(T, x);   
            continue;   
        }    
        Cl(x);   
    }   
    Find();   
    return 0;    
}   

T2:上升序列
(lis)
【问题描述】
给出一个长度为 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.

状压dp好题。
首先需要回忆O(nlogn)O(nlogn) 的方法,我们会维护一个单调递增的d数组。
可以设计状态f(s1,s2)表示选取的数的集合是s1,然后d数组中元素的出现情况是s2。
这样转移是很简单的。
但时空都无法承受。
于是我们考虑优化,不难发现s1是s2的子集。
因此我们三进制状压dp就行了。

代码

#include<bits/stdc++.h>   
#define fo(i,a,b) for(int i=a;i<=b;++i)   
#define fod(i,a,b) for(int i=a;i>=b;--i)   
#define N 16   
#define M 14348910   
#define LL long long   
using namespace std;   
int n,m,cf[N],a[N],wz[N],cf2[N];   
LL f[M],ans;   
 void dfs(int k,int lim,int v)   
 {   
     if(k>n)   
     {   
         if(lim==0) ans+=f[v];   
         return;   
     }   
     v+=cf[k-1];   
     dfs(k+1,lim,v);   
     v+=cf[k-1];   
     dfs(k+1,lim-1,v);   
 }   
 int main()   
 {   
     cin>>n>>m;   
     fo(i,1,m) scanf("%d",&a[i]),wz[a[i]]=i;   
     cf[0]=cf2[0]=1;   
     fo(i,1,n) cf[i]=cf[i-1]*3,cf2[i]=cf2[i-1]*2;   
     f[0]=1;   
     fo(i,0,cf2[n]-2)   
     {   
         bool pd=1,c1=1;   
         fo(j,1,m)    
         {   
             if((i&cf2[a[j]-1])==0) pd=0;   
             else if(!pd)   
             {   
                c1=0;   
                break;   
            }   
        }   
        if(c1)   
        {   
            for(int i1=i;1;i1=(i1-1)&i)   
            {   
                int vi=0;   
                fo(j,1,n)    
                {   
                    if(i1&cf2[j-1]) vi+=cf[j-1];   
                    if(i&cf2[j-1]) vi+=cf[j-1];   
                }    
                if(f[vi])   
                fo(j,1,n)   
                {   
                    if(vi/cf[j-1]%3==0)   
                    {   
                        int v=vi+2*cf[j-1];   
                        fo(p,j+1,n)    
                            if(v/cf[p-1]%3==2)    
                            {   
                                v-=cf[p-1];   
                                break;   
                            }   
                        f[v]+=f[vi];   
                    }   
                }   
                if(i1==0) break;   
            }   
        }   
    }   
    dfs(1,m,0);   
    printf("%lld\n",ans);return 0;   
}   

T3 :相遇
(meet)
【问题描述】
豪哥生活在一个 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、这条路径的lca在另一条路径上。
2、某条路径穿过这条路径的lca。

我们用两个树状数组来维护,用dfs序。

对于第一种情况,我们维护这个点到根节点的路径上面有多少个lca,然后相减就能得到答案。
对于第二种情况,我们在两个节点上面打一个+1的标记,在lca上面打一个-2的标记,这样就将整条链都+1了。

代码:

#include<bits/stdc++.h> 
#define lowbit(x) ((x)&-(x))
#define treeSum(t,x) (sum(t,R[x])-sum(t,L[x]-1))
using namespace std;
const int N=2e5+10;int n,m,L[N],R[N],stm;
int tot,to[N*2],next[N*2],final[N],app[N];int f[N][19],dep[N];int t1[N],t2[N],ans;
void change(int *tr,int x,int v) {for (; x<=n; x+=lowbit(x)) tr[x]+=v;}
int sum(int *tr,int x) 
{
	int ret=0;
	for (; x; x-=lowbit(x)) ret+=tr[x];
	return ret;
}
void link(int x,int y) {to[++tot]=y,next[tot]=final[x],final[x]=tot;}
void dfs(int x,int fa) 
{
	L[x]=++stm;
	f[x][0]=fa; for (int i=1; i<19; i++) f[x][i]=f[f[x][i-1]][i-1];
	dep[x]=dep[fa]+1;
	for (int i=final[x]; i; i=next[i]) if (to[i]!=fa) dfs(to[i],x);
	R[x]=stm;
}
int lca(int x,int y) 
{
	if (dep[x]<dep[y]) swap(x,y);
	for (int i=18; i>=0; i--) if (dep[f[x][i]]>=dep[y]) x=f[x][i];
	if (x==y) return x;
	for (int i=18; i>=0; i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return f[x][0];
}
void calcAns(int x,int y,int lca) 
{
	ans+=treeSum(t1,lca);
	ans+=sum(t2,L[x])+sum(t2,L[y])-sum(t2,L[lca])*2;
}
int main() 
{
	cin>>n;
	for (int i=1; i<n; i++) 
	{
		int u,v; 
		scanf("%d %d\n",&u,&v); 
		link(u,v),link(v,u);
	}
	dfs(1,0);
	cin>>m;
	for (int i=1; i<=m; i++) 
	{
		int u,v; scanf("%d%d",&u,&v);
		int g=lca(u,v);
		ans=0;
		calcAns(u,v,g);
		printf("%d\n",ans+app[g]);
		app[g]++;
		change(t1,L[u],1);
		change(t1,L[v],1);
		change(t1,L[g],-2);
		change(t2,L[g],1);
		change(t2,R[g]+1,-1);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值