【算法比賽】競碼編程-IOI賽制測試賽1

比賽:

http://oj.hzjingma.com/contest/view?id=71


A.Fade

思路一、枚舉

根據題目意思,x^2 \% p = 1。由於x<p。所以我們可以枚舉所有的  x,判斷是否滿足條件

根據題目的 p 的數據範圍,p < 1000。不會超時。如果有成立的 x 存在,說明有一個解,那就答案 + 1。

 

思路二、數學分析

也可以根據數學分析,我們對於x^2 \% p = 1,可以得到 (x + 1)(x - 1) \% p = 0,根據x<p,可以得到,要麼 x - 1 = 0使得左邊爲 0,那麼就是 p 的倍數。要麼 x + 1 = p,使得等於  p 的倍數。

因此,解都是 2 個。x = 1 \, or\, x = p - 1

特別的,當 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,保證最大乘積。因此,是去掉了最大數,同時,最大和,也是去掉了這個最大數。
      • 有正有負的情況下, 我們應該去掉一個負數,同時爲了保證是最大乘積,去掉的是,絕對值最小的負數,那就是最大負數,同時,最大和,也就是去掉了這個最大負數。

 

 

#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

數學公式題,根據題意,是求4 i ^ 2 - 2i的前 n 項和,根據等差數列前 n 項和 公式,還是平方數的前 n 和公式,我們可以得到4\frac{n(n + 1)(2n + 1)}{6} - 2\frac{n(n +1)}{2} = \frac{2n(n + 1)(2n + 1)}{3} - n(n +1)

注意是要求 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問題,主要是設好變量

  • 設 dp[i][num][k][sum] 表示,第 i 個元素,填的 num,此時遞減長度爲 k,前 i 項(包括該項)和爲 sum 的序列種類。
  • 初始條件,對於第一個元素,如果是 - 1,也就是說,這個元素可以隨便放,那麼就是 dp[1][num = 0 \, to \, 40][1][num] = 1。如果這個元素不是 -1,也就是有值。假設輸入的數組爲 a,那麼dp[1][a[1]][1][a[1]] = 1
  • 狀態轉移,我們對於當前狀態
    • 當前狀態爲 -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] 的值,剩下的和上面一樣。
    • 轉移過來的時候,是值進行累加。
  • 結果,結果就是,我們知道所有元素結束 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;
}

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章