"位元組跳動杯"2018中國大學生程式設計競賽-女生專場(ing)
1002. 口算訓練
題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=6287
Problem Description
小Q非常喜歡數學,但是他的口算能力非常弱。因此他找到了小T,給了小T一個長度為n的正整數序列a1,a2,...,an,要求小T丟擲m個問題以訓練他的口算能力。
每個問題給出三個正整數l,r,d,小Q需要通過口算快速判斷al×al+1×...×ar−1×ar是不是d的倍數。
小Q迅速地回答了出來,但是小T並不知道正確答案是什麼,請寫一個程式幫助小T計算這些問題的正確答案。
Input
第一行包含一個正整數T(1≤T≤10),表示測試資料的組數。
每組資料第一行包含兩個正整數n,m(1≤n,m≤100000),分別表示序列長度以及問題個數。
第二行包含n個正整數a1,a2,...,an(1≤ai≤100000),表示序列中的每個數。
接下來m行,每行三個正整數l,r,d(1≤l≤r≤n,1≤d≤100000),表示每個問題。
Output
對於每個問題輸出一行,若是倍數,輸出Yes,否則輸出No。
Solution:
將輸入的每個數分解質因數並記錄,對d分解質因數並判斷每個質因數在陣列中是否有足夠的因數。
[l, r] 中每個數相乘的結果是d的倍數,只需要 [l, r] 之間每個數的因數選擇一些乘起來是 d 或 d 的倍數即可。但是暴力的儲存查詢會超時,所以可以通過用 vector 存因子 x 出現的所有座標 i,然後再利用二分查詢當前範圍內由多少個因數。
注意,分解的時候要注意當前數是素數的情況。
Code:
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <map> #include <cstdlib> #include <string> #include <iostream> #include <vector> #include <map> #include <queue> using namespace std; typedef long long LL; typedef pair<int, int> PII; const int MaxN = 1e5 + 5; vector<int> G[MaxN]; int query(int l, int r, int x) { return upper_bound(G[x].begin(), G[x].end(), r) - lower_bound(G[x].begin(), G[x].end(), l); } bool check(int l, int r, int d) { for(int i = 2; i * i <= d; i++) { if(d % i == 0) { //對d分解質因數 int cnt = 0; while(d % i == 0) { cnt++; d /= i; } if(query(l, r, i) < cnt) return 0; } } if(d > 1 && query(l, r, d) < 1) return 0; //如果當前數是素數 return 1; } int main () { int t; scanf("%d", &t); while(t--) { for(int i = 1; i <= MaxN; i++) G[i].clear(); int n, m; scanf("%d %d", &n, &m); for(int i = 1; i <= n; i++) { int x; scanf("%d", &x); for(int j = 2; j * j <= x; j++) { //對陣列中的數分解質因數 while(x % j == 0) { x /= j; G[j].push_back(i); //對於每次當前的質因數都儲存座標i } } if(x > 1) G[x].push_back(i); //當前數是素數 } while(m--) { int l, r, d; scanf("%d %d %d", &l, &r, &d); if(check(l, r, d)) printf("Yes\n"); else printf("No\n"); } } return 0; }
1003. 缺失的資料範圍
題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=6288
Problem Description
著名出題人小Q出過非常多的題目,在這個漫長的過程中他發現,確定題目的資料範圍是非常痛苦的一件事。
每當思考完一道題目的時間效率,小Q就需要結合時限以及評測機配置來設定合理的資料範圍。
因為確定資料範圍是一件痛苦的事,小Q出了非常多的題目之後,都沒有它們設定資料範圍。對於一道題目,小Q會告訴你他的演算法的時間複雜度為O(nalogbn),且蘊含在這個大O記號下的常數為1。同時,小Q還會告訴你評測機在規定時限內可以執行k條指令。小Q認為只要na(⌈log2n⌉)b不超過k,那麼就是合理的資料範圍。其中,⌈x⌉表示最小的不小於x的正整數,即x上取整。
自然,小Q希望題目的資料範圍n越大越好,他希望你寫一個程式幫助他設定最大的資料範圍。
Input
第一行包含一個正整數T(1≤T≤1000),表示測試資料的組數。
每組資料包含一行三個正整數a,b,k(1≤a,b≤10,106≤k≤1018),分別描述時間複雜度以及允許的指令數。
Output
對於每組資料,輸出一行一個正整數n,即最大可能的n。
Description:
給出a、b、k,對於公式 找出最大的n。
Range:
1 ≤ a, b ≤ 10
1e6 ≤ k ≤ 1e18
Solution:
二分查詢可行解。
把公式分為 和 兩部分分別進行判斷。
需要注意的兩點是:
- 乘法可能溢位,需要將乘法轉換為除法
- 求 ⌈log2 n⌉ 時不能使用實數計算,會帶來誤差,應當使用整數計算。
Code:
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#define fi first
#define se second
#define mst(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const double eps = 1e-9;
const int Mod = 1e9 + 7;
const int MaxN = 1e5 + 5;
int a, b;
LL k;
LL log2(LL mid)
{
for(int i = 0; i <= 64; i++) {
if(pow(2LL, i) >= mid) return i;
}
}
bool check(LL mid)
{
LL pre = 1LL;
for(int i = 1; i <= a; i++) { //判斷前面部分
if(pre <= k / mid) pre *= mid; //防止乘法溢位
else return false; //當前的結果大於k說明不合法
}
LL base = log2(mid);
LL suf = 1LL;
if(suf == 0) return true; //否則會在下面的除法中出現RE
for(int i = 1; i <= b; i++) { //判斷後面部分
if(suf <= k / mid) suf *= base;
else return false;
}
if(pre <= k / suf) return true; //將兩部分合起來判斷一次
return false;
}
int main()
{
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t; scanf("%d", &t);
while(t--) {
scanf("%d %d %lld", &a, &b, &k);
LL l = 0LL, r = 1e18, ans = 0LL;
while(l <= r) {
LL mid = (l + r) / 2;
if(check(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
cout << ans << endl;
}
return 0;
}
1004. 尋寶遊戲
題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=6289
Problem Description
小Q最近迷上了一款尋寶遊戲,這款遊戲中每局都會生成一個n×m的網格地圖,從上往下依次編號為第1行到第n行,從左往右依次編號為第1列到第m列。每個格子上都有不同數量的金幣,第i行第j列的格子上的金幣數量為ai,j。
小Q一開始位於(1,1),每次他可以往右或者往下走,每當他經過某個格子時,他就可以拿走這個格子上的所有金幣。小Q不能走出這個地圖,當小Q不能再行動時,遊戲結束。顯然當且僅當小Q位於(n,m)時,遊戲才會結束。
一輪遊戲的得分為這一輪中收集到的金幣總量,而在遊戲開始前,因為小Q是超級VIP使用者,所以他有k次機會交換某兩個格子中的金幣數。這k次機會不一定要用完,請寫一個程式幫助小Q在一輪內拿到儘可能多的金幣。
Input
第一行包含一個正整數T(1≤T≤10),表示測試資料的組數。
每組資料第一行包含三個整數n,m,k(2≤n,m≤50,0≤k≤20),分別表示地圖的長寬以及交換的次數。
接下來n行,每行m個整數ai,j(0≤ai,j≤106),依次表示每個格子中金幣的數量。
Output
對於每組資料,輸出一行一個整數,即能收集到的金幣數量的最大可能值。
Description:
對於 n*m 的矩陣每個點都有一個權值。從 (1, 1) 開始每次只能向下或向右走,求到達 (n, m) 時可以獲得的最大值,其中你有k次交換機會交換任意兩點上的權值。
Solution:
假設已經選定了一條路線,那麼不考慮具體交換方案,考慮選定一個 t(t ≤ k),經過的格子裡要有 t 個格子的權值不計入得分,而不經過的格子裡要有 t 個格子的權值計入總分。那麼每一種交換方案都可以對應這樣一個轉化。
設 表示從 (1, 1) 出發來到 (i, j),考慮完前 i − 1 行所有格子以及第 i 行前 j 個格子時,有 x 個經過的格子不計分,y 個不經過的格子計分的情況下,總分的最大值是多少。那麼有兩種狀態轉移:
- 往右走一格,直接轉移到;
- 往下走一格,轉移到,需要列舉這一行有多少個不經過的格子計分。顯然這些
格子一定按照權值從大到小貪心選擇。
最終答案即為 (0 ≤ t ≤ k)
時間複雜度O()。