【AtCoder】AGC023 A-F题解

可以说是第一场AGC了,做了三道题之后还有30min,杠了一下D题发现杠不出来,三题滚粗了

rating起步1300+,感觉还是很菜。。。

只有三题水平显然以后还会疯狂--啊(CF的惨痛经历)

改题的感觉似乎还不错因为思维都非常的妙(我根本想不到)

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)的,然后过掉了

#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)!\)

#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}\)后总排列数减去计算出的新排列值的一半

以上两种情况都可以用树状数组快速维护

代码

#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实现就可以

神仙题神仙题。。。

代码

#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;
}

原文地址:https://www.cnblogs.com/ivorysi/p/8984795.html

时间: 2024-10-30 13:43:45

【AtCoder】AGC023 A-F题解的相关文章

AtCoder Beginner Contest 154 题解

人生第一场 AtCoder,纪念一下 话说年后的 AtCoder 比赛怎么这么少啊(大雾 AtCoder Beginner Contest 154 题解 A - Remaining Balls We have A balls with the string S written on each of them and B balls with the string T written on each of them. From these balls, Takahashi chooses one

【题解】 AtCoder ARC 076 F - Exhausted? (霍尔定理+线段树)

题面 题目大意: 给你\(m\)张椅子,排成一行,告诉你\(n\)个人,每个人可以坐的座位为\([1,l]\bigcup[r,m]\),为了让所有人坐下,问至少还要加多少张椅子. Solution: 为什么加椅子?我们可以在最左边或最右边一直加直到人人都有座位. 首先这道题目抽象成二分图很简单,然后我们可以只要求解出人与座位的最大匹配是多少,总人数减去即可,但跑二分图最大匹配显然会超时,我们就可以往霍尔定理方面想. 然后你还需要知道一个霍尔定理推论:假设某个人的集合为\(X\),这个集合所对应的

【ATcoder】AtCoder Beginner Contest 161 题解

题目链接:AtCoder Beginner Contest 161 原版题解链接:传送门 A - ABC Swap 这题太水,直接模拟即可. 1 #include <iostream> 2 using namespace std; 3 int main() { 4 int a, b, c; 5 cin >> a >> b >> c; 6 swap(a, b); 7 swap(a, c); 8 cout << a << " &

【AtCoder】ARC082 F - Sandglass

[链接]F - Sandglass [题意]给定沙漏A和B,分别装着a和X-a的沙子,开始时A在上B在下,每秒漏1,漏完不再漏.给定n,有n个时刻ai沙漏倒转.给定m个询问,每次询问给定初值a和时刻t,求A中沙子量. [算法]数学(函数) [题解] 先不考虑时刻,令ft(a)表示沙子初值a时,当前A中的沙子数.(x轴是初值a,y轴是沙子数num) 时刻为0时,显然是一条从0出发斜率为1的直线. 若A在上,则每过1s,整段函数都下移一个单位,碰到y=0则变成平的. 若A在下,则每过1s,整段函数都

【AtCoder】ARC067 F - Yakiniku Restaurants 单调栈+矩阵差分

[题目]F - Yakiniku Restaurants [题意]给定n和m,有n个饭店和m张票,给出Ai表示从饭店i到i+1的距离,给出矩阵B(i,j)表示在第i家饭店使用票j的收益,求任选起点和终点的最大(收益-代价).n<=5000,m<=200. [算法]单调栈+矩阵差分 [题解]直接枚举区间,很难同时计算m张票,我们反过来考虑每个B(i,j)的贡献. 对于B(i,j),令x为满足x<i,B(x,j)>B(i,j)的最大的x,令y为满足y>i,B(y,j)>B(

【AtCoder】AGC005 F - Many Easy Problems 排列组合+NTT

[题目]F - Many Easy Problems [题意]给定n个点的树,定义S为大小为k的点集,则f(S)为最小的包含点集S的连通块大小,求k=1~n时的所有点集f(S)的和取模924844033.n<=2*10^5. [算法]排列组合+NTT [题解]考虑每个点只会在k个点都在其一个子树时无贡献,即: $$ANS_k=\sum_{x=1}^{n}\binom{n}{k}-\sum_{y}\binom{sz[y]}{k}+\binom{n-sz[y]}{k}$$ 令$cnt_i$表示满足s

【AtCoder】ARC096 C-F题解

听说日本题思维都很棒,去涨涨智商qwq C - Half and Half 题解 枚举买多少个AB披萨也行 但是关于买x个AB披萨最后的总花费是个单峰函数,可以三分 这题有点像六省联考2017D1T1送分题期末考试 代码 #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #define MAXN 100005 #define PLI pair<lo

【AtCoder】ARC099 F - Eating Symbols Hard

题解 一道神奇的题 我们把操作S构成的A数组用一个多项式表示出来 \(t(S) = \sum_{i = -10^9}^{10^9} A_{i}X^{i}\) 如果往S前面添加一个字符的话 \(t<(S) = t(S)X^{-1}\) \(t>(S) = t(S)X\) \(t+(S) = t(S) + 1\) \(t-(S) = t(S) - 1\) 那么我们对于最终的序列求一个哈希值c,如果一段区间操作后的结果和c一样的话就有 \(t_{S_i}t_{S_{i + 1}}...t_{S_j}

AtCoder Beginner Contest 115 题解

题目链接:https://abc115.contest.atcoder.jp/ A Christmas Eve Eve Eve 题目: Time limit : 2sec / Memory limit : 1024MB Score : 100 points Problem Statement In some other world, today is December D-th. Write a program that prints Christmas if D=25, Christmas E

AtCoder Grand Contest 007题解

传送门 \(A\) 咕咕咕 //quming #include<bits/stdc++.h> #define R register #define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i) #define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i) #define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v) template<