1. 程式人生 > 實用技巧 >Codeforces Round #660 (Div. 2) A、B、C題解

Codeforces Round #660 (Div. 2) A、B、C題解

A. Captain Flint and Crew Recruitment #構造

題目連結

題意

定義一類正整數,能夠被\(p*q\)表示,其中\(p、q(1<p<q)\)均為素數,稱之為\(nearly\ prime\) 。現要求判斷整數\(n\),是否能被4個不同整數之和表示,且其中至少三個整數為\(nearly\ prime\) (是則,輸出YES否則輸出NO)

分析

\(n = 31 = 2\times7+2\times5+2\times3+1\),其中\(14,10,6\)\(nearly\ prime\)

\(n = 32 = 2\times3+2\times5+2\times7+2\)

,其中\(14,10,6\)\(nearly\ prime\)

容易得到最小的三個\(nearly\ prime\)\(6,10,14\),因而滿足條件的最小整數為31,見上。當\(n\leq30=6+10+14\),答案NO。當\(n>30\),答案定為YES

難道剩餘的正整數都能被\(6,10,14,n-30\)表示嗎?注意,當\(n-30 = 6or10or14\)時,出現了重複數字,我們可以構造出比{\(2*3,2*5,2*7,n\)}稍大的組合,即{\(2*3,2*5,3*5,n\)}

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
int main() {
    int Q; scanf("%d", &Q);
    while (Q--) {
        int n;
        scanf("%d", &n);
        if (n <= 30) {
            printf("NO\n");
            continue;
        }
        int last = n - 30;
        if (last == 6 || last == 10 || last == 14) {
            printf("YES\n6 10 15 %d\n", n - 31);
        }
        else {
            printf("YES\n6 10 14 %d\n", n - 30);
        }
    }
    return 0;
}

B. Captain Flint and a Long Voyage #構造 #貪心

題目連結

題意

給定整數\(n\),找出一個十進位制數字\(x\)滿足:長度為\(n\),得到\(x\)在二進位制下的串,去掉後面的\(n\)位,新二進位制串是最大的,且數字\(x\)越小越好。好繞,原題面看得十分吃力

分析

我們將\([0, 9]\)數字分別轉化為二進位制串,可以觀察到數字\(8,9\)的二進位制串長度最長,分別為\(1000, 1001\),由題幹要求,抹除\(n\)位後的二進位制串最大,那麼利用貪心思想,要找到這樣的串,那麼十進位制數字\(x\)的二進位制串越長越好,於是我們可以斷定這個\(x\)

肯定由\(8or9\)組成

但別忘了題幹還要求我們數字\(x\)越小越好,為什麼可以有這樣的條件?拿數字\(8,9\)的二進位制串\(1000, 1001\)而言,當我們抹除最後1位時,得到的兩個新串是相等的!於是但凡得到數字\(999...999\),我們可以在合法的長度內將末位的幾個\(9\)替換為\(8\),這樣既保證二進位制串大且原十進位制數小的情況了,即\(x\)的組成肯定為\(999...888\)

如何確定這一合法的替換長度呢?考慮到8/9二進位制串長度為4。對於原數字\(x\)最末尾的最末尾的(向上取整)\(n/4\)個要被去除的串,選8還是9的二進位制串是沒有區別的

#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
int main(){
    int Q; scanf("%d", &Q);
    while(Q--){
        int n;
        scanf("%d", &n);
        int len = n / 4; //將最後n位換成len個數字8的二進位制串
        if(n % 4 != 0) len++; //餘數為[1, 3]說明還可以再替換多一個數字8的二進位制串
        for (int i = 1; i <= n - len; i++)
            printf("9");
        for (int i = 1; i <= len; i++)
            printf("8");
        printf("\n");
    }
    return 0;
}

C. Uncle Bogdan and Country Happiness #DFS #逆向思維

題目連結

題意

給定\(n\)個節點的樹結構,\(m\)個人初始在根節點\(1\)號,已知每個人都有各自要到達的節點(預設走最短路),他們剛出發有各自的心情狀態(好/壞)且我們未知,在節點之間的轉移過程中好心情會轉化為壞心情,但心情壞的不會發生改變。先給定每個節點資料h[i],理論上值為該節點上 心情好的人數 - 心情壞的人數,現要判斷給定資料是否正確。

分析

若從節點1出發來考慮似乎不容易,我們可以嘗試從每個到達的目標節點逆推到根節點。

經過城市\(now\)人數有\(sum[now]\),該城市理論快樂指數為\(h[now]\),可以計算出理論上該城市的快樂人數

\(Hapsum[now] =(sum[now]+h[now])/2\)

\(= (Hapsum[now]+badsum[now]+Hapsum[now]-badsum[now])\)

參考官方題解,我們有三個標準去衡量資料的合法性:

  1. \(sum[now]+h[now]\)一定能被整除
  2. \(0\leq Hapsum[now]\leq sum[now]\) 即快樂人數肯定不會超過總人數
  3. \(Hapsum[to_1]+Hapsum[to_2]+...+Hapsum[to_{k}]\leq Hapsum[now]\) 即每個人從now出發可能會心情變糟,因而子節點的快樂人數總數一定不超過父節點
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
struct Edge {
    int to, nextNbr;
}E[MAXN << 1];
int Head[MAXN], tot = 0;
void addEdge(int u, int v) {
    tot++;
    E[tot].to = v; E[tot].nextNbr = Head[u]; Head[u] = tot;
}
int n, m, delt[MAXN], cnt[MAXN], sum[MAXN], HapSum[MAXN];
bool f = true;
void dfs(int pre, int now) {
    if (f == false) return;
    sum[now] = cnt[now];
    int tmp_sum = 0;
    for (int i = Head[now]; i >= 0; i = E[i].nextNbr) {
        int v = E[i].to;
        if (v != pre) {
            dfs(now, v);
            sum[now] += sum[v]; 
            tmp_sum += HapSum[v]; //統計 快樂人數
        }
    } //sum[now]統計now節點為根的子樹的總人數
    if ((sum[now] + delt[now]) % 2 != 0) f = false; //說明不是整數...case1
    HapSum[now] = (sum[now] + delt[now]) >> 1; //利用給定資料算出經過該城市後的理論快樂人數
    if (HapSum[now] < 0 || HapSum[now] > sum[now]) f = false;//說明快樂人數比總人數還多...case2
    if (HapSum[now] < tmp_sum) f = false;
    //說明經過now城市後的快樂人數 竟然比 走得更遠的城市的快樂人數 還 少...case3
}
int main() {
    int Q; scanf("%d", &Q);
    while (Q--) {
        scanf("%d%d", &n, &m);
        tot = 0; f = true;
        for (int i = 1; i <= n; i++) scanf("%d", &cnt[i]);
        for (int i = 1; i <= n; i++) scanf("%d", &delt[i]), Head[i] = -1;
        for (int i = 1; i <= n - 1; i++) {
            int x, y; scanf("%d%d", &x, &y);
            addEdge(x, y); addEdge(y, x);
        }
        dfs(0, 1);
        printf("%s\n", f ? "YES" : "NO");
    }
    return 0;
}