AtCoder Grand Contest 008

本文深入探讨了几种不同的算法问题,包括根据绝对值进行加法操作、连续涂装问题的最优化策略、四色问题的简化分析以及堆在解决K-th元素问题中的应用。通过具体实例和代码实现,展示了算法在解决复杂问题时的智慧和效率。
摘要由CSDN通过智能技术生成

正题

A - Simple Calculator

根据绝对值来决定加还是变成负值之后加就可以了。

#include<bits/stdc++.h>
using namespace std;

int A,B,ans=0;

int main(){
	scanf("%d %d",&A,&B);
	if(abs(A)<abs(B)){
		if(A<0) ans++,A=-A;
		ans+=abs(B)-abs(A);
		A+=abs(B)-abs(A);
	}
	else if(abs(A)>abs(B)){
		if(A>0) ans++,A=-A;
		
		ans+=abs(A)-abs(B);
		A+=abs(A)-abs(B);
	}
	if(A!=B) ans++;
	printf("%d\n",ans);
}

B - Contiguous Repainting

既然操作了,那么就考虑最后一次操作在那里,另外的位置想选想不选都可以。

#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int n,k,a[N];
long long f[N],g[N],sum[N];

int main(){
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
	for(int i=1;i<=n;i++) f[i]=f[i-1]+max(a[i],0);
	for(int i=n;i>=1;i--) g[i]=g[i+1]+max(a[i],0);
	long long ans=0;
	for(int i=1;i<=n-k+1;i++) 
		ans=max(ans,f[i-1]+g[i+k]+max(sum[i+k-1]-sum[i-1],0ll));
	printf("%lld\n",ans);
}

C - Tetromino Tiling

发现只有 1 , 2 , 4 , 5 1,2,4,5 1,2,4,5有用,其他的必定会多出两个头没办法消除。
2 2 2就不用考虑了,直接加到答案里面。
1 , 4 , 5 1,4,5 1,4,5可以组合成一个 2 × ( 2 × 3 ) 2\times (2\times 3) 2×(2×3)的。
自己也可以跟自己组合成一个 2 × ( 2 × 2 ) 2\times (2\times 2) 2×(2×2)的。
那么我们让每一个都至少留一个出来先,然后考虑是否都剩下了,而且 = 2 = 2 =2的是否小于等于 1 1 1个,如果是的话,那么就组成一个 2 × ( 2 × 3 ) 2\times (2\times 3) 2×(2×3)的,否则就让 = 2 =2 =2的自己组成 2 × ( 2 × 2 ) 2\times (2\times 2) 2×(2×2)的。

#include<bits/stdc++.h>
using namespace std;

int a,b,c,d,e,f,g;
long long ans=0;

int main(){
	scanf("%d %d %d %d %d %d %d",&a,&b,&c,&d,&e,&f,&g);
	ans+=b;
	ans+=(a-1)/2*2;a=(a-1)%2+1;
	ans+=(d-1)/2*2;d=(d-1)%2+1;
	ans+=(e-1)/2*2;e=(e-1)%2+1;
	int tot=(a==2)+(d==2)+(e==2);
	if(tot<=1 && a && d && e) ans+=(a && d && e)*3;
	else ans+=tot*2;
	printf("%lld\n",ans);
}

D - K-th K

根据位置要求来贪心放就可以了,使用堆来维护。

#include<bits/stdc++.h>
using namespace std;

const int N=510;
int n,a[N],d[N*N],las[N];
priority_queue<pair<int,int> > qs;
pair<int,int> X;

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		d[a[i]]=i;
		if(i>1) qs.push(make_pair(-a[i],i)),las[i]=i-1;
	}
	bool tf=true;
	for(int i=1;i<=n*n;i++){
		if(d[i]){
			if(las[d[i]]) {tf=false;break;}
			if(d[i]!=n) qs.push(make_pair(-n*n,d[i])),las[d[i]]=n-d[i];
		}
		else {
			if(qs.empty()){tf=false;break;}
			X=qs.top();
			las[X.second]--;
			d[i]=X.second;
			if(las[X.second]==0) qs.pop();			
		}
	}
	if(!tf) printf("No\n");
	else{
		printf("Yes\n");
		for(int i=1;i<=n*n;i++) printf("%d ",d[i]);
	}
}

E - Next or Nextnext

很容易先考虑答案的置换,再考虑 a i a_i ai在上面的位置,但是这样考虑很难算。
正难则反。
考虑对于 a i a_i ai来说,是多个基环内向树。
对于单纯的环来说,可以发现让两个相等长度的环交错套在一起是可行的,自己形成一个单独的环也可以,这部分可以使用组合数暴力算出,注意当环长为奇数的时候,自己套在一起有两种方案,因为奇环上不断 + 2 +2 +2也是可以遍历所有位置的。
对于基环上长出一些边的点来说,这些点形成的一定是一条链,不然无解。
如果是一条链的话,观察一下是否可以将它交错插入环中,如果距离上一个有出边点的长度为 l l l,当前链的长度为 l e n len len,若 l e n > l len>l len>l,那么无法插入,若 l e n = l len=l len=l,那么刚好可以插入,若 l e n < l len<l len<l,说明还可以先空出一个点出来不插入,即存在两种方案。
那么根据乘法原理乘一乘就做完了。

#include<bits/stdc++.h>
using namespace std;

const int N=100010,mod=1000000007;
struct edge{
	int y,nex;
}s[N];
int first[N],len=0,a[N],n,d[N],tf[N],tag,tot[N];
int fac[N],inv[N];
vector<int> V;

int ksm(int x,int t){
	int tot=1;
	while(t){
		if(t&1) tot=1ll*tot*x%mod;
		x=1ll*x*x%mod;
		t/=2;
	}
	return tot;
}

void ad(int&x,int y){x=(x+y>=mod)?(x+y-mod):(x+y);}

void ins(int x,int y){
	d[x]++;s[++len]=(edge){y,first[x]};first[x]=len;
}

void dfs(int x){
	if(tf[x]){
		if(tf[x]==tag) V.push_back(x);
		return ;
	}
	tf[x]=tag;
	dfs(a[x]);
	if(V.size()){
		if(tag){
			if(V[0]!=x) V.push_back(x);
			else tag=0;
		}
	}
}

int gas(int x,int fa){
	if(d[x]>=2) return 1e9;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa)
		return gas(s[i].y,x)+1;
	return 0;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),ins(a[i],i);
	int ans=1;
	for(int i=1;i<=n;i++) if(!tf[i]){
		V.clear();
		tag=i,dfs(i);
		if(!V.size()) continue;
		int las=0;
		for(int j=0;j<V.size();j++) if(d[V[j]]==1) las++;
		else break;
		if(las==V.size()) {tot[las]++;continue;}las++;
		for(int j=V.size()-1;j>=0;j--) if(d[V[j]]>1){
			if(d[V[j]]>2) {ans=0;break;}
			int nex=s[first[V[j]]].y;
			if(nex==(j==V.size()-1?V[0]:V[j+1])) nex=s[s[first[V[j]]].nex].y;
			int tmp=gas(nex,V[j])+1;
			if(tmp>las) {ans=0;break;}
			if(tmp<las) ad(ans,ans);
			las=1;
		}
		else las++;
	}
	fac[0]=1;for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
	inv[n]=ksm(fac[n],mod-2);for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++) if(tot[i]){
		int tmp=0,op=1,po=1;
		for(int k=0;2*k<=tot[i];k++)
			tmp=(tmp+1ll*fac[tot[i]]*op%mod*inv[tot[i]-2*k]%mod*inv[k]%mod*po%mod*(((i&1)&&(i>1))?ksm(2,tot[i]-2*k):1))%mod,op=1ll*op*inv[2]%mod,po=1ll*po*i%mod;
		ans=1ll*ans*tmp%mod;
	}
	printf("%d\n",ans);
}

F - Black Radius

这题不是一般的难。
考虑先设一个状态 f ( x , d ) f(x,d) f(x,d)表示选择 x x x点, d d d距离所对应的点集,我们先不计算 f ( x , d ) = f(x,d)= f(x,d)=全集的情况,最后 + 1 +1 +1就可以了。

然后考虑怎么去重。对于 f ( x , d 1 ) = f ( y , d 2 ) f(x,d_1)=f(y,d_2) f(x,d1)=f(y,d2),对于该路径的一系列点 v 0 = x , v 1 , v 2 , . . . , v m = y v_0=x,v_1,v_2,...,v_{m}=y v0=x,v1,v2,...,vm=y

该式子成立:
f ( x , d 1 ) = f ( v 1 , d 1 − 1 ) = . . . = f ( v k , d 1 − k ) = . . . = f ( z , d 3 ) = . . . = f ( v k , d 2 − m + k ) = . . . = f ( v m − 1 , d 2 − 1 ) = f ( y , d 2 ) f(x,d_1)=f(v_1,d_1-1)=...=f(v_k,d_1-k)=...=f(z,d_3)=...=f(v_k,d_2-m+k)=...=f(v_{m-1},d_2-1)=f(y,d_2) f(x,d1)=f(v1,d11)=...=f(vk,d1k)=...=f(z,d3)=...=f(vk,d2m+k)=...=f(vm1,d21)=f(y,d2)
x , y x,y x,y拉到一条横线上,横线上面的点就是路径的点,将其他点摆在对应连接点的下放,我们管一个点下面的点叫做其的管辖点。

1.表示该路径上相等的点集所对应的距离 d d d一定会从 d 1 d_1 d1缩减到 d 3 d_3 d3,再从 d 3 d_3 d3增加到 d 2 d_2 d2
2.而且以 z z z为根时,只有 z z z的某些管辖点没有被覆盖。

证明如下:
先证明2的话,1就显而易见了。

那么现在 z z z处于 x , y x,y x,y之间,如果除了 z z z外的点的管辖点有未被覆盖的,从 x x x z z z y y y z z z考虑所有的 f ( v , d ) f(v,d) f(v,d),经过该点之后, d d d仍然不断减小,使得覆盖管辖点的数量不断减小,此时点集发生了改变,证毕。

靠近 z z z的时候显然 d d d要恰好减少 1 1 1,否则就会覆盖到更多的管辖点,使点集发生改变。

f ( v , d ) = f ( z , d 3 ) f(v,d)=f(z,d_3) f(v,d)=f(z,d3) d d d的最小值不一定是 d 3 d_3 d3,也就是说 z z z仍然可以走一条 d d d不断 − 1 -1 1的路径走到最小值,而这个最小值唯一。

有了这个,我们就可以很轻易的判断 f ( x , d ) f(x,d) f(x,d)是否要被计入答案了,如果存在一个 x x x的相邻点 y y y,满足 f ( x , d ) = f ( y , d − 1 ) f(x,d)=f(y,d-1) f(x,d)=f(y,d1),那么就不用管 f ( x , d ) f(x,d) f(x,d)

简单来说一个点管理的距离其实是 [ 0 , min ⁡ ( a − 1 , b + 1 ) ] [0,\min(a-1,b+1)] [0,min(a1,b+1)],其中 a a a表示以该点为根的最大深度, b b b表示以该点为根的次大深度。

如何证明?首先前面一个限制是显然的,后面一个限制考虑什么情况下才会不变。显然是往最大深度那里移动一步,此时满足 d − 1 ≥ b + 1 d-1\geq b+1 d1b+1,也就是说仍然可以包含次大子树,那么 d < b + 2 d<b+2 d<b+2的时候肯定就会发生改变了。

那么所有点都为好点的情况就会做了。

如果一个点不是好点,考虑怎么将该状态转移到另外一个好点?
我们只需要对于一个点来说,对于每一个子树看看是否有好点,如果有那么就对这个子树的最大深度取min,然后就得到了转移的下界。

#include<bits/stdc++.h>
using namespace std;

const int N=200010;
struct edge{
	int y,nex;
}s[N<<1];
int first[N],len=0,n,up[N],down[N],sz[N],g[N];
pair<int,int> f[N],tmp;
long long ans=0;
char ch[N];

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
}

void upd(pair<int,int>&x,int d){
	if(d>x.first) x.second=x.first,x.first=d;
	else if(d>x.second) x.second=d;
}

void dfs(int x,int fa){
	f[x].first=0;f[x].second=0;
	if(ch[x]=='1') sz[x]=1;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa)
		dfs(s[i].y,x),upd(f[x],f[s[i].y].first+1),sz[x]+=sz[s[i].y];
}

void dfs_2(int x,int fa){
	tmp.first=tmp.second=0;
	upd(tmp,f[x].first);upd(tmp,f[x].second);upd(tmp,g[x]);
	up[x]=min(tmp.first-1,tmp.second+1);
	vector<int> pre,las;
	if(ch[x]=='1') down[x]=0;
	else down[x]=up[x]+1;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
		pre.push_back(f[s[i].y].first+2);
		las.push_back(f[s[i].y].first+2);
	}
	for(int i=1;i<pre.size();i++) pre[i]=max(pre[i],pre[i-1]);
	for(int i=las.size()-2;i>=0;i--) las[i]=max(las[i],las[i+1]);
	int t=0;
	for(int i=first[x];i!=0;i=s[i].nex) if(s[i].y!=fa){
		int y=s[i].y;
		g[y]=g[x]+1;
		if(t) g[y]=max(g[y],pre[t-1]);
		if(t!=las.size()-1) g[y]=max(g[y],las[t+1]);
		t++;dfs_2(y,x);
		if(sz[s[i].y]) down[x]=min(down[x],f[y].first+1);
	}
	if(sz[1]-sz[x]) down[x]=min(down[x],g[x]);
	ans+=up[x]-down[x]+1;
}

int main(){
	scanf("%d",&n);
	int x,y;
	for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y),ins(y,x);
	scanf("%s",ch+1);
	dfs(1,0);dfs_2(1,0);
	printf("%lld\n",ans+1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值