• 【学习笔记】AGC018


    AGC018

    Tree and Hamilton Path

    理论上界是 ∑ ( u , v ) 2 min ⁡ ( s v , n − s v ) w i \sum_{(u,v)}2\min(s_v,n-s_v)w_i (u,v)2min(sv,nsv)wi,这对于哈密顿回路是可以取到的(只需取原树重心,注意最后要回到根节点)。

    比较容易想到枚举终点 v v v(假设重心是 u u u),然后只需断掉一条边。应该是正确的吧(确信

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    using namespace std;
    int n,rt,f=0x3f3f3f3f,siz[100005];
    ll L;
    vector<pair<int,int>>g[100005];
    void dfs(int u,int topf){
    	siz[u]=1;int z=0;
    	for(auto v:g[u]){
    		if(v.fi!=topf){
    			dfs(v.fi,u),siz[u]+=siz[v.fi],z=max(z,siz[v.fi]);
    			L+=2ll*min(siz[v.fi],n-siz[v.fi])*v.se;
    		}
    	}if(max(z,n-siz[u])<f)f=max(z,n-siz[u]),rt=u;
    }
    void dfs2(int u,int topf){
    	siz[u]=1;
    	for(auto v:g[u]){
    		if(v.fi!=topf){
    			dfs2(v.fi,u),siz[u]+=siz[v.fi];
    		}
    	}
    }
    int main(){
    	cin>>n;for(int i=1;i<n;i++){
    		int u,v,w;cin>>u>>v>>w,g[u].pb(make_pair(v,w)),g[v].pb(make_pair(u,w));
    	}
    	dfs(1,0),dfs2(rt,0);int z=0x3f3f3f3f,son=0;
    	for(auto v:g[rt])if(!son||siz[v.fi]>siz[son])son=v.fi;
    	for(auto v:g[rt]){
    		if(v.fi==son||siz[son]<=(n-1)/2)z=min(z,v.se);
    	}cout<<L-z;
    }
    
    • 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

    AGC014

    Black and White Tree

    应该可以看出就是求一个匹配。如果存在匹配( n n n是偶数)那么显然后手必胜,否则先手一直选比叶子浅的那个节点即可。

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    using namespace std;
    int n,dp[100005];
    vector<int>g[100005];
    void dfs(int u,int topf){
    	dp[u]=1;
    	for(auto v:g[u]){
    		if(v!=topf){
    			dfs(v,u);
    			if(dp[v]){
    				if(!dp[u]){
    					cout<<"First";
    					exit(0);
    				}dp[u]=0;
    			}
    		}
    	}
    }
    int main(){
    	cin>>n;if(n&1){
    		cout<<"First";
    		return 0;
    	}
    	for(int i=1;i<n;i++){
    		int u,v;cin>>u>>v;
    		g[u].pb(v),g[v].pb(u);
    	}dfs(1,0);cout<<"Second";
    }
    
    • 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

    AGC028

    Removing Blocks

    答案是 ∑ i ∑ j n ! ∣ i − j ∣ + 1 a j \sum_i\sum_j\frac{n!}{|i-j|+1}a_j ijij+1n!aj。固定 ∣ i − j ∣ |i-j| ij然后预处理 a i a_i ai前缀和即可。

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    #define inf 0x3f3f3f3f
    using namespace std;
    const int mod=1e9+7;
    int n;
    ll res,fac[100005],a[100005],s[100005];
    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;
    }
    int main(){
    	ios::sync_with_stdio(false);
    	cin.tie(0),cout.tie(0);
    	cin>>n;for(int i=1;i<=n;i++)cin>>a[i],s[i]=(s[i-1]+a[i])%mod;fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
    	for(int i=1;i<=n;i++){
    		if(i!=1)res+=fac[n]*fpow(i,mod-2)%mod*(s[n]-s[i-1]+s[n-i+1])%mod;
    		else res+=fac[n]*s[n]%mod; 
    		res%=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
    • 25
    • 26
    • 27

    Min Cost Cycle

    长的很像结论题。

    问题可以转化为,对于每条边 ( u , v ) (u,v) (u,v),选取 a u , b v a_u,b_v au,bv中的任意值加入答案,这样不必考虑它们的大小关系。

    这可以形象表示为二分图的完美匹配,其中有 n n n个点产生贡献,每条边恰好对应一个产生贡献的点。

    我们观察到,如果不存在 a i , b i a_i,b_i ai,bi同时产生贡献,那么这张图就会分裂成两条哈密顿回路而不合法。否则一定合法,证明将 i i i作为回路起点遍历二分图,过程中不经过重复的点即可。

    因此合法的答案为:

    1.1 1.1 1.1 只包含 { a i } , { b i } \{a_i\},\{b_i\} {ai},{bi}
    1.2 1.2 1.2 至少存在一个 i i i,使得 a i a_i ai, b i b_i bi都对答案产生贡献。

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    using namespace std;
    int n,a[200005],b[200005],vis[200005];
    ll res,s1,s2;
    vector<pair<int,int>>v;
    int main(){
    	cin>>n;for(int i=1;i<=n;i++)cin>>a[i]>>b[i],v.pb(make_pair(a[i],i)),v.pb(make_pair(b[i],i+n)),s1+=a[i],s2+=b[i];
    	sort(v.begin(),v.end());
    	for(int i=0;i<n;i++)res+=v[i].fi,vis[v[i].se]=1;
    	for(int i=1;i<=n;i++){
    		if(vis[i]&&vis[i+n]){
    			cout<<min({res,s1,s2});
    			return 0;
    		}
    	}
    	ll res2=0x3f3f3f3f3f3f3f3f;
    	for(int i=1;i<=n;i++){
    		if(vis[i]){
    			if(i!=v[n-1].se)res2=min(res2,res+b[i]-v[n-1].fi);
    			else res2=min(res2,res+b[i]-v[n-2].fi);
    		}
    		else {
    			if(i+n!=v[n-1].se)res2=min(res2,res+a[i]-v[n-1].fi);
    			else res2=min(res2,res+a[i]-v[n-2].fi);
    		}
    	}
    	cout<<min({res2,s1,s2});
    }
    
    • 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

    Chords

    不太会做。

    AGC013

    Ants on a Circle

    如果不考虑掉头,那么蚂蚁位置组成的集合是可以确定的。并且顺时针方向蚂蚁的相对位置不会改变。

    假设圆足够大,而所有蚂蚁都集中在下部,那么 1 1 1的位置就是最终坐标最小的位置。

    假设某一时刻一个蚂蚁穿过了 L L L(相当于坐标减成了 0 0 0),那么变成了 { n , 1 , 2 , . . . , n − 1 } \{n,1,2,...,n-1\} {n,1,2,...,n1},因此只需计算环上被顺时针逆时针穿过的次数即可。

    比较抽象

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    using namespace std;
    int n;
    ll L,T;
    ll res;
    vector<int>v;
    int main(){
    	cin>>n>>L>>T;
    	for(int i=0;i<n;i++){
    		ll X,w;cin>>X>>w;
    		if(w==1){
    			v.pb((X+T)%L);
    			res+=(X+T)/L;
    		}
    		else{
    			v.pb(((X-T)%L+L)%L);
    			res-=(L-X+T-1)/L;
    		}
    	}
    	sort(v.begin(),v.end());
    	res=(res%n+n)%n;
    	for(int i=res;i<n;i++)cout<<v[i]<<"\n";
    	for(int i=0;i<res;i++)cout<<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

    AGC016

    Games on DAG

    很明显是状压的题目。

    d p i , S dp_{i,S} dpi,S表示当前 S G SG SG值考虑到 i i i,点集为 S S S的方案数。如果一个点不在 S S S中,那么隐含它的 S G SG SG值比 i i i大,因此对于 j ∉ S j\notin S j/S,记有 n u m j num_j numj条边指向 S G SG SG i i i的点,那么 d p i , S ← d p i , S × ∏ j ∉ S ( 2 n u m j − 1 ) dp_{i,S}\gets dp_{i,S}\times \prod_{j\notin S}(2^{num_j}-1) dpi,Sdpi,S×j/S(2numj1)

    最后统计 S G ( 1 ) ≠ S G ( 2 ) SG(1)\ne SG(2) SG(1)=SG(2)的方案数即可。复杂度 O ( n 3 3 n ) O(n^33^n) O(n33n)

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    #define inf 0x3f3f3f3f
    using namespace std;
    const int mod=1e9+7;
    int n,m,dp[1<<15],a[15][15];
    struct node{
    	int u,v;
    }e[1005];
    void add(int &x,int y){
    	if((x+=y)>=mod)x-=mod;
    }
    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;
    }
    int main(){
    	ios::sync_with_stdio(false);
    	cin.tie(0),cout.tie(0);
    	cin>>n>>m;for(int i=1;i<=m;i++){
    		int u,v;cin>>u>>v,u--,v--,a[u][v]=1;
    	}dp[0]=1;int res=0;
    	for(int i=0;i<n;i++){
    		for(int s=(1<<n)-1;s>=0;s--){
    			if(dp[s]){
    				for(int s2=(1<<n)-1-s;s2;s2=(s2-1)&((1<<n)-1-s)){
    					if((s2&1)&&(s2&2))continue;
    					ll v=dp[s];for(int j=0;j<n;j++){
    						if(!(s>>j&1)&&!(s2>>j&1)){
    							int num=0,num2=0;
    							for(int k=0;k<n;k++){
    								if(a[j][k]&&(s2>>k&1))num++;
    								if(a[k][j]&&(s2>>k&1))num2++;
    							}v=v*(fpow(2,num)-1)%mod*fpow(2,num2)%mod;
    						}
    					}add(dp[s+s2],v); 
    				}dp[s]=0;
    			}
    		}
    		add(res,dp[(1<<n)-1]);
    	}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
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    AGC017

    Game on Tree

    博弈论题目。

    S G ( u ) SG(u) SG(u)表示以 u u u为根的子树对应的 S G SG SG函数值。如果 u u u是叶子那么 S G ( u ) = 0 SG(u)=0 SG(u)=0

    我们不妨将 u u u的博弈问题视为所有子节点的问题的组合,也就是说,我们将 u u u相连的边以及 v v v的子树视为整体 S G ( u ) SG(u) SG(u)就等于其异或和。
    请添加图片描述
    进一步分析,如果我们不看与 u u u相连的那条边,那么其对应的就是 S G ( v ) SG(v) SG(v)。考虑这条边的意义是,随时可以把整棵子树删掉,因此随时可以转化为 S G ( v ′ ) → 0 SG(v')\to 0 SG(v)0,所以每个状态的 S G SG SG值都要加 1 1 1,故而 S G ( u ) = ⊕ v ∈ s o n ( u ) ( S G ( v ) + 1 ) SG(u)=\oplus_{v\in son(u)}(SG(v)+1) SG(u)=vson(u)(SG(v)+1)

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    #define inf 0x3f3f3f3f
    using namespace std;
    int n,dp[100005];
    vector<int>g[100005];
    void dfs(int u,int topf){
    	for(auto v:g[u]){
    		if(v!=topf)dfs(v,u),dp[u]^=dp[v]+1;
    	}
    }
    int 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);
    	}dfs(1,0);
    	if(dp[1])cout<<"Alice";
    	else cout<<"Bob";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    AGC009

    Uninity

    直接建点分树好像是最优的。

    题目可以等价于,对每个点标一个 lebal \text{lebal} lebal 记作 f i f_i fi,满足对于任意标号相同的点 u u u, v v v,其路径上存在一个点 w w w,满足 f w > f u f_w>f_u fw>fu 。要求 max ⁡ f i \max f_i maxfi的最小值。

    道理很简单,因为点分树可以看成一个类似 B F S BFS BFS树的结构。

    请添加图片描述
    然后回到原树上。任选一点作 D F S DFS DFS,确定 f ( u ) f(u) f(u)的步骤为,首先确定子树的 f f f值,然后对于无遮挡的标号 k k k,如果两颗子树有,那么 f ( u ) ≥ k f(u)\ge k f(u)k,如果一颗子树有,那么 f ( u ) ≠ k f(u)\ne k f(u)=k

    可以感性认识到其贪心得到的点分树是合法的,并且保证了 f ( u ) f(u) f(u)最小。

    #include
    #define ll long long
    #define fi first
    #define se second
    #define pb push_back
    #define inf 0x3f3f3f3f
    using namespace std;
    int n,ban[100005],lab[100005],res;
    vector<int>g[100005];
    void dfs(int u,int topf){
    	int s=0;
    	for(auto v:g[u]){
    		if(v!=topf){
    			dfs(v,u);
    			s|=ban[u]&ban[v];
    			ban[u]|=ban[v];
    		}
    	}int k=0;while((1<<k)<=s||ban[u]>>k&1){
    		if(ban[u]>>k&1)ban[u]-=1<<k;
    		k++;
    	}lab[u]=k,ban[u]|=1<<k;
    }
    int 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);
    	}dfs(1,0);
    	for(int i=1;i<=n;i++)res=max(res,lab[i]);
    	cout<<res;
    }
    
    • 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

    AGC024

    Sequence Growing Hard

    我爱计数题。

    事实上限制可以转述为,设当前删的数的连续块是 x x x,连续块后继是 y y y,那么 x > y x>y x>y

    如果给定 A n A_n An { A 0 , A 1 , . . . , A n − 1 } \{A_0,A_1,...,A_{n-1}\} {A0,A1,...,An1}的方案数,那么等价于删数的方案数,把每个连续段抽象为 ( v a l , x ) (val,x) (val,x),那么能将第 i i i段的最右边的数删去的条件是 v a l i > v a l i + 1 val_i>val_{i+1} vali>vali+1

    d p i , j dp_{i,j} dpi,j表示第 i i i个序列,只用了 [ 1 , j ] [1,j] [1,j]的整数,每次删一个数,满足 v a l i > v a l i + 1 val_i>val_{i+1} vali>vali+1的方案数。类似于笛卡尔树的思想,我们枚举第一个 1 1 1的位置 k k k,以及第 l l l个被删除,那么左边相当于 d p k − 1 , j − 1 dp_{k-1,j-1} dpk1,j1(不能出现 1 1 1),右边相当于 d p i − k , j dp_{i-k,j} dpik,j,并且只有当右边全部删完后才能删 k k k,所以乘上组合数 ( l − 1 i − k ) \binom{l-1}{i-k} (ikl1)

    所以 d p i , j = ∑ k = 1 i ∑ l = 1 i d p k − 1 , j − 1 × d p i − k , j × ( l − 1 i − k ) dp_{i,j}=\sum_{k=1}^i\sum_{l=1}^idp_{k-1,j-1}\times dp_{i-k,j}\times \binom{l-1}{i-k} dpi,j=k=1il=1idpk1,j1×dpik,j×(ikl1)。预处理组合数,复杂度 O ( n 3 ) O(n^3) O(n3)

    remark \text{remark} remark 这个状态和转移确实比较抽象,反正我肯定想不出来。 感性理解一下吧。

    #include
    #define fi first
    #define se second
    #define ll long long
    #define pb push_back
    #define inf 0x3f3f3f3f3f3f3f3f
    using namespace std;
    int n,K,mod,c[305][305];
    ll dp[305][305];
    void init(int n){
    	for(int i=0;i<=n;i++)c[i][0]=1;
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    		}
    	}
    }
    int main(){
    	cin>>n>>K>>mod,init(300);
    	for(int i=0;i<=K;i++)dp[0][i]=1;
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=K;j++){
    			dp[i][j]=dp[i][j-1];
    			for(int k=1;k<=i;k++){
    				dp[i][j]=(dp[i][j]+dp[k-1][j-1]*dp[i-k][j]%mod*c[i][i-k+1]%mod)%mod;
    			}
    		}
    	}cout<<dp[n][K];
    }
    
    • 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
  • 相关阅读:
    C++ 多线程
    循环生成el-descriptions-item
    Spring Boot Bean 注入的常用方式教程
    [算法刷题笔记]二叉树练习(2):对称二叉树有关的练习
    数据结构哈希表(散列)Hash,手写实现(图文推导)
    C++ - 开放地址法的哈希介绍 - 哈希表的仿函数例子
    【ShardingSphere】单实例模式创建分片表、广播表、单表
    ConfigMap概述
    得心应手应对 OOM 的疑难杂症
    【Qt开发流程】之富文本处理
  • 原文地址:https://blog.csdn.net/cqbzlydd/article/details/127793278