【AtCoder】AGC023 A-F題解

能夠說是第一場AGC了,作了三道題以後還有30min,槓了一下D題發現槓不出來,三題滾粗了
rating起步1300+,感受仍是很菜。。。
只有三題水平顯然之後還會瘋狂--啊(CF的慘痛經歷)ios

改題的感受彷佛還不錯由於思惟都很是的妙(我根本想不到)數組

A - Zero-Sum Ranges

開場娛樂你們的小水題,區間和爲0的狀況存在於sum[R] == sum[L - 1],只要記錄一下某一個值的sum出現了多少次就行,懶得離散化直接用map就OK啊優化

代碼

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#define MAXN 200005
#define PLI pair<long long,int>
#define fi first
#define se second
#define mp make_pair
//#define ivorysi
using namespace std;
typedef long long int64;
int N;
int64 A[MAXN];
map<int64,int> mmm;
void Solve() {
    scanf("%d",&N);
    for(int i = 1 ; i <= N ; ++i) scanf("%lld",&A[i]);
    mmm[0] = 1;
    int64 ans = 0;
    for(int i = 1 ; i <= N ; ++i) {
        A[i] += A[i - 1];
        ans += mmm[A[i]];
        mmm[A[i]] += 1;
    }
    printf("%lld\n",ans);
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
    return 0;
}

B - Find Symmetries

把矩陣行循環平移x,列循環平移y後,矩陣關於左上到右下這條對角線對稱
矩陣大小是300
而後我就寫了個暴力然而我並沒發現個人暴力是\(n^4\)而後愉快TLE
我就開始想着優化,發現固定行平移,每平移一列hash值的更改能夠O1算出來,check把每行每列hash起來比較,是O(n)的,而後過掉了spa

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#define MAXN 200005
#define PLI pair<long long,int>
#define fi first
#define se second
#define mp make_pair
#define ha 99994711
#define ba 823
//#define ivorysi
using namespace std;
typedef long long int64;
int N;
char s[305][305],b[305][305];
int64 hc[305],hr[305],e;
inline int C(int x) {
    return x > N ? x - N : x;
}
bool check(int y) {
    int T = C(N + y);
    for(int i = 1 ; i <= N ; ++i) 
        hr[i] = (hr[i] * ba + (b[i][T] - 'a' + 1)) % ha;
    for(int i = 1 ; i <= N ; ++i) {
        if(hr[i] != hc[C(i + y)]) {
            T = C(N + y + 1);
            for(int j = 1 ; j <= N ; ++j) 
                hr[j] = (hr[j] - e * (b[j][T] - 'a' + 1) % ha + ha) % ha;
            return false;
        }
    }
    T = C(N + y + 1);
    for(int i = 1 ; i <= N ; ++i) 
        hr[i] = (hr[i] - e * (b[i][T] - 'a' + 1) % ha + ha) % ha;
    return true;
}
void Solve() {
    scanf("%d",&N);
    if(N == 1) {
        puts("1");return;
    }
    e = 1;
    for(int i = 1 ; i < N ; ++i) e = e * ba % ha;
    for(int i = 1 ; i <= N ; ++i) {
        scanf("%s",s[i] + 1);
    }
    int cnt = 0;
    for(int A = 0 ; A < N ; ++A) {
        memset(hc,0,sizeof(hc));
        memset(hr,0,sizeof(hr));
        for(int i = 1 ; i <= N ; ++i) {
            for(int j = 1 ; j <= N ; ++j) {
                b[i][j] = s[C(i + A)][j];
                hc[j] = (hc[j] * ba + (b[i][j] - 'a' + 1)) % ha;
                if(j != N) {
                    hr[i] = (hr[i] * ba + (b[i][j] - 'a' + 1)) % ha;
                }
            }
        }
        for(int B = 0 ; B < N ; ++B) {
            if(check(B)) ++cnt;
        }
    }
    printf("%d\n",cnt);
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
    return 0;
}

C - Painting Machines

while(1) 推式子
聯想到地震後的幻想鄉顯然題目能夠轉化爲
\(\sum_{i = 0}^{N - 2} T(i)\)其中\(T(i)\)表示用了i次操做沒有所有染黑的方案數
\(T(0) = (N - 1)!\)
而後分類討論,一個是由於沒用N-1而沒有所有染黑的方案數是\(\binom{N - 2}{i}i!\)
若是用了N - 1,那麼方案是\((\binom{N - 2}{i - 1} - W(i - 1))i!\)
\(W(i)\)表示用了一個N - 1號機器和其餘i個機器,把全部方格染黑了的方案數
顯然序列裏會有1
把序列差分一下,會發現這個序列不是1就是2,也就是一堆1和一堆2的數目固定,一堆1和一堆2的和是N - 2,這是個二元一次方程,而後求出了1的個數和2的個數,就是個帶重複元素的全排列,答案是
\(\frac{(num_1 + num_2)! }{num_1 ! num_2 !}\)
其餘\(N - 1 - i\)臺機器隨意排列,最後還要乘上\((N - i - 1)!\)code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#define MAXN 1000005
#define PLI pair<long long,int>
#define fi first
#define se second
#define mp make_pair
#define ha 99994711
#define ba 823
#define MOD 1000000007
//#define ivorysi
using namespace std;
typedef long long int64;
int64 fac[MAXN],invfac[MAXN],ans;
int N;
int64 fpow(int64 x,int64 c) {
    int64 res = 1,t = x;
    while(c) {
        if(c & 1) res = res * t % MOD;
        t = t * t % MOD;
        c >>= 1;
    }
    return res;
}
int64 C(int n,int m) {
    if(n < m) return 0;
    return fac[n] * invfac[m] % MOD * invfac[n - m] % MOD;
}
int64 W(int k) {
    int x = N - 2 - k;
    int y = k - x;
    if(x < 0 || y < 0) return 0;
    return fac[k] * invfac[y] % MOD * invfac[x] % MOD;
}
void Solve() {
    scanf("%d",&N);
    if(N == 2) {
        puts("1");
        return;
    }
    fac[0] = invfac[0] = 1;
    for(int i = 1 ; i <= N ; ++i) {
        fac[i] = fac[i - 1] * i % MOD;
    }
    invfac[N] = fpow(fac[N],MOD - 2);
    for(int i = N - 1 ; i >= 1 ; --i) {
        invfac[i] = invfac[i + 1] * (i + 1) % MOD;
    }
    ans += fac[N - 1];
    for(int i = 1 ; i < N - 1 ; ++i) {
        ans += (C(N - 2,i) + C(N - 2,i - 1) + MOD - W(i - 1))* fac[i] % MOD * fac[N - 1 - i] % MOD;
        ans %= MOD;
    }
    printf("%lld\n",ans);
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
    return 0;
}

D - Go Home

題解

神仙博弈題(其實也不算博弈)
你看完題,發現,啥,最優決策,咋最優啊,啥最優啊,什麼玩意,棄療吧……
題解是這麼說的,若是汽車在1 和N中間,且\(A_1 >= A_n\),那麼N確定會投票給1,爲何,由於贏不了,若是暫時讓車靠近了N,最後也會由於N - 1公寓裏的人都回家了而車也再回到1,因此爲了早回家,N的票都會給1,且最後的操做必定會有一個\(X_{n} - X_{1}\)的長度,那麼咱們把N的票所有給1,\(P_{1} += P_{N}\)也沒有問題,若是\(A_{n} > A_{1}\)是相似的,以後就變成了1和N - 1之間的子問題……中止條件是全部公寓都在初始位置\(S\)的一邊
累加答案的話要注意和前一次操做方向相反才累加,若是前一次1 - N,下一次1 - (N - 1),第二次的距離不用累加進答案排序

代碼

#include <iostream>
#include <cstdio>
#define MAXN 100005
//#define ivorysi
using namespace std;
typedef long long int64;
typedef double db;
int N;
int64 S,X[MAXN],P[MAXN],ans;
void Solve() {
    scanf("%d%lld",&N,&S);
    for(int i = 1 ; i <= N ; ++i) {
        scanf("%lld%lld",&X[i],&P[i]);
    }
    int L = 1,R = N;
    int dir = 0;
    while(1) {
        if(X[L] >= S) {ans += X[R] - S;break;}
        if(X[R] <= S) {ans += S - X[L];break;}
        if(P[L] >= P[R]) {
            if(dir != 1) {dir = 1;ans += X[R] - X[L];}
            P[L] += P[R];R--;
        }
        else {
            if(dir != 2) {dir = 2;ans += X[R] - X[L];}
            P[R] += P[L];L++;
        } 
    }
    printf("%lld\n",ans);
}
 
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
    return 0;
}

E - Inversions

我以爲這道題計算排列的方法應該算個比較重要前置技能,但這又不是什麼板啥的,若是知道了這個子問題那麼這道題至少會有頭緒
如何計算每一個數位有限制的排列總個數?
先計算一個\(cnt[k]\)表示能夠填k的位置的個數,能夠用一個後綴和算出來
答案就是\(\prod_{i = 1}^{N} cnt[i] - (N - i)\)
有了這個咱們再來看這道題
咱們對於兩個位置\(i < j\)\(A_{i} <= A_{j}\)計算\(i\)位置上的數大於\(j\)位置上的數的排列的總和,咱們可讓\(A_{j} = A_{i}\),而後計算排列個數,再除二就是答案
如下的\(cnt[i]\)更改爲\(cnt[i] - (N - i)\)
這樣的話咱們考慮更改\(A_{j}\)會使得\([A_{i} + 1,A_{j}]\)這個區間的cnt減1,也就是乘上了\((cnt[i] - 1) / cnt[i]\),也就是處理成區間前綴乘積後相除,然而會發現處理0的狀況比較特殊,還須要記錄前綴出現了幾個0,前綴上0個數相同值纔不爲0,位置增長0的個數單調不減,因此能夠預處理出來每一段開始位置和結束位置
\(A_{i} > A_{j}\)咱們發現就是把\(A_{i} := A_{j}\)後總排列數減去計算出的新排列值的一半
以上兩種狀況均可以用樹狀數組快速維護get

代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 200005
//#define ivorysi
using namespace std;
typedef long long int64;
typedef double db;
const int MOD = 1000000007;
int N,A[MAXN],x[MAXN],st[MAXN],ed[MAXN];
int64 cnt[MAXN],V[MAXN],D[MAXN],ID[MAXN],S,tr[MAXN],tr_cnt[MAXN],Inv_2,ans;
int64 fpow(int64 x,int64 c) {
    int64 res = 1,t = x;
    while(c) {
        if(c & 1) res = res * t % MOD;
        t = t * t % MOD;
        c >>= 1;
    }
    return res;
}
int lowbit(int x) {return x & (-x);}
void insert(int x,int64 v) {
    while(x <= N) {
        tr[x] += v;
        tr[x] %= MOD;
        tr_cnt[x]++;
        x += lowbit(x);
    }
}
int64 Query(int x,int64 v) {
    int64 res = 0;
    while(x > 0) {
        res += tr[x];
        x -= lowbit(x);
    }
    res %= MOD;
    return res * v % MOD;
}
int64 getnum(int x) {
    int64 res = 0;
    while(x > 0) {
        res += tr_cnt[x];
        x -= lowbit(x);
    }
    return res;
}
int64 Query_range(int L,int R,int64 v) {
    if(L == 0) ++L;
    if(L > R) return 0;
    return (Query(R,v) + MOD - Query(L - 1,v)) % MOD;
}
void Solve() {
    scanf("%d",&N);
    for(int i = 1 ; i <= N ; ++i) scanf("%d",&A[i]),cnt[A[i]]++;
    for(int i = N - 1; i >= 1 ; --i) cnt[i] += cnt[i + 1];
    S = 1;Inv_2 = (MOD + 1) / 2;
    for(int i = 1 ; i <= N ; ++i) {
        cnt[i] -= N - i;
        if(cnt[i] <= 0) {puts("0");return;}
        S = S * cnt[i] % MOD;
    }
    for(int i = 1 ; i <= N ; ++i) {
        V[i] = (cnt[i] - 1) * fpow(cnt[i],MOD - 2) % MOD;
    }
    D[0] = 1;
    st[0] = 0;
    for(int i = 1 ; i <= N ; ++i) {
        x[i] = x[i - 1];D[i] = D[i - 1];
        if(V[i] == 0) {x[i]++;st[x[i]] = i;ed[x[i] - 1] = i - 1;}
        else D[i] = D[i] * V[i] % MOD;
        ID[i] = fpow(D[i],MOD - 2);
    }
    ed[x[N]] = N;
    for(int i = 1 ; i <= N ; ++i) {
        ans += Query_range(st[x[A[i]]],A[i],D[A[i]] * S % MOD * Inv_2 % MOD);
        ans %= MOD;
        insert(A[i],ID[A[i]]);
    }
    memset(tr,0,sizeof(tr));
    memset(tr_cnt,0,sizeof(tr_cnt));
    for(int i = 1 ; i <= N ; ++i) {
        int64 t = Query_range(A[i] + 1,ed[x[A[i]]],ID[A[i]] * S % MOD * Inv_2 % MOD);
        ans += ((getnum(N) - getnum(A[i])) * S % MOD + MOD - t) % MOD;
        ans %= MOD;
        insert(A[i],D[A[i]]);
    }
    printf("%lld\n",ans);
}
 
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
    return 0;
}

F - 01 on Tree

題解

這個是真的神仙題。。。
腦子裏一直是某個按子樹大小的貪心,而後一直被叉掉
最後說是初始化每一個節點就是一個單獨的樹,記錄這個樹裏1的個數和0,初始的時候1的個數即爲節點值是否爲1,0的個數即爲節點值是否爲0
咱們按照\(C_{0i}/C_{1i}\)排序,並把每一個點和直接父親接起來,意思就是把這個點的0和1接在父親節點的後面,多出來的貢獻便是
v是兒子,p是父親,\(C_{1p} * C_{0v}\)
推一下式子能夠證出來選別的兒子確定不優
而後更新父親p的值,也就是p變成了一個新的樹,這個能夠用並查集維護
而後帶更新的排序用一個set實現就能夠string

神仙題神仙題。。。hash

代碼

#include <iostream>
#include <cstdio>
#include <vector>
#include <set>
#include <cstring>
#define MAXN 200005
//#define ivorysi
using namespace std;
typedef long long int64;
typedef double db;
int N,P[MAXN],V[MAXN],fa[MAXN],C[2][MAXN];
int getfa(int x) {
    return fa[x] == x ? x : fa[x] = getfa(fa[x]);
}
struct cmp {
    bool operator() (const int &a,const int &b) {
        if(1LL * C[0][a] * C[1][b] != 1LL * C[0][b] * C[1][a])
            return 1LL * C[0][a] * C[1][b] > 1LL * C[0][b] * C[1][a];
        else return a > b;
    }
};
multiset<int,cmp> S;
void Solve() {
    scanf("%d",&N);
    for(int i = 2 ; i <= N ; ++i) scanf("%d",&P[i]);
    for(int i = 1 ; i <= N ; ++i) scanf("%d",&V[i]);
    for(int i = 1 ; i <= N ; ++i) fa[i] = i;
    for(int i = 1 ; i <= N ; ++i) C[V[i]][i] = 1;
    for(int i = 2 ; i <= N ; ++i) S.insert(i);
    int64 ans = 0;
    for(int i = 1 ; i <= N - 1 ; ++i) {
        int v = *S.begin();
        int p = getfa(P[v]);
        S.erase(v);S.erase(p);
        ans += 1LL * C[0][v] * C[1][p];
        C[1][p] += C[1][v];C[0][p] += C[0][v];
        fa[v] = p;
        if(p != 1) S.insert(p);
    }
    printf("%lld\n",ans);   
}
 
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    Solve();
    return 0;
}
相關文章
相關標籤/搜索