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;
}
正解…………
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 ,樹狀陣列單點修改區間查詢
…………