• 【学习笔记】AGC009/AGC019/AGC029/AGC035


    AGC009

    Eternal Average

    奥妙重重

    如果我们用树的结构来描述操作,设一个点的深度为 x i x_i xi,那么一定满足 ∑ k − x i = 1 \sum k^{-x_i}=1 kxi=1,并且 Z = ∑ a i = 1 k − x i Z=\sum_{a_i=1}k^{-x_i} Z=ai=1kxi

    方便起见,我们引入 k k k进制小数的概念。设 Z = 0. z 1 z 2 . . . z n Z=0.z_1z_2...z_n Z=0.z1z2...zn,其中 z i z_i zi表示 k − i k^{-i} ki z n ≠ 0 z_n\ne 0 zn=0

    那么合法的小数部分 { z i } \{z_i\} {zi}应该满足:

    1.1 1.1 1.1 如果不进位,那么 ∑ i = 1 n z i = n \sum_{i=1}^n z_i= n i=1nzi=n
    1.2 1.2 1.2 如果进位,那么 ∑ i = 1 n z i ≤ n \sum_{i=1}^nz_i\le n i=1nzin ,并且每次进位会导致数位之和减去 k − 1 k-1 k1 ∑ i = 1 n z i ≡ n ( m o d k − 1 ) \sum_{i=1}^n z_i\equiv n\pmod {k-1} i=1nzin(modk1)

    假设上述限制是充要的。换句话说,我们只关注 Z Z Z最终的形式,而不关注具体的方案。

    不妨做一些逆向思考。知道了 Z Z Z的最终形式后,通过不断拆分高位可以得到恰好 n n n个数,而根据这 n n n个点的深度又可以把实际的操作树求出来。具体操作是,从根节点开始分裂,在每一层保留恰当的点数,不难自行验证。扯得有点远了

    最后不要忘了 ∑ k − x i = 1 \sum k^{-x_i}=1 kxi=1这个条件,事实上这等价于 ∑ a i = 0 k − x i = 1 − Z = 0. ( k − 1 − z 1 ) ( k − 1 − z 2 ) . . . ( k − z n ) \sum_{a_i=0}k^{-x_i}=1-Z=0.(k-1-z_1)(k-1-z_2)...(k-z_n) ai=0kxi=1Z=0.(k1z1)(k1z2)...(kzn) ,根据上述推导得出 1 + ∑ i = 1 n ( k − 1 − z i ) ≤ m 1+\sum_{i=1}^n(k-1-z_i)\le m 1+i=1n(k1zi)m并且 1 + ∑ i = 1 n ( k − 1 − z i ) ≡ m ( m o d k − 1 ) 1+\sum_{i=1}^n(k-1-z_i)\equiv m\pmod {k-1} 1+i=1n(k1zi)m(modk1)

    让我们总结一下。设 d p i , j dp_{i,j} dpi,j表示当前从高到低考虑到第 i i i位,数位之和为 j j j的方案数。转移方程为 d p i , j = ∑ d p i − 1 , j − k dp_{i,j}=\sum dp_{i-1,j-k} dpi,j=dpi1,jk ,可以前缀和优化。因为 z n ≠ 0 z_n\ne 0 zn=0所以可能要用 0 / 1 0/1 0/1记录一下当前这位的状态。

    复杂度 O ( n 2 ) O(n^2) O(n2)

    #include
    #define ll long long
    #define pb push_back
    #define fi first
    #define se second
    #define db double
    #define inf 0x3f3f3f3f3f3f3f3f
    using namespace std;
    const int mod=1e9+7;
    int n,m,K;
    ll dp[2005][2005][2],sum[2005][2005],res;
    void add(ll &x,ll y){x=(x+y)%mod;}
    int main(){
    	cin>>n>>m>>K;
    	dp[0][0][0]=1;for(int i=0;i<=n;i++)sum[0][i]=1;
    	for(int i=1;i<=(n+m-1)/(K-1);i++){
    		for(int j=0;j<=n;j++){
    			add(dp[i][j][0],dp[i-1][j][0]+dp[i-1][j][1]);
    			if(min(K-1,j)==j)add(dp[i][j][1],sum[i-1][j-1]);
    			else add(dp[i][j][1],sum[i-1][j-1]-sum[i-1][j-min(K-1,j)-1]);
    			if(j%(K-1)==n%(K-1)&&i*(K-1)-j+1<=m&&(i*(K-1)-j+1)%(K-1)==m%(K-1))add(res,dp[i][j][1]);
    		}sum[i][0]=1;for(int j=1;j<=n;j++)sum[i][j]=(sum[i][j-1]+dp[i][j][0]+dp[i][j][1])%mod;
    	}cout<<(res+mod)%mod;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    AGC019

    Yes or No

    组合数学题。

    因为本人数学比较菜所以只会组合意义啦。

    方便起见,我们引入坐标 ( x , y ) (x,y) (x,y)表示当前状态。那么对于一条路径,猜对的问题数目就是蓝边的数量。

    请添加图片描述
    如果这条路径多次穿过 y = x y=x y=x,那么我们只保留第一个交点,并对路径作对称,这样蓝边的数目并不会改变。

    请添加图片描述
    设交点坐标是 ( x , x ) (x,x) (x,x),发现蓝边的数目 ( n − x ) + x = n (n-x)+x=n (nx)+x=n是定值!最后我们认为对角线的贡献是路径与对角线的交点数目/2

    答案是 n + ∑ i = 1 m ( 2 i i ) ( n + m − 2 i n − i ) 2 ( n + m n ) n+\frac{\sum_{i=1}^m\binom{2i}{i}\binom{n+m-2i}{n-i}}{2\binom{n+m}{n}} n+2(nn+m)i=1m(i2i)(nin+m2i)

    复杂度 O ( n ) O(n) O(n)

    remark \text{remark} remark 妙处在于发现潜在的组合意义。

    #include
    #define ll long long
    #define pb push_back
    #define fi first
    #define se second
    #define db double
    #define inf 0x3f3f3f3f3f3f3f3f
    using namespace std;
    const int mod=998244353;
    int n,m;
    ll fac[1000005],inv[1000005],res;
    ll fpow(ll x,ll y){
    	ll z(1);
    	for(;y;y>>=1){
    		if(y&1)z=z*x%mod;
    		x=x*x%mod;
    	}return z;
    }
    void init(int n){
    	fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    	inv[n]=fpow(fac[n],mod-2);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
    }
    ll binom(int x,int y){
    	return fac[x]*inv[y]%mod*inv[x-y]%mod;
    }
    int main(){
    	ios::sync_with_stdio(false);
    	cin.tie(0),cout.tie(0);
    	cin>>n>>m;if(n<m)swap(n,m);init(2*n);
    	for(int i=1;i<=m;i++)res=(res+binom(2*i,i)*binom(n+m-2*i,n-i)%mod)%mod;
    	cout<<(res*fpow(binom(n+m,n),mod-2)%mod*fpow(2,mod-2)%mod+n)%mod;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    AGC029

    Wandering TKHS

    考虑 u u u到根节点的路径为 { t 1 , t 2 , . . . , t k } \{t_1,t_2,...,t_k\} {t1,t2,...,tk},当从 t i t_i ti走到 t i + 1 t_{i+1} ti+1时,会扩展子树内比 t i + 1 t_{i+1} ti+1小的节点。因此想到设 d p i dp_i dpi表示从 i i i走到 f a i fa_i fai时,子树内扩展了多少个点。

    请添加图片描述

    F ( u , v ) F(u,v) F(u,v)表示从 u u u v v v的路径上不包括 v v v的最大的点,那么 v ∈ s u b t r e e ( u ) v\in subtree(u) vsubtree(u)有贡献等价于 F ( v , u ) < F ( 1 , u ) F(v,u)F(v,u)<F(1,u) 。注意到 F ( 1 , u ) F(1,u) F(1,u)是定值,因此只需维护 F ( v , u ) F(v,u) F(v,u)

    考虑自底向上维护,假设 x ∈ s o n ( u ) x\in son(u) xson(u),对于小于 x x x的路径直接设为 x x x,用 ( v a l , c ) (val,c) (val,c)记录权值为 v a l val val的路径有 c c c条,可以手写平衡树,复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n) 。当然,线段树合并是更优秀的写法,复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

    #include
    #define ll long long
    #define pb push_back
    #define fi first
    #define se second
    #define db double
    #define inf 0x3f3f3f3f3f3f3f3f
    using namespace std;
    int n,vis[200005],dep[200005],res[200005],dis[200005],rt[200005],dp[200005],f[200005],tot;
    struct node{
    	int l,r,sum;
    }t[20000005];
    vector<int>g[200005];
    int ask(int p,int l,int r,int ql,int qr){
    	if(!p)return 0;
    	if(ql<=l&&r<=qr)return t[p].sum;
    	int mid=l+r>>1;
    	if(qr<=mid)return ask(t[p].l,l,mid,ql,qr);
    	if(mid<ql)return ask(t[p].r,mid+1,r,ql,qr);
    	return ask(t[p].l,l,mid,ql,qr)+ask(t[p].r,mid+1,r,ql,qr);
    }
    void ins(int &p,int l,int r,int x,int y){
    	if(!p)p=++tot;t[p].sum+=y;
    	if(l==r)return;
    	int mid=l+r>>1;
    	x<=mid?ins(t[p].l,l,mid,x,y):ins(t[p].r,mid+1,r,x,y);
    }
    void del(int &p,int l,int r,int ql,int qr){
    	if(!p)return;
    	if(ql<=l&&r<=qr){p=0;return;}
    	int mid=l+r>>1;
    	if(ql<=mid)del(t[p].l,l,mid,ql,qr);if(mid<qr)del(t[p].r,mid+1,r,ql,qr);
    	t[p].sum=t[t[p].l].sum+t[t[p].r].sum;
    }
    int merge(int p,int q){
    	if(!p||!q)return p+q;
    	t[p].sum+=t[q].sum;
    	t[p].l=merge(t[p].l,t[q].l),t[p].r=merge(t[p].r,t[q].r);
    	return p; 
    }
    void dfs(int u,int topf){
    	for(auto v:g[u]){
    		if(v!=topf){
    			dis[v]=max(dis[u],u),dep[v]=dep[u]+1,dfs(v,u);
    			int tot=ask(rt[v],1,n,1,v-1);del(rt[v],1,n,1,v-1),ins(rt[v],1,n,v,tot+1);
    			dp[u]+=(f[v]=ask(rt[v],1,n,1,dis[u]-1)),rt[u]=merge(rt[u],rt[v]);
    		}
    	}
    }
    void dfs2(int u,int topf,int sum){
    	res[u]=sum+dp[u];
    	for(auto v:g[u]){
    		if(v!=topf)dfs2(v,u,sum+dp[u]-f[v]);
    	}
    }
    signed main(){
    	ios::sync_with_stdio(false);
    	cin.tie(0),cout.tie(0);
    	cin>>n;for(int i=1;i<n;i++){
    		int u,v;cin>>u>>v,g[u].pb(v),g[v].pb(u);
    	}for(auto u:g[1])dfs(u,1);for(auto u:g[1])dfs2(u,1,0);
    	for(int i=2;i<=n;i++)cout<<res[i]+dep[i]+1<<' ';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    Construction of a tree

    神仙构造题。

    我们首先构造二分图,左边是 n n n个节点,右边是 n − 1 n-1 n1个集合, ( u , E i ) (u,E_i) (u,Ei)有边当且仅当 u ∈ E i u\in E_i uEi

    然后有解的必要条件是,对于右边任意 S ⊆ { E 1 , E 2 , . . . , E n − 1 } S\subseteq\{E_1,E_2,...,E_{n-1}\} S{E1,E2,...,En1},设其对应的点集为 f ( S ) f(S) f(S),都有 ∣ S ∣ < f ( S ) |S|S<f(S),否则一定会形成环。

    事实上这是充要的。给出如下构造。

    首先求一个大小为 n − 1 n-1 n1的匹配。钦定左边没有被匹配的 v v v为根节点,从 v v v开始跑 D F S DFS DFS树(首先把之前的匹配加进 D F S DFS DFS树中),对于右边集合相连的两个点就在原树上连一条边。如果过程中途停止的话,当且仅当,对于所有左侧经过的点,与它们相邻的右边的点都经历过了,并且没有扩展出新点。

    因为此时左边点多一个 v v v,所以左边点数比右边点数多 1 1 1,那么取右边边未经过的点, 它们在左侧对应的点也是未被经过的,那么 f ( S ) = ∣ S ∣ f(S)=|S| f(S)=S,矛盾。

    比较复杂。感性理解一下就好。

    请添加图片描述

    复杂度 O ( n n ) O(n\sqrt{n}) O(nn ) 。瓶颈在于 dinic \text{dinic} dinic求最大匹配。

    #include
    #define ll long long
    #define pb push_back
    #define fi first
    #define se second
    #define db double
    #define inf 0x3f3f3f3f3f3f3f3f
    using namespace std;
    const int N=2e6+5;
    int n,m,vis[N],head[N],nxt[N],to[N],cur[N],lab[N],cap[N],tot=1,v;
    int U[200005],V[200005];
    queue<int>Q;
    void add(int u,int v,int w){
    	to[++tot]=v,cap[tot]=w,nxt[tot]=head[u],head[u]=tot;
    	to[++tot]=u,cap[tot]=0,nxt[tot]=head[v],head[v]=tot;
    }
    bool BFS(){
    	for(int i=0;i<2*n;i++)lab[i]=-1;
    	Q.push(2*n);
    	while(Q.size()){
    		int u=Q.front();Q.pop();
    		for(int k=head[u];k;k=nxt[k]){
    			int v=to[k];
    			if(lab[v]==-1&&cap[k^1]>0)lab[v]=lab[u]+1,Q.push(v);
    		}
    	}return lab[0]!=-1;
    }
    int flow(int u,int lim){
    	if(u==2*n)return lim;
    	int used=0;
    	for(int k=cur[u];k;k=nxt[k]){
    		cur[u]=nxt[k];
    		int v=to[k];
    		if(lab[u]==lab[v]+1&&cap[k]){
    			int tmp=min(lim-used,cap[k]);
    			int ret=flow(v,tmp);
    			cap[k]-=ret,cap[k^1]+=ret,used+=ret;
    			if(used==lim)break;
    		}
    	}
    	return used;
    }
    int dinic(){
    	int tot=0;
    	while(BFS()){
    		for(int i=0;i<=2*n;i++)cur[i]=head[i];
    		tot+=flow(0,0x3f3f3f3f);
    	}
    	return tot;
    }
    void dfs(int u){
    	for(int k=head[u];k;k=nxt[k]){
    		if(to[k]==0||vis[to[k]-n])continue;
    		int id=to[k]-n;
    		vis[id]=1;
    		for(int l=head[to[k]];l;l=nxt[l]){
    			if(cap[l])m++,U[id]=u,V[id]=to[l],dfs(to[l]);
    		}
    	}
    }
    signed main(){
    	ios::sync_with_stdio(false);
    	cin.tie(0),cout.tie(0);
    	cin>>n;
    	for(int i=1;i<=n;i++)add(0,i,1);for(int i=n+1;i<2*n;i++)add(i,2*n,1);
    	for(int i=1;i<n;i++){
    		int k;cin>>k;
    		while(k--){
    			int x;cin>>x,add(x,n+i,1);
    		}
    	}
    	if(dinic()!=n-1)cout<<"-1";
    	else{
    		for(int k=head[0];k;k=nxt[k]){
    			if(cap[k])v=to[k];
    		}
    		dfs(v);
    		if(m!=n-1)cout<<"-1";
    		else for(int i=1;i<n;i++)cout<<U[i]<<' '<<V[i]<<"\n";
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    AGC035

    Add and Remove

  • 相关阅读:
    【JavaEE】JavaScript webAPI的基本知识
    异常:找不到匹配的key exchange算法
    【C++从0到王者】第三十七站:模拟unordered_map和unordered_set
    Redission 使用Jackson处理LocalDateTime的一些坑
    2022-2023年度的AMC数学竞赛报名时间来了
    Vue路由 replace属性 控制浏览记录不能前进或后退
    计算机组成体系
    Scala入门到精通(尚硅谷学习笔记)章节六——流程控制
    【JavaWeb】EL表达式
    C# 语法分析器(二)LR(0) 语法分析
  • 原文地址:https://blog.csdn.net/cqbzlydd/article/details/127722508