Codeforces 1098 简要题解

文章目录

  • 前言
  • A题
  • B题
  • C题
  • D题
  • E题

传送门

前言

没错因为蒟蒻太菜了这场的最后一道题也咕掉了,只有 A A A E E E的题解233

A题

传送门
题意简述:给出一棵带点权的树,根节点深度为 1 1 1,现在你只知道深度为奇数的点到根路径上所有点的点权和(要求保证点权非负),问所有点的点权和最小值是多少。


思路:
显然让深度为偶数的点点权尽量大是最优策略。
代码:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0,w=1;
	char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans*w;
}
typedef long long ll;
const int N=1e5+5,inf=0x3f3f3f3f;
int n,s[N],a[N],dep[N],fa[N],mn[N];
vector<int>e[N];
void dfs(int p){
	if(dep[p]&1)mn[p]=inf;
	for(ri i=0,v;i<e[p].size();++i){
		dep[v=e[p][i]]=dep[p]^1,dfs(v);
		if(dep[p]&1){
			mn[p]=min(mn[p],s[v]);
			if(s[v]<s[fa[p]])puts("-1"),exit(0);
		}
	}
	if(dep[p]&1){
		if(e[p].size()){
			a[p]=mn[p]-s[fa[p]];
			for(ri i=0,v;i<e[p].size();++i)a[v=e[p][i]]=s[v]-s[fa[p]]-a[p];
		}
		else a[p]=0;
	}
}
int main(){
	n=read();
	for(ri i=2;i<=n;++i)e[(fa[i]=read())].push_back(i);
	for(ri i=1;i<=n;++i)s[i]=read();
	a[1]=s[1],dfs(1);
	ll ans=0;
	for(ri i=1;i<=n;++i)ans+=a[i];
	cout<<ans;
	return 0;
}

B题

传送门
题意简述:给你一个表格,上面只有四种字符 A , T , C , G A,T,C,G A,T,C,G,你可以把任意位置上的字符替换成这四种中的一种,问所有满足在替换之后对于每个 2 ∗ 2 2*2 22的字正方形四种字符全部都出现的表格中跟原表格相比替换的字符数最小值是多少,并输出任意一种替换之后的表格。


思路:考虑到最终的表格要么每一行只有两种字符,要么每一列只有两种字符,于是枚举左上角 2 ∗ 2 2*2 22的正方形的方法再枚举是每一行两种字符还是每一列两种字符更新答案即可。

代码:

#include
#define ri register int
using namespace std;
const int N=3e5+5;
int p[4],tmp[4],ans,type,n,m,h[N][4][2],l[N][4][2];
vector<int>a[N],print[N];
char s[N];
inline int idx(const char&x){return x<='C'?(x=='A'?0:1):(x=='T'?2:3);}
inline void Print(const int&x){cout<<(x<2?(x==0?'A':'C'):(x==2?'T':'G'));}
int main(){
	scanf("%d%d",&n,&m),ans=0;
	for(ri i=0;i<n;++i){
		scanf("%s",s);
		for(ri j=0;j<m;++j)a[i].push_back(idx(s[j]));
	}
	for(ri i=0;i<n;++i)print[i].resize(m);
	for(ri i=0;i<n;++i)for(ri j=0;j<m;++j)++h[i][a[i][j]][j&1],++l[j][a[i][j]][i&1];
	for(ri i=0;i<4;++i)p[i]=3-i;
	for(ri tt=1,cnt;tt<=24;++tt){
		next_permutation(p,p+4);
		cnt=0;
		for(ri i=0;i<n;++i)cnt+=max(h[i][p[(i&1)*2]][0]+h[i][p[(i&1)*2+1]][1],h[i][p[(i&1)*2]][1]+h[i][p[(i&1)*2+1]][0]);
		if(cnt>=ans){
			type=1,ans=cnt;
			for(ri i=0;i<4;++i)tmp[i]=p[i];
		}
		cnt=0;
		for(ri i=0;i<m;++i)cnt+=max(l[i][p[(i&1)*2]][0]+l[i][p[(i&1)*2+1]][1],l[i][p[(i&1)*2]][1]+l[i][p[(i&1)*2+1]][0]);
		if(cnt>=ans){
			type=2,ans=cnt;
			for(ri i=0;i<4;++i)tmp[i]=p[i];
		}
	}
	if(type==1){
		for(ri i=0,x,y;i<n;++i){
			x=tmp[(i&1)*2],y=tmp[(i&1)*2+1];
			if(h[i][x][0]+h[i][y][1]<h[i][x][1]+h[i][y][0])swap(x,y);
			for(ri j=0;j<m;++j){
				print[i][j]=j&1?y:x;
			}
		}
	}
	else{
		for(ri i=0,x,y;i<m;++i){
			x=tmp[(i&1)*2],y=tmp[(i&1)*2+1];
			if(l[i][x][0]+l[i][y][1]<l[i][x][1]+l[i][y][0])swap(x,y);
			for(ri j=0;j<n;++j){
				print[j][i]=j&1?y:x;
			}
		}		
	}
	for(ri i=0;i<n;++i,puts(""))for(ri j=0;j<m;++j)Print(print[i][j]);
	return 0;
}

C题

传送门
题意简述:问你能不能构造出一棵 n n n个点的树,使得每个点的 s i z e size size加起来等于 s s s,如果能输出使得这个树的叉数最小的任意一种方案。


思路:
先特判掉非法情况: s s s过大的情况。
然后有个显然的性质: ∑ s i z e i = ∑ d e p t h i \sum size_i=\sum depth_i sizei=depthi,知道了这个之后就可以从 1 1 1开始枚举这个树是几叉树,显然在所有 k k k叉树中完全 k k k叉树的 d e p t h depth depth之和是最小的。
因此如果当前完全 k k k叉树的 d e p t h depth depth之和大于 s s s是不可能的,直接考虑 k + 1 k+1 k+1叉树的情况。
否则当前一定可以构造出一种方案,我们把除最深点到根以外的所有点一个一个往当前最深点接即可,接到最后如果再接 d e p t h depth depth之和会超过 s s s的话一定可以找到一个之前接上的点使得把当前点接上去之后所有点的 d e p t h depth depth之和刚好等于 s s s
注意细节
代码:

#include
#define ri register int
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,fa[N],dept[N];
ll s;
bool vis[N];
inline void solve(const int&x){
	int tot=1;
	queue<int>q;
	q.push(1);
	while(!q.empty()){
		int p=q.front();
		q.pop(),dept[p]=dept[fa[p]]+1;
		for(ri i=1;i<=x&&tot<n;++i)q.push(++tot),fa[tot]=p;
	}
}
inline void check(const int&x){
	ll cnt=1,mul=1,dep=1;
	ll sum=1;
	while(cnt<n){
		mul*=x,cnt+=mul,++dep,sum+=(ll)dep*mul;
	}
	sum-=(ll)dep*(cnt-n);
	if(sum>s)return;
	puts("Yes");
	solve(x);
	int p=cnt-mul+1,pre=p;
	while(p)vis[p]=1,p=fa[p];
	p=pre;
	queue<int>q;
	for(ri i=n;i;--i)if(!vis[i])q.push(i);
	while(s>sum){
		p=q.front(),q.pop(),sum-=dept[p];
		fa[p]=pre,dept[p]=dept[fa[p]]+1,pre=p,sum+=dept[p];
	}
	if(sum>s){
		for(ri i=pre;i;i=fa[i]){
			if(sum-dept[pre]+dept[i]+1==s){
				fa[pre]=i,sum-=dept[pre],dept[pre]=dept[i]+1,sum+=dept[pre];
				break;
			}
		}
	}
	for(ri i=2;i<=n;++i)cout<<fa[i]<<' ';
	exit(0);
}
int main(){
	cin>>n>>s;
	if(s>(ll)(n+1)*n/2)return puts("No"),0;
	for(ri i=1;i<n;++i)check(i);
	return puts("No"),0;
}

D题

传送门
题意简述:现在有一个大鱼吃小鱼的游戏,每条鱼有一个权值,假如 a i ≤ a j a_i\le a_j aiaj那么 i i i会被 j j j吃掉并且 a j + = a i a_j+=a_i aj+=ai,如果对于两条吃与被吃的鱼满足 a ≤ b ≤ 2 a a\le b\le 2a ab2a称之为一次危险的打斗,现在要求支持向鱼群中加入一条鱼或者删去一条鱼(鱼群最初为空),问每次操作之后对所有鱼按最优方案安排打斗顺序能够产生的危险的打斗次数的最大值。


思路:
我们称一次危险的打斗 ( a , b ) ( a ≤ b ) (a,b) (a≤b) (a,b)(ab) b b b产生的。
可以发现如果按大小把鱼排序,第一条鱼不会产生危险的打斗,一条比其之前所有鱼大小之和的两倍更大的鱼不会产生危险的打斗。
这个时候我们可以构造出一种方案使得不满足上述两点的鱼均产生一次危险的打斗。
证明可以手玩一下,就只用考虑两条最小的鱼之前是否吃过其它鱼。
然后考虑维护方式:
我们把值域分为若干区间: [ 1 , 2 ) , [ 2 , 4 ) , [ 4 , 8 ) . . . [1,2),[2,4),[4,8)... [1,2),[2,4),[4,8)...,不难发现,只有区间最小值才有可能成为不产生危险打斗,对每个区间分别用可删堆维护信息即可。

#include
#define ri register int
using namespace std;
typedef long long ll;
struct In_Out_priority_queue{
	priority_queue<int,vector<int>,greater<int> >a,b;
	ll sum;
	int cnt;
	In_Out_priority_queue(){sum=cnt=0;while(!a.empty())a.pop();while(!b.empty())b.pop();}
	inline void push(const int&x){a.push(x),sum+=x,++cnt;}
	inline void del(const int&x){b.push(x),sum-=x,--cnt;}
	inline int top(){while(b.size()&&a.top()==b.top())a.pop(),b.pop();return a.top();}
}q[30];
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
int main(){
	for(ri ans,tt=read(),x;tt;--tt){
		char s[2];
		scanf("%s",s),x=read();
		for(ri i=0;i<30;++i){
			if(x<(1<<(i+1))){
				if(s[0]=='+')q[i].push(x);
				else q[i].del(x);
				break;
			}
		}
		ans=0;
		ll sum=0;
		for(ri i=0;i<30;++i){
			if(!q[i].cnt)continue;
			ans+=q[i].cnt;
			if(q[i].top()>2*sum)--ans;
			sum+=q[i].sum;
		}
		cout<<ans<<'\n';
	}
	return 0;
}

E题

传送门
题意简述:先给出一个 n n n个数的数组 a , n ≤ 100000 a,n\le100000 a,n100000,然后把所有 a a a的子区间的 g c d gcd gcd提出来排个序构成数组 b b b,然后把 b b b的所有子区间的和提出来排个序构成数组 c c c,问 c c c数组的中位数。


思路:
感觉考了好多知识点啊。
首先考虑求出数组 b b b,直接用 s t st st表预处理+枚举子区间左端点+二分 l o g log log次求出不同的 g c d gcd gcd段可以弄出来 b b b数组。
然后考虑二分 c c c数组的中位数。
考虑如何 c h e c k check check,如果 b b b数组小的话貌似双指针就行了。
但现在 b b b数组的长度是 O ( n 2 ) O(n^2) O(n2)的直接做会爆,但是由于有很多相同的值,我们可以把相同的值压在一起,这样还是可以双指针,然后我们发现对于一个压起来的值有可能只会用到其中的一部分,考虑怎么做这个情况。
由于当前区间访问的连续性,只有一头一尾可能是访问的不完整的,因此相当于是求一个 v a l l x + v a l r y ≤ z val_lx+val_ry\le z vallx+valryz的正整数解组数,上类欧几里得即可。
代码:

#include
#define fi first
#define se second
#define ri register int
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N=50005;
int n,a[N],tot=0;
pii b[N*20];
ll lim;
vector<pii>B;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
inline int gcd(int a,int b){while(b){int t=a;a=b,b=t-t/a*a;}return a;}
namespace ST{
	int st[N][17],Log[N];
	inline void init(){
		Log[0]=-1;
		for(ri i=1;i<=n;++i)st[i][0]=a[i],Log[i]=Log[i>>1]+1;
		for(ri j=1;j<=16;++j)for(ri i=1;i+(1<<j)-1<=n;++i){
			st[i][j]=gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
		}
	}
	inline int query(int l,int r){
		int k=Log[r-l+1];
		return gcd(st[l][k],st[r-(1<<k)+1][k]);
	}
}
inline ll calc(ll len,ll k){return k*(k+1)/2+k*(len-k);}
inline ll solve(ll a,ll b,ll c,ll t){
	if(!a)return (b/c)*(t+1);
	if(a>=c||b>=c)return solve(a%c,b%c,c,t)+t*(t+1)/2*(a/c)+(t+1)*(b/c);
	ll m=(a*t+b)/c;
	return t*m-solve(c,-b+c-1,a,m-1);
}
inline ll Calc(ll Lim,int pl,int pr){
	ll x=b[pl].fi,y=b[pr].fi,lim1=b[pl].se-1,lim2=b[pr].se-1;
	if(Lim<0)return 0;
	ll ret=0;
	if(Lim>=y*lim2){
		if(Lim>=x*lim1+y*lim2)return (lim1+1)*(lim2+1);
		ll tmp=(Lim-y*lim2)/x;
		ret+=(lim2+1)*(tmp+1),Lim-=(tmp+1)*x,lim1-=tmp+1;
	}
	if(Lim<0)return ret;
	return lim1=min(lim1,Lim/x),ret+lim1+1+solve(x,Lim-lim1*x,y,lim1);
}
inline bool check(const ll&tmp){
	ll stmp,sum=0,len=0,ans=0;
	for(ri i=1;i<=n;++i)ans+=calc((ll)b[i].se,(ll)min((ll)b[i].se,tmp/b[i].fi));
	for(ri i=1,j=1;i<=n;++i){
		while(j<i&&sum+(ll)b[i].fi*b[i].se>tmp)len-=b[j].se,sum-=(ll)b[j].fi*b[j].se,++j;
		ans+=len*b[i].se;
		stmp=sum;
		for(ri k=j-1;k&&stmp<=tmp;--k){
			ans+=Calc(tmp-stmp-b[i].fi-b[k].fi,i,k);
			stmp+=(ll)b[k].fi*b[k].se;
		}
		len+=b[i].se,sum+=(ll)b[i].fi*b[i].se;
	}
	return ans<lim;
}
int main(){
	n=read();
	for(ri i=1;i<=n;++i)a[i]=read();
	ST::init();
	for(ri i=1,x,pl,l,r,res;i<=n;++i){
		l=i;
		while(l<=n){
			res=l,r=n,x=ST::query(i,l),pl=l;
			while(l<=r){
				int mid=l+r>>1;
				if(ST::query(i,mid)==x)l=mid+1,res=mid;
				else r=mid-1;
			}
			B.push_back(pii(x,res-pl+1));
			l=res+1;
		}
	}
	sort(B.begin(),B.end());
	b[++tot]=B[0];
	for(ri i=1;i<B.size();++i)if(b[tot].fi^B[i].fi)b[++tot]=B[i];else b[tot].se+=B[i].se;
	lim=(ll)n*(n+1)/2,lim=lim*(lim+1)/2,lim=(lim+1)>>1,n=tot;
	ll l=1,r=0,res;
	for(ri i=1;i<=n;++i)r+=(ll)b[i].fi*b[i].se;
	res=r;
	while(l<=r){
		ll mid=l+r>>1;
		if(!check(mid))r=mid-1,res=mid;
		else l=mid+1;
	}
	cout<<res;
	return 0;
}

你可能感兴趣的:(#,题解)