1. 程式人生 > >洛谷比賽 waaadreamer的聖誕虐題賽題解

洛谷比賽 waaadreamer的聖誕虐題賽題解

比賽連結
本題解同步於洛谷部落格

獲獎名單

前三:
rank1:@GZY_GZY
rank2:@ljc1301
rank3:@加藤惠
orz三位dalao!tql!

一血榜:
A: @ThinkofBlank(09:05:39)
B: @muller(09:08:57)
C: @GZY_GZY(11:34:05)
D: @東師附中大頭(10:57:32)
E: @GZY_GZY(18:29:35獲得最高分58pts)

吐槽

誰知道我元旦想放水題放出鍋了啊……T2據說是和別人差不多的題……對此我深表歉意。(既然這是基本全場AC的簽到題大家就放我一馬吧,感激不盡2333)
其它題應該沒啥問題。

還有某人特別喜歡抄別人程式碼,不是我針對你,抄一題就算了,你T3T4T5都是抄的,在這裡做個警告,你拿了高分又如何,別以為改了變數名函式名我就看不出來,別以為開了完全隱私保護就NB了。

總體評估

T1,T2簡單題,T3,T4稍有難度,T5較難(可能不太準?)部分分放的真的好多好多啊……
好了廢話不多說,放題解。

T1 WD與矩陣

subtask1:

暴力即可。複雜度 O ( T

2 n m ) O(T2^{nm})

subtask2:

這個部分分是腦子一抽給的,肯定沒人寫。直接列舉列,把行的狀態狀壓起來就行。複雜度 O

( T m 2 2 n ) O(Tm2^{2n})

subtask3:

打表應該都能發現,答案就是 2 ( n 1 ) ( m 1 ) 2^{(n-1)(m-1)} ,因為我們可以最後一行和最後一列完全可以根據前面填的值直接算出來,因此任意排列前面的東西即可。複雜度 O ( T l o g n ) O(Tlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int mod = 998244353;
ll modpow(ll a, ll b) {
    ll res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}
int main(){
    int n, m, T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        printf("%lld\n", modpow(2, (ll)(n - 1) * (m - 1)));
    }
    return 0;
}

T2 WD與迴圈

subtask1:

我們會顯然的發現問題轉化為求有多少種不同的值使 i = 1 n a i m \sum_{i=1}^na_i\le m 。因此這個直接dp即可。
f [ i ] [ j ] f[i][j] 表示當前到第 i i 個迴圈,前面值的和為 j j 的方案數,最後就是 i = 0 m f [ n ] [ i ] \sum_{i=0}^mf[n][i] ,複雜度 O ( n m + T ) O(nm+T) .

subtask2:

發現有不等號不太方便,考慮如果是等號怎麼做。這是顯然的插板法(如果不知道可以度娘……),也就是說 i = 1 n a i = m \sum_{i=1}^na_i=m 的非負解共有 ( n + m 1 m 1 ) \binom{n+m-1}{m-1} 組,因為我們可以視為在 n n 個數中加上 m 1 m-1 個可相鄰的隔板,相鄰兩個隔板之間的長度就代表了對應的數字。
因此我們預處理階乘,直接列舉 m m 計算對應的組合數,然後求和即可。複雜度 O ( T m + m o d ) O(Tm+mod)

subtask3:

有個結論,就是:
i = q n ( i q ) = ( n + 1 q + 1 ) \sum_{i=q}^n \binom{i}{q}=\binom{n+1}{q+1}
簡略證明如下:
i = q n ( i q ) = ( n q ) + ( n q + 1 ) i = q n 1 ( i q ) = ( n q + 1 ) \sum_{i=q}^n\binom{i}{q}=\binom{n}{q}+\binom{n}{q+1}即\sum_{i=q}^{n-1}\binom{i}{q}=\binom{n}{q+1}
歸納即可。
因此原題的答案直接就是 ( n + m m ) \binom{n+m}{m} ,直接上盧卡斯定理即可。複雜度 O ( T + m o d ) O(T+mod) 。(假定盧卡斯求組合數為 O ( 1 ) O(1) )。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int mod = 19491001;
ll fac[mod], rev[mod], n, m;
ll modpow(ll a, int b) {
    ll res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}
ll lucas(ll a, ll b) {
    if (a < b) return 0;
    if (a < mod) return fac[a] * rev[b] % mod * rev[a - b] % mod;
    return lucas(a / mod, b / mod) * lucas(a % mod, b % mod) % mod;
}
int main() {
    for (int i = fac[0] = 1; i < mod; i++) fac[i] = fac[i - 1] * i % mod;
    rev[mod - 1] = modpow(fac[mod - 1], mod - 2);
    for (int i = mod - 1; i > 0; i--) rev[i - 1] = rev[i] * i % mod;
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%lld%lld", &n, &m);
        printf("%lld\n", lucas(n + m, m));
    }
    return 0;
}

T3 WD與數列

subtask1:

似乎 O ( n 3 ) O(n^3) 暴力直接可以把subtask2艹過去?luogu還是太快了啊!這個應該不用多說吧,直接暴力列舉一對開頭,然後暴力向後擴充套件直到相減的值改變了。

subtask2:

本來這個點是給hash的……又怕hash常數大被卡,於是就只有1000的資料量……不管不管送分大甩賣!
(我看你是送分大甩鍋吧2333)
首先可以想到差分整個序列,然後問題轉化為求不相交不相鄰的相等子串對數。
就是列舉答案的長度,然後從左往右跑,對於某個串,把他左邊和他不相交或相鄰的串的hash加到hash_map裡,直接看有多少串的hash和它相同即可。複雜度 O ( n 2 ) O(n^2)

subtask3:

嘿嘿嘿這個點沒法水過去了吧……
做法1: 肯定還是差分整個數列,接下來考慮怎麼做。如果去掉不相交不相鄰的這個限制直接SA+單調棧即可(或者SAM,但是慢啊)。接下來考慮如何把多算的減掉。
我們會發現多算的一定靠的很近(廢話),可以考慮使用NOI2016優秀的拆分那題的方法,也就是使用調和級數。
F0fokD.png
我們考慮列舉兩個串的偏移位置(即圖中的 k k ),然後每 k k 位打一個點,可以發現我們只需要讓這兩個串在它們第一次出現紅點的位置被計算一次即可。
考慮計算兩個紅點為結尾的LCS和為開頭的LCP,由於我們要讓每對串只能在第一個紅點處被計算,因此LCS要和 k k 取個min。然後就是細節功底了……考慮暴力怎麼做,我們可以列舉兩個串的開頭位置 a , a + k a,a+k ,並且由於兩個串要相鄰或相交,則第一個串的結尾