比賽:
http://oj.hzjingma.com/contest/view?id=71
A.Fade
思路一、枚舉
根據題目意思,。由於。所以我們可以枚舉所有的 x,判斷是否滿足條件
根據題目的 p 的數據範圍,。不會超時。如果有成立的 x 存在,說明有一個解,那就答案 + 1。
思路二、數學分析
也可以根據數學分析,我們對於,可以得到 ,根據,可以得到,要麼 使得左邊爲 0,那麼就是 p 的倍數。要麼 ,使得等於 p 的倍數。
因此,解都是 2 個。
特別的,當 p = 2的時候,是重根,那解爲 1 個。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int p;
cin >> p;
int ans = 0;
for(int x = 1; x < p; ++x)
{
if(x * x % p == 1) ++ans;
}
cout << ans << endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int main()
{
int p;
cin >> p;
if(p == 2) cout << 1 << endl;
else cout << 2 << endl;
return 0;
}
B. Different World
簡單思維題,無論一個數,是質數還是合數,它的倍數,幾乎都是合數,除了特別的 1,要至少是 8 和 9 纔會是連續的合數。
所以我們知道了差,要找到對應的兩個合數,使得這兩個合數的差,是輸入的值。那麼根據 k * x - (k - 1) * x = x,我們可以知道,只要是輸入值的連續倍數(滿足要都是合數),就可以了。
正常的 >= 2 的,我們可以 2 倍 和 3 倍即可。
但是對應 1 而言, 2 倍 和 3 倍 不行,至少要爲 8 和 9 才滿足條件。
因此我們特判,如果是 1,那就輸出 8 和 9。否則,輸出 輸入值的 2 倍數和 3倍數。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a, b;
if(n == 1)
{
a = 9;
b = 10;
}
else
{
a = 2 * n;
b = 3 * n;
}
printf("%d %d\n", a, b);
return 0;
}
C.Sing Me to Sleep
一開始的想法,枚舉少的那一個數,然後求出剩餘數的乘積,和。如果乘積都是最大值,那麼取出,和中的最大值。
這個方法的時間複雜度應該是 O(N ^ 2),因爲枚舉缺少的數,剩下的數還要遍歷一次相乘,相加。
根據 N 的範圍不會超時。但是根據每一個值的大小,那麼可能的乘積大小,最大爲 10 ^ (2 * N),這個超出了數據範圍,無法存儲(除非用大數)。
那麼能不能根據分類討論呢,因爲是要從 N 個數中,選出 N - 1 個數。我們可以根據 數據中 0 的個數分類討論
- 當 0 的個數 >= 2的時候,那麼無論 從 N 個數中,怎麼取 N - 1,都會包含 0,也就是說,乘積的最大值是 0。那麼只要我們在 N 個數的和,去掉最小值,就是最大的 和。
- 當 0 個數 == 1 的時候。這個時候,又分爲兩種情況
- 取出 N - 1 個數中,有 0。那麼乘積爲 0。
- 取出的 N - 1 個數中,沒有 0。那麼此時根據數據中,負數的個數。如果是偶數個負數,那麼乘積是 正的,最大乘積也是這個值。如果是奇數個 負數,那麼乘積是 負的,最大乘積是 0。
- 如果最大乘積是 0,那麼對於最大和就是,我們要從 N 個數中,去掉一個最小數即可。
- 如果最大乘積是第二種情況的(也就是偶數個 負數),那麼最大和是確定的,因爲從 N 個數中,去掉的那個數是 0。
- 當 沒有出現 0。
- 如果 N 個數的 乘積是 正數 (也就是負數個數是 偶數個)
- 全是負數的情況下,說明,去掉其中一個屬之後,乘積變爲負數,那麼此時的最大乘積,比如 - 4,-3,-2,-1,我們應該選擇大的三個,也就是說,此時的最大乘積是除去了最小數,因爲最大和,也是去掉了最小數。
- 全是正數的情況下。那麼最大乘積,去掉最小數,因此,最大和,也是去掉了最小數
- 有正有負的情況下, 我們應該去掉一個正數,同時爲了保證是最大乘積,去掉的是,最小正數,同時,最大和,也就是去掉了這個最小正數。
- 如果 N 個數的乘積 是 負數 (也就是負數 個數 是 奇數)
- 不可能全是正數
- 全是負數的情況下,那麼去掉了一個負數,乘積一定是正的,比如 比如 - 4,-3,-2,我們應該去掉 -2,保證最大乘積。因此,是去掉了最大數,同時,最大和,也是去掉了這個最大數。
- 有正有負的情況下, 我們應該去掉一個負數,同時爲了保證是最大乘積,去掉的是,絕對值最小的負數,那就是最大負數,同時,最大和,也就是去掉了這個最大負數。
- 如果 N 個數的 乘積是 正數 (也就是負數個數是 偶數個)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
int main() {
int n, a;
cin >> n;
vector<int> nums;
int zeros = 0;
int sum = 0;
int fushu = 0;
int zhengshu = 0;
for(int i = 0;i < n; ++i) {
cin >> a;
nums.push_back(a);
sum += a;
if(a < 0) ++fushu;
if(a > 0) ++zhengshu;
if(a == 0) ++zeros;
}
sort(nums.begin(), nums.end());
if(zeros >= 2)
{
sum -= nums[0];
cout << sum << endl;
return 0;
}
if(zeros == 1)
{
if(fushu % 2 == 0)
{
cout << sum << endl;
}
else
{
cout << sum - nums[0] << endl;
}
return 0;
}
if(fushu % 2 == 0) // 正數
{
if(zhengshu == n){
sum -= nums[0];
}
else if(fushu == n) {
sum -= nums[0];
}
else
{
int tep = INF;
for(int i = 0;i < n; ++i){
if(nums[i] < 0) continue;
tep = min(tep, nums[i]);
}
sum -= tep;
}
cout << sum << endl;
}
else // 結果是負數
{
if(fushu == n) {
sum -= nums[n - 1];
}
else
{
int tep = -INF;
for(int i = 0;i < n; ++i){
if(nums[i] > 0) continue;
tep = max(tep, nums[i]);
}
sum -= tep;
}
cout << sum << endl;
}
return 0;
}
D. Alone
要求是三角形,那就是,任意兩邊之和,大於第三邊。
一開始的想法,是枚舉所有可能的三個值,那麼複雜度是 O(N ^ 3)。根據數據範圍,會超時。
因此,我們要降低時間複雜度。根據數據範圍,時間複雜度最大不能超過 O(N ^ 2)。
那麼也就是要兩層遍歷,就要得到答案。
我們如果第一層循環,先固定三條邊中的最大值,那麼根據兩邊之和大於第三邊,剩下的條件,就是剩下的兩條沒確定的邊之和要大於這個 三條邊的最大值。
我們對數組先排序,然後第二層循環的時候,我們 left 是剩下數的第一個值,right 是剩下數的第二個值,類似二分查找。
- 那麼當這個兩個滿足條件的時候,我們可以發現,因爲 比 left 後面的數,一定是大於 left 這個數的。因爲對於 left 滿足條件,剩下的數,和 right ,還有最大數,都滿足,將這些都加進答案(也就是 right - left)。而且因爲這兩個值的大於了這個最大值了,那麼對於我們來說,考慮變小點滿不滿足條件,那就是 right - 1
- 如果不滿足條件,說明這兩個數之和太小,我們想增大,那麼就是 left +1.
這樣子,第二層循環,我們用了雙指針,雙指針剛好一起走的一次數組。
所以時間複雜度是 O(N ^ 2)
這道題也是一道LeetCode的題目,第 611。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int MAXN = 1e4 + 10;
LL nums[MAXN];
int main() {
int n;
scanf("%d", &n);
for(int i = 0;i < n; ++i) {
scanf("%lld", &nums[i]);
}
LL count = 0;
sort(nums, nums + n);
for (int i = n - 1; i >= 2; i--) {
int left = 0, right = i - 1;
while(left < right) {
if (nums[left] + nums[right] > nums[i]) {
count += (right - left) * 1ll;
right--;
}
else {
left++;
}
}
}
cout << count << endl;
return 0;
}
E. Lost Control
對於少部分的數據,可以解決的。對於大範圍的數據,是要分塊打表。具體可以看官方題解,不是很懂
http://oj.hzjingma.com/contest/editorial?id=71
代碼就去看官方比賽AC的人的代碼
F. All Falls Down
數學公式題,根據題意,是求的前 n 項和,根據等差數列前 n 項和 公式,還是平方數的前 n 和公式,我們可以得到
注意是要求 MOD,同時,我們注意 n 的取值很大,所以對於 n 我們就需要求 MOD。
還有一個地方要注意,對於除法的取模,需要計算逆元。
也可以不用計算逆元,不過這個時候,就需要先利用 n 把 3 給約去。
- 當 n % 3 == 0,那麼先計算 n / 3,可以剛好相除
- 當 n % 3 == 1,那麼先計算 (2n + 1) / 3
- 當 n % 3 == 2,那麼先計算 (n + 1) / 3
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MOD = 1e9 + 7;
int main() {
LL res = 0;
LL n;
cin >> n;
if(n % 3 == 0)
{
res = (((((2 * n / 3) % MOD) * ((n + 1) % MOD)) % MOD) * ((2 * n + 1) % MOD)) % MOD;
}
else if(n % 3 == 1)
{
res = (((((2 * (2 * n + 1) / 3) % MOD) * ((n + 1) % MOD)) % MOD) * ((n) % MOD)) % MOD;
}
else if(n % 3 == 2)
{
res = (((((2 * (n + 1) / 3) % MOD) * ((n) % MOD)) % MOD) * ((2 * n + 1) % MOD)) % MOD;
}
LL tep = (n % MOD) * ((n + 1) % MOD) % MOD;
res = (res - tep + MOD) % MOD;
cout << res << endl;
return 0;
}
G. Love
這是一道,DP問題,主要是設好變量
- 設 表示,第 i 個元素,填的 num,此時遞減長度爲 k,前 i 項(包括該項)和爲 sum 的序列種類。
- 初始條件,對於第一個元素,如果是 - 1,也就是說,這個元素可以隨便放,那麼就是 。如果這個元素不是 -1,也就是有值。假設輸入的數組爲 a,那麼
- 狀態轉移,我們對於當前狀態
- 當前狀態爲 -1,那麼就是這個值可以隨便放,那麼遍歷此時可以放的 值 j 從 0 到 40。這個值是從上一個值過來,那麼L上一個值也可以是 0 到 40,遍歷。同時,我們還要遍歷,前 i - 1 的 和(要保證 前面的平均數 >= j )纔可以狀態過來。同時轉移過來的,還要求比較 L 和 j 的值(因爲遞減長度不能爲 3)
- 假如 L > j,那麼只能是前面遞減序列爲長度爲 1 的轉移到,現在的變成了 2.
- 假如 L <= j,那麼此時不消耗遞減序列,所以可以是 遞減序列 1 - 1,也可以是 2 到 2。
- 當前狀態不爲 -1,也是就是 上面討論的 j 是確定的 a[i] 的值,剩下的和上面一樣。
- 轉移過來的時候,是值進行累加。
- 當前狀態爲 -1,那麼就是這個值可以隨便放,那麼遍歷此時可以放的 值 j 從 0 到 40。這個值是從上一個值過來,那麼L上一個值也可以是 0 到 40,遍歷。同時,我們還要遍歷,前 i - 1 的 和(要保證 前面的平均數 >= j )纔可以狀態過來。同時轉移過來的,還要求比較 L 和 j 的值(因爲遞減長度不能爲 3)
- 結果,結果就是,我們知道所有元素結束 n,但是不知道 num,k,sum,所以枚舉所有中,任何一種都是可能的序列。問題問,我們有多少種序列,那就是把所有可能情況的值,都累加起來。
#include <bits/stdc++.h>
using namespace std;
#define Mod 1000000007
long long a[42];
long long dp[42][42][3][1602];
int main(){
int n;
memset(dp,0,sizeof(dp));
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
if(a[1]==-1){ // 初始條件
for(int i=0;i<=40;i++)
dp[1][i][1][i]=1;
}
else
dp[1][a[1]][1][a[1]]=1;
// 從 2 開始
for(int i=2;i<=n;i++)
{
if(a[i]==-1) // == -1
{
for(int j=0;j<=40;j++) // 當前值 j 都有可能
{
for(int L=0;L<=40;L++) // 上一個值的取值 L 都可以轉移過來
{
// 對於前 i - 1的和 k,要求 k/(i - 1) >= j,所以 k 從 (i - 1) * j 開始,但是總和值不會超過 1600(因爲最長 40,每一個元素最大 40).所以 k + j <= 1600確定上界
for(int k=j*(i-1);k<=1600-j;k++)
{
if(j>=L)
{
dp[i][j][1][k+j] = (dp[i][j][1][k+j]+dp[i-1][L][1][k])%Mod;
dp[i][j][1][k+j] = (dp[i][j][1][k+j]+dp[i-1][L][2][k])%Mod;
}
else
dp[i][j][2][k+j]=(dp[i][j][2][k+j]+dp[i-1][L][1][k])%Mod;
}
}
}
}
else // a[i] != -1,也就是上面 j = a[i],是確定的
{
for(int L=0;L<=40;L++)
{
for(int k=a[i]*(i-1);k<=1600-a[i];k++)
{
if(a[i]>=L)
{
dp[i][a[i]][1][k+a[i]] = (dp[i][a[i]][1][k+a[i]]+dp[i-1][L][1][k])%Mod;
dp[i][a[i]][1][k+a[i]] = (dp[i][a[i]][1][k+a[i]]+dp[i-1][L][2][k])%Mod;
}
else
dp[i][a[i]][2][k+a[i]]=(dp[i][a[i]][2][k+a[i]]+dp[i-1][L][1][k])%Mod;
}
}
}
}
// 最後答案累加,對於 n 結束的時候,枚舉 num,k,sum的所有可能成立的序列
long long sum=0;
for(int j=0;j<=40;j++){
for(int k=j*n;k<=1600;k++){
sum=(sum+dp[n][j][1][k])%Mod;
sum=(sum+dp[n][j][2][k])%Mod;
}
}
cout<<sum<<endl;
return 0;
}
H. Diamond Heart
注意到,此題如果真的去一一枚舉dist,那麼複雜度最優也是O(n^2logn)的
我們考慮dist的意義。對於每一條邊,這條邊兩端上,左半邊的點與右半邊的點,一定會經過這條邊。所以這條邊會被經過左半邊點數 * 右半邊點數,這條邊的權值是w,所以這條邊產生的答案貢獻就是次數 * 權值。
把所有邊的貢獻加起來就是答案了。
因此,先要構建圖,然後 DFS,我們從一個節點 u,找到 v 節點 的時候,需要統計(u --- v), v 那邊的的點數,因爲我們需要 DFS 還可以附帶計算 某一個點這邊的點數,我們用 一個數組 sz 記錄。
從 u 父節點開始的時候,表示在 u 這邊有一個點(u 本身),所以 sz[u] = 1。
然乎遍歷這個 u 的所有子節點 v,得到 v 的時候,由於我們要先計算 v 這邊都有多少點(從而得到 一個半邊的點數,同時另一半邊的點數 是 n - 另一邊,從而計算答案)。所以我們先繼續 DFS,等這裏的 DFS 返回的時候,表示 sz[v] 這裏計算好了,那麼就計算。同時這裏返回得到 v 的點數,那麼我們要更新 u 的點數(因爲 u 的這邊,包括了 v,所以 v 的點數,加回到 u 上),sz[ u ] += sz[ v ]。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 200000 + 11;
typedef pair<int, int> pii;
ll ans;
int n;
vector<pii> G[N]; // 圖
int sz[N];
void DFS(int u,int f){
sz[u] = 1; // 這個點的點數 初始化,就自己 = 1
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].first;
if(v == f) continue; // 因爲是 u 到 v,如果 u 回到了自己的父節點,那就不考慮,因爲是要找子節點
DFS(v, u); // 先繼續 DFS,因爲 DFS的一個隱返回值,可以返回,這個點半邊的點數
ans = (ans + sz[v] * 1ll * (n - sz[v]) * G[u][i].second);
sz[u] += sz[v]; // 返回了 v 的點數,那麼 u 的點數,累加 v 起來
}
}
int main(){
scanf("%d", &n);
for(int i = 0;i < n - 1; ++i)
{
int a, b, c; scanf("%d%d%d", &a, &b, &c);
G[a].push_back(pii(b, c));
G[b].push_back(pii(a, c));
}
ans = 0; // 答案
DFS(1, -1); // 隨便從某一個節點出發
printf("%lld\n", ans);
return 0;
}
I. Lily
KMP的題目(等待學習中)
看到這個Alan Walker值的定義,如果對kmp熟悉的選手一定知道,這就是next[]數組的含義。
所以跑一遍kmp,然後取個min就好了。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios :: sync_with_stdio(false); cin.tie(0);
string s; cin >> s;
int n = s.size(); s = ' ' + s;
vector <int> kmp(n + 1);
for (int i = 2, j = 0; i <= n; i++) {
while (j > 0 && s[i] != s[j + 1]) {
j = kmp[j];
}
if (s[i] == s[j + 1]) {
j++;
}
kmp[i] = j;
//cout << "kmp[" << i << "] = " << kmp[i] << '\n';
}
int q; cin >> q;
while (q--) {
int l, k;
cin >> l >> k;
if (kmp[l] >= k) {
cout << 0 << '\n';
} else {
cout << k - kmp[l] << '\n';
}
}
return 0;
}