【学习笔记】AGC部分题目01

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<bits/stdc++.h>
#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;
}

AGC014

Black and White Tree

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

#include<bits/stdc++.h>
#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";
}

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<bits/stdc++.h>
#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;
}

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<bits/stdc++.h>
#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});
}

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<bits/stdc++.h>
#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";
}

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<bits/stdc++.h>
#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;
}

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<bits/stdc++.h>
#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";
}

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<bits/stdc++.h>
#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;
}

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<bits/stdc++.h>
#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];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值