zoukankan      html  css  js  c++  java
  • 【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
    ",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
    ",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而没有全部染黑的方案数是(inom{N - 2}{i}i!)
    如果用了N - 1,那么方案是((inom{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
    ",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
    ",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
    ",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
    ",ans);	
    }
     
    int main() {
    #ifdef ivorysi
        freopen("f1.in","r",stdin);
    #endif
        Solve();
        return 0;
    }
    
  • 相关阅读:
    PC software
    pdf.js查看器 在线预览 office文件、图片、文本文件
    核算两个时间之间时长
    Flink sql 之 join 与 StreamPhysicalJoinRule (源码解析)
    Flink sql 之 TopN 与 StreamPhysicalRankRule (源码解析)
    Flink Sql 之 Calcite Volcano优化器(源码解析)
    Keepalive高可用部署
    Keepalive配置文件
    Erlang环境安装
    ZooKeeper集群
  • 原文地址:https://www.cnblogs.com/ivorysi/p/8984795.html
Copyright © 2011-2022 走看看