1. 程式人生 > 其它 >【題解報告】ZJNU綜合訓練(2021.1.26)

【題解報告】ZJNU綜合訓練(2021.1.26)

技術標籤:題解報告演算法

【題解報告】ZJNU綜合訓練(2021.1.26)

B

  • n n n 盒糖果並排放,每盒有數量 r i r_i ri ,裡面顏色為 c i c_i ci
  • 如果吃糖,必須在該位置的糖全部吃完,且必須比上一次吃得數量多,且必須與上一次吃的糖的顏色不一樣。
  • 每次向相鄰方向走一格的時間花費為 1 1 1,吃糖不花費時間。
    起點在 s s s,問你至少吃 k k k 顆糖最少花費時間為多少。

  • 題目要求真地多,而且我開始還讀錯題意了。。
    d p [ i ] [ j ] dp[i][j] dp[i][j] 表示你現在在位置 i i i 且吃了該位置的糖,花了 j j j 時間的最多吃糖數。
    為什麼可以這麼設呢?因為你每次吃糖一定是吃完的,你中間如果走過去沒有吃得話,其實是為了走到目的地然後吃,所以設的不是你目前的位置,而是你目前吃完糖的位置
  • 轉移條件也很簡單,滿足糖果顏色不同、數量要更多就行了。
    注意,時間上限是多少? n 2 n^2
    n2
    ,因為你不可能沒吃糖,經過同一個點兩次。

時間複雜度: O ( n 4 log ⁡ n ) O(n^4\log n) O(n4logn)

const int MAX = 55;
int n,s,k;
int r[MAX];
string c;
int dp[MAX][MAX*MAX];

int main()
{
    cin >> n >> s >> k;
    for(int i = 1;i <= n;++i)cin >> r[i];
    cin >> c;
    c = " " + c;
    memset
(dp,-1,sizeof(dp)); priority_queue<pair<int,int> >Q; for(int i = 1;i <= n;++i){ dp[i][abs(i-s)] = r[i]; Q.push(make_pair(-(abs(i-s)),i)); } while(!Q.empty()){ int d = -Q.top().first; int x = Q.top().second; Q.pop(); if(dp[x][d] >= k){ cout << d; return 0; } for(int i = 1;i <= n;++i){ if(c[x] != c[i] && r[i] > r[x] && dp[i][abs(i-x)+d] < dp[x][d] + r[i]){ dp[i][abs(i-x)+d] = dp[x][d] + r[i]; Q.push(make_pair(-(abs(i-x)+d),i)); } } } cout << -1; return 0; }

C

  • 給你一個長度為 1 0 5 10^5 105 的數字串。
    問你,中間連續刪掉一段數字之後,剩下的數字頭尾相接,形成刪數。問你所有不同的刪法後,所拼接出來的刪數的和取模 998244353 998244353 998244353 是多少。
  • 比如 107 107 107,刪掉中間連續一段之後會變成 07 、 17 、 10 、 7 、 1 、 0 07 、 17、 10、 7、 1、 0 071710710,他們的和為 42 42 42

  • 把數字拼接起來之後再算怎麼搞都是搞不出來的、、我們按頭和尾來考慮。
    如果他們倆能連線起來,我們給他們連一條線。下面的 + + +號表示把他們收尾拼接。
    比如 ϕ + 07 = 07 \phi +07=07 ϕ+07=07 ϕ + ϕ = 0 \phi+\phi=0 ϕ+ϕ=0 1 + 7 = 17 1+7=17 1+7=17
    在這裡插入圖片描述
  • 考慮每一串頭和每一串尾到底對答案作出多少貢獻。
    注意:頭的拼接還要考慮拼接後相當於頭的數字增加了 1 0 k 10^k 10k
  • 尾的收益:最長的尾只貢獻 1 1 1次,次長的貢獻 2 2 2次等
    尾的收益比較好算,單個串的數字好算,單個串的貢獻次數也好算。
  • 頭的收益:
    (1) ϕ \phi ϕ 的貢獻為 1 1 1 次和長度為 2 2 2 的串拼接, 1 1 1 次為和長度為 1 1 1 的串拼接, 1 1 1 次和長度為 0 0 0 的串拼接,它的貢獻就是 0 × ( 1 0 2 + 1 0 1 + 1 0 0 ) 0\times(10^2+10^1+10^0) 0×(102+101+100)
    (2) 1 1 1 的貢獻為 1 1 1 次和長度為 1 1 1 的串拼接, 1 1 1 次和長度為 0 0 0 的串拼接,它的收益就是 1 × ( 1 0 1 + 1 0 0 ) 1\times(10^1+10^0) 1×(101+100)
    (3)對於某一個頭串,它的貢獻為 它 本 身 × p r e [ x ] 它本身\times pre[x] ×pre[x] ,其中 p r e [ x ] = 1 0 x + 1 0 x − 1 + ⋯ + 1 0 0 pre[x]=10^x+10^{x-1}+\cdots+10^0 pre[x]=10x+10x1++100。這個 x x x 取原串長減去該頭串長再減一。

時間複雜度: O ( l o g 10 S ) O(log_{10}S) O(log10S)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
char ss[MAX];
ll pre[MAX];
void init(int x){
    pre[0] = 1;
    ll base = 10;
    for(int i = 1;i <= x;++i){
        pre[i] = (pre[i-1] + base) % MOD;
        base = base * 10 % MOD;
    }
}
int main()
{
    init((int)1e5+50);
    scanf("%s",ss);
    int len = strlen(ss);
    ll sum = 0;
    ll base = 1;
    ll tmp = 0;
    for(int i = len-1;i >= 1;--i){
        tmp = (base * (ss[i]-'0') % MOD + tmp) % MOD;
        sum = (sum + tmp * i % MOD) % MOD;
        base = (base * 10) % MOD;
    }
    tmp = 0;
    base = 1;
    for(int i = 0;i < len-1;++i){
        tmp = (tmp * 10 % MOD + (ss[i]-'0')) % MOD;
        sum = (sum + tmp * pre[len-i-2] % MOD) % MOD;
        base = (base * 10) % MOD;
    }
    printf("%lld",sum);
    return 0;
}

D

  • 問你對於一個序列,是否可以刪掉一個數字後,其他數字成等差,問你刪掉哪一個。
  • 序列先排序,再考慮刪頭或者刪尾或者刪中間。
    刪中間的話:要刪除的那個數字的序號一定是 該位置之後的數字減去該位置之前的數字的和最小
    考慮一個等差 5 、 10 、 15 、 20 、 25 5、 10、 15、 20、 25 510152025
    如果要刪中間,你多餘的數字應該要放在這些數字的中間
    5 、 10 、 15 、 18 、 20 、 25 5、 10、 15、 18、 20、 25 51015182025
    此時明顯 20 − 18 + 18 − 15 = 20 − 15 = 5 20-18+18-15=20-15=5 2018+1815=2015=5 是所有裡面最小的,因為其他的數字 15 − 5 = 10 15-5=10 155=10 明顯是兩倍等差。

F

  • 給你一個樹,但是樹的邊是單向的。
    你要選擇首都的位置,要修改一些道路的朝向,使得首都能夠到達所有的位置。
    問你最少修改多少條路,以及首都的所有可選位置。
  • 樹形 D P DP DP。設 d p [ x ] dp[x] dp[x] 表示在 x x x 位置處選擇首都的話,需要改多少條路。
    雖然這個 d p [ x ] dp[x] dp[x] 我們是直接算不出來的,但是我們知道相鄰兩個位置的 d p dp dp 值的大小關係
    在這裡插入圖片描述
  • 這樣就能算出哪個節點的 d p dp dp 值相對最小,然後以該節點作為起點進行 d f s dfs dfs,檢視需要修改幾條邊就行了。
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
vector<pair<int,int> >V[MAX];
int dp[MAX];
int mn,id;
void dfs(int u,int fa){		/// 跑dp
    if(dp[u] < mn){
        mn = dp[u];
        id = u;
    }
    for(auto it : V[u]){
        int v = it.first;
        int w = it.second;
        if(v == fa)continue;
        if(w == 1)dp[v] = dp[u] + 1;
        else dp[v] = dp[u] - 1;
        dfs(v,u);
    }
}
int ans;
void dfs2(int u,int fa){	// 跑最小修改路徑數量
    for(auto it : V[u]){
        int v = it.first;
        int w = it.second;
        if(v == fa)continue;
        if(w == -1)ans++;
        dfs2(v,u);
    }
}
int main()
{
    int n;scanf("%d",&n);
    for(int i = 1;i < n;++i){
        int ta,tb;scanf("%d%d",&ta,&tb);
        V[ta].push_back(make_pair(tb,1));
        V[tb].push_back(make_pair(ta,-1));
    }
    mn = 0;id = 1;
    dfs(1,1);
    dfs2(id,id);
    printf("%d\n",ans);
    for(int i = 1;i <= n;++i){
        if(dp[i] == dp[id])cout << i << " ";
    }
    return 0;
}

H

  • 問你,最少正幾邊形,你通過頂點連線能夠得到 d d d 的角度(角度制),該角度為 1 ∼ 180 1\sim180 1180的整數。
    在這裡插入圖片描述
  • 圓周角所對的弧長相等的話,圓周角是相同的。因此一個正 n n n 邊形,算出一份圓周角是多少度,然後每次暴力列舉份數 1 ∼ n − 2 1\sim n-2 1n2,檢視是不是整數度數即可。
int mn[MAX];

int main()
{

    int T;cin >> T;
    while(T--){
        int n;cin >> n;
        int mn = INF;
        for(int i = 3;i <= 700;++i){
            double deg = (180.0*i-360)/(1.0*i)/(1.0*i-2);
            for(int j = 1;j <= i-2;++j){
                double de = deg * j;
                if(fabs(de-n)<EPS)mn=min(mn,i);
            }
        }
        if(mn == INF)cout << -1 << endl;
        else cout << mn << endl;
    }
    return 0;
}

I

  • m e r g e ( A , B ) merge(A,B) merge(A,B) 函式表示每次取 A 、 B A、B AB 序列的開頭較小的元素,然後把該位置的元素拿出來,放在集合的尾。如果有一個序列是空的話,直接取非空序列。
    比如 m e r g e ( { 3 , 2 } , { 1 , 4 } ) = { 1 , 3 , 2 , 4 } merge(\{3,2\},\{1,4\})=\{1,3,2,4\} merge({3,2},{1,4})={1,3,2,4}
    給定一個 2 n 2n 2n 個元素的全排列。問該全排列是否能通過 m e r g e ( A , B ) merge(A,B) merge(A,B),且 ∣ A ∣ = ∣ B ∣ = n |A|=|B|=n A=B=n得到。

  • 非常巧妙的一題。
    (1)對於目前的最大元素 2 n 2n 2n ,假設該元素原來是在 A A A 裡面,那麼該位置之後的所有數字一定全屬於 A A A 序列。然後我們把這些數字給拿出來。
    (2)對於剩下的數字,有最大元素 p p p ,該位置到末尾的位置一定全屬於 A A A B B B 序列。
    (3)重複上述步驟,我們就得到了一段一段的序列,每一段必須都屬於 A A A 或者 B B B 序列。
    (4)怎麼求是否能存在 ∣ A ∣ = ∣ B ∣ = n |A|=|B|=n A=B=n 呢?設一個 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示目前處理到第 i i i 段,若 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1 則表示目前我們能拿到長度為 j j j 的段。最後看 d p [ 段 數 ] [ n ] = 1 o r 0 dp[段數][n]=1\ or\ 0 dp[][n]=1or0就可以了。
    時間複雜度: O ( n 2 ) O(n^2) O(n2)
int aa[MAX];
int pos[MAX];
bool use[MAX];
int shu[MAX];
int dp[MAX][MAX];
int main()
{
    int T;cin >> T;
    while(T--){
        int n;cin >> n;
        int ed = n * 2;
        for(int i = 1;i <= ed;++i){
            cin >> aa[i];
            pos[aa[i]] = i;
            use[aa[i]] = 0;
        }
        int you = ed;
        int last = ed;
        int cnt = 0;
        while(you){
            while(use[last])last--;
            for(int i = pos[last];i <= you;++i)use[aa[i]] = 1;
            shu[++cnt] = you - pos[last] + 1;
            you = pos[last] - 1;
        }
        for(int i = 1;i <= cnt;++i)
        for(int j = 0;j <= ed;++j)
            dp[i][j] = 0;
        dp[0][0] = 1;
        for(int i = 1;i <= cnt;++i){
            for(int j = 0;j <= ed;++j){
                if(j >= shu[i])dp[i][j] |= dp[i-1][j-shu[i]];
                dp[i][j] |= dp[i-1][j];		/// 這個不要忘記轉移
            }
        }
        if(dp[cnt][n])puts("YES");
        else puts("NO");
    }
    return 0;
}

J

  • n + 1 n+1 n+1個點,第 0 0 0個點的位置為 0 0 0,第 i i i 個點的位置為 i i i
    對於中間 1 ∼ n 1\sim n 1n的點,每個點都有 1 2 \frac{1}{2} 21 的概率放置一個訊號塔,該點為 i i i ,訊號塔的強度如果為 p p p ,則他能覆蓋到 j j j 點的條件是 ∣ i − c ∣ < p |i-c|<p ic<p
    所有塔的訊號強度是你自己調整的,可以都不同。
    求:有多少的概率使得你可以調整所有訊號塔,使得 1 ∼ n 1\sim n 1n的所有點都只被一個訊號塔的訊號覆蓋,且 0 0 0 n + 1 n+1 n+1 沒有被訊號覆蓋

  • 可以想到,不管訊號強度為多少,該訊號塔能覆蓋到的數量一定是奇數
    題目就轉變為 d p ( i ) 2 n \frac{dp(i)}{2^n} 2ndp(i),其中 d p ( i ) dp(i) dp(i) 表示 i i i 能被拆成奇數的方案個數。
    比如 d p ( 3 ) = 2 dp(3)=2 dp(3)=2,因為 3 = 1 + 1 + 1 = 3 3=1+1+1=3 3=1+1+1=3
    考慮狀態轉移方程就可以了:
    d p ( i ) = d p ( i − 1 ) + d p ( i − 3 ) + ⋯ + d p ( ( x − ( 2 k + 1 ) ) > 0 ) dp(i)=dp(i-1)+dp(i-3)+\cdots+dp((x-(2k+1))>0) dp(i)=dp(i1)+dp(i3)++dp((x(2k+1))>0)
    就用一個字首和 p r e [ x ] = d p ( x ) + d p ( x − 2 ) + ⋯ pre[x]=dp(x)+dp(x-2)+\cdots pre[x]=dp(x)+dp(x2)+ 就可以了。

時間複雜度: O ( N ) O(N) O(N)

/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
ll dp[MAX];
ll pre[MAX];        /// pre[i] = dp[i] + dp[i-2] + dp[i-4]...
int main()
{
    int n;cin >> n;
    dp[0] = 1;
    pre[0] = 1;
    dp[1] = 1;
    pre[1] = 1;
    for(int i = 2;i <= n;++i){
        dp[i] = pre[i-1];
        pre[i] = (pre[i-2] + dp[i]) % MOD;
    }
    cout << dp[n] * inv(qpow(2,n)) % MOD;
    return 0;
}

M

  • 一張 n × m n\times m n×m的桌子,有無窮多的盤子,盤子半徑都為 r r r
    兩個人輪流放盤子,盤子不能重疊,不能邊超出桌子。
    問你先手必勝還是後手必勝

  • 這題真不是水題也不是 B U G BUG BUG 題呀。。
    如果先手第一個盤子都放不下,那肯定是後手贏了。
    否則,先手第一個盤子放在該桌子的正中心處
    這樣,構造除了一個中心對稱圖形。後手不論怎麼放,先手都放在該位置的中心對稱位置處。這樣,不論後手怎麼放,先手都能有位置放(易證),因為每一次先手放置的時候該圖都是中心對稱圖形。